ultra_settings 2.9.1 → 2.10.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/CHANGELOG.md +36 -0
- data/README.md +24 -0
- data/VERSION +1 -1
- data/app/_config_description.html.erb +10 -1
- data/app/_data_source.html.erb +2 -2
- data/app/application.css +119 -26
- data/app/application_vars.css.erb +40 -14
- data/app/configuration.html.erb +19 -1
- data/app/layout.css +41 -2
- data/app/layout.html.erb +46 -0
- data/app/layout_vars.css.erb +12 -8
- data/app/locales/ar.json +5 -2
- data/app/locales/cs.json +5 -2
- data/app/locales/da.json +5 -2
- data/app/locales/de.json +5 -2
- data/app/locales/el.json +5 -2
- data/app/locales/en.json +5 -2
- data/app/locales/es.json +5 -2
- data/app/locales/fa.json +5 -2
- data/app/locales/fr.json +5 -2
- data/app/locales/gd.json +5 -2
- data/app/locales/he.json +5 -2
- data/app/locales/hi.json +5 -2
- data/app/locales/it.json +5 -2
- data/app/locales/ja.json +5 -2
- data/app/locales/ko.json +5 -2
- data/app/locales/lt.json +5 -2
- data/app/locales/nb.json +5 -2
- data/app/locales/nl.json +5 -2
- data/app/locales/pl.json +5 -2
- data/app/locales/pt-br.json +5 -2
- data/app/locales/pt.json +5 -2
- data/app/locales/ru.json +5 -2
- data/app/locales/sv.json +5 -2
- data/app/locales/ta.json +5 -2
- data/app/locales/tr.json +5 -2
- data/app/locales/uk.json +5 -2
- data/app/locales/ur.json +5 -2
- data/app/locales/vi.json +5 -2
- data/app/locales/zh-cn.json +5 -2
- data/app/locales/zh-tw.json +5 -2
- data/lib/ultra_settings/application_view.rb +17 -7
- data/lib/ultra_settings/audit_data_sources.rb +1 -1
- data/lib/ultra_settings/coerce.rb +1 -1
- data/lib/ultra_settings/configuration.rb +36 -10
- data/lib/ultra_settings/configuration_view.rb +166 -9
- data/lib/ultra_settings/mini_i18n.rb +14 -10
- data/lib/ultra_settings/rack_app.rb +4 -4
- data/lib/ultra_settings/view_helper.rb +27 -5
- data/lib/ultra_settings/web_view.rb +26 -16
- data/lib/ultra_settings/yaml_config.rb +10 -2
- data/lib/ultra_settings.rb +66 -2
- data/ultra_settings.gemspec +1 -1
- metadata +2 -2
|
@@ -24,13 +24,24 @@ module UltraSettings
|
|
|
24
24
|
|
|
25
25
|
# Render the HTML for the configuration view.
|
|
26
26
|
#
|
|
27
|
+
# The runtime settings cache is reloaded before rendering so that values changed
|
|
28
|
+
# from the UI are displayed immediately rather than after the runtime settings
|
|
29
|
+
# engine next refreshes itself. If a page renders more than one view, wrap them all
|
|
30
|
+
# in `UltraSettings.with_runtime_settings_reloaded` so that the settings are only
|
|
31
|
+
# reloaded once for the page instead of once per view.
|
|
32
|
+
#
|
|
27
33
|
# @param table_class [String] @deprecated CSS class for the table element (maintained for backwards compatibility).
|
|
28
34
|
# @return [String] The rendered HTML.
|
|
29
35
|
def render(table_class: "")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
UltraSettings.with_runtime_settings_reloaded do
|
|
37
|
+
# Expose configuration as a local for the ERB template without a direct
|
|
38
|
+
# assignment, which would emit an unused variable warning under ruby -w.
|
|
39
|
+
template_binding = binding
|
|
40
|
+
template_binding.local_variable_set(:configuration, @configuration)
|
|
41
|
+
html = ViewHelper.erb_template("configuration.html.erb").result(template_binding)
|
|
42
|
+
html = html.html_safe if html.respond_to?(:html_safe)
|
|
43
|
+
html
|
|
44
|
+
end
|
|
34
45
|
end
|
|
35
46
|
|
|
36
47
|
# Convert the view to a string by rendering it.
|
|
@@ -70,6 +81,25 @@ module UltraSettings
|
|
|
70
81
|
end
|
|
71
82
|
end
|
|
72
83
|
|
|
84
|
+
# The text placed on the clipboard by the copy button. This is the raw value
|
|
85
|
+
# rather than the inspected value shown in the UI so that, for example, copying
|
|
86
|
+
# a string setting does not include the surrounding quotes.
|
|
87
|
+
#
|
|
88
|
+
# @param value [Object] The setting value.
|
|
89
|
+
# @return [String] The text to copy.
|
|
90
|
+
def copy_value(value)
|
|
91
|
+
case value
|
|
92
|
+
when nil
|
|
93
|
+
""
|
|
94
|
+
when Time
|
|
95
|
+
value.iso8601
|
|
96
|
+
when Array
|
|
97
|
+
value.join("\n")
|
|
98
|
+
else
|
|
99
|
+
value.to_s
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
73
103
|
def secret_value(value)
|
|
74
104
|
if value.nil?
|
|
75
105
|
t("field.nil")
|
|
@@ -78,13 +108,63 @@ module UltraSettings
|
|
|
78
108
|
end
|
|
79
109
|
end
|
|
80
110
|
|
|
111
|
+
# Shorten a file path for display by making it relative to the working
|
|
112
|
+
# directory or to the YAML configuration directory. The absolute path is
|
|
113
|
+
# used if the file is not inside either directory.
|
|
114
|
+
#
|
|
115
|
+
# @param path [Pathname] The absolute file path.
|
|
116
|
+
# @return [String] The path to display.
|
|
81
117
|
def relative_path(path)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
118
|
+
paths = display_paths(Pathname.new(path).expand_path)
|
|
119
|
+
display_path_roots.each do |root|
|
|
120
|
+
paths.each do |file_path|
|
|
121
|
+
relative = path_inside(file_path, root)
|
|
122
|
+
return relative if relative
|
|
123
|
+
end
|
|
86
124
|
end
|
|
87
|
-
|
|
125
|
+
paths.first.to_s
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The file path in both its literal and symlink resolved forms so that it can
|
|
129
|
+
# be matched against a directory that is specified in either form.
|
|
130
|
+
#
|
|
131
|
+
# @param path [Pathname] The absolute file path.
|
|
132
|
+
# @return [Array<Pathname>]
|
|
133
|
+
def display_paths(path)
|
|
134
|
+
[path, resolved_path(path.dirname)&.join(path.basename)].compact.uniq
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Directories that a displayed path can be made relative to, in order of
|
|
138
|
+
# preference. Directories are listed in both their literal and symlink
|
|
139
|
+
# resolved forms since a file path can be in either form.
|
|
140
|
+
#
|
|
141
|
+
# @return [Array<Pathname>]
|
|
142
|
+
def display_path_roots
|
|
143
|
+
roots = [Pathname.new(Dir.pwd)]
|
|
144
|
+
config_path = UltraSettings::Configuration.yaml_config_path
|
|
145
|
+
roots << Pathname.new(config_path) if config_path
|
|
146
|
+
roots.flat_map { |root| [root.expand_path, resolved_path(root)] }.compact.uniq
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @param path [Pathname] The absolute file path.
|
|
150
|
+
# @param root [Pathname] The absolute directory path.
|
|
151
|
+
# @return [String, nil] The path relative to the directory or nil if it is not inside it.
|
|
152
|
+
def path_inside(path, root)
|
|
153
|
+
relative = path.relative_path_from(root).to_s.delete_prefix("./")
|
|
154
|
+
return nil if relative == "." || relative.start_with?("..")
|
|
155
|
+
|
|
156
|
+
relative
|
|
157
|
+
rescue ArgumentError
|
|
158
|
+
# relative_path_from raises if the paths have no common root (e.g. different drives).
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @param path [Pathname] The directory path.
|
|
163
|
+
# @return [Pathname, nil] The path with symlinks resolved or nil if it does not exist.
|
|
164
|
+
def resolved_path(path)
|
|
165
|
+
path.realpath
|
|
166
|
+
rescue SystemCallError
|
|
167
|
+
nil
|
|
88
168
|
end
|
|
89
169
|
|
|
90
170
|
def source_chip_label(source)
|
|
@@ -116,6 +196,34 @@ module UltraSettings
|
|
|
116
196
|
end
|
|
117
197
|
end
|
|
118
198
|
|
|
199
|
+
# True if the YAML keys for a configuration are hidden behind a toggle button.
|
|
200
|
+
# They are hidden by default when the YAML file does not exist since the keys
|
|
201
|
+
# are not used by the application.
|
|
202
|
+
#
|
|
203
|
+
# @param configuration [UltraSettings::Configuration] The configuration instance.
|
|
204
|
+
# @return [Boolean]
|
|
205
|
+
def hide_yaml_keys?(configuration)
|
|
206
|
+
config_class = configuration.class
|
|
207
|
+
file = config_class.configuration_file
|
|
208
|
+
file.is_a?(Pathname) && !file.exist? && config_class.fields.any?(&:yaml_key)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Inline script for the button that shows and hides the YAML keys. It is
|
|
212
|
+
# inlined on the element so that the button also works when the configuration
|
|
213
|
+
# view is embedded in a host application page that does not include the
|
|
214
|
+
# bundled JavaScript.
|
|
215
|
+
#
|
|
216
|
+
# @return [String] JavaScript source for an onclick attribute.
|
|
217
|
+
def toggle_yaml_keys_script
|
|
218
|
+
<<~JAVASCRIPT.gsub(/\s+/, " ").tr('"', "'")
|
|
219
|
+
var block = this.closest('.ultra-settings-block');
|
|
220
|
+
if (block) {
|
|
221
|
+
var hidden = block.classList.toggle('ultra-settings-yaml-hidden');
|
|
222
|
+
this.setAttribute('aria-checked', hidden ? 'false' : 'true');
|
|
223
|
+
}
|
|
224
|
+
JAVASCRIPT
|
|
225
|
+
end
|
|
226
|
+
|
|
119
227
|
def open_panel_script
|
|
120
228
|
<<~JAVASCRIPT.gsub(/\s+/, " ").tr('"', "'")
|
|
121
229
|
var el = this;
|
|
@@ -146,6 +254,38 @@ module UltraSettings
|
|
|
146
254
|
JAVASCRIPT
|
|
147
255
|
end
|
|
148
256
|
|
|
257
|
+
# Inline script for the copy button. It is inlined on the element so that the
|
|
258
|
+
# button also works when the configuration view is embedded in a host
|
|
259
|
+
# application page that does not include the bundled JavaScript.
|
|
260
|
+
#
|
|
261
|
+
# @return [String] JavaScript source for an onclick attribute.
|
|
262
|
+
def copy_value_script
|
|
263
|
+
<<~JAVASCRIPT.gsub(/\s+/, " ").tr('"', "'")
|
|
264
|
+
var btn = this;
|
|
265
|
+
var text = btn.dataset.copyValue || '';
|
|
266
|
+
var flash = function() {
|
|
267
|
+
btn.classList.add('copied');
|
|
268
|
+
window.setTimeout(function() { btn.classList.remove('copied'); }, 1500);
|
|
269
|
+
};
|
|
270
|
+
var fallback = function() {
|
|
271
|
+
var input = document.createElement('textarea');
|
|
272
|
+
input.value = text;
|
|
273
|
+
input.setAttribute('readonly', '');
|
|
274
|
+
input.style.position = 'fixed';
|
|
275
|
+
input.style.opacity = '0';
|
|
276
|
+
document.body.appendChild(input);
|
|
277
|
+
input.select();
|
|
278
|
+
try { if (document.execCommand('copy')) { flash(); } } catch (e) {}
|
|
279
|
+
document.body.removeChild(input);
|
|
280
|
+
};
|
|
281
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
282
|
+
navigator.clipboard.writeText(text).then(flash, fallback);
|
|
283
|
+
} else {
|
|
284
|
+
fallback();
|
|
285
|
+
}
|
|
286
|
+
JAVASCRIPT
|
|
287
|
+
end
|
|
288
|
+
|
|
149
289
|
def source_priority
|
|
150
290
|
[:env, :settings, :yaml, :default]
|
|
151
291
|
end
|
|
@@ -196,6 +336,23 @@ module UltraSettings
|
|
|
196
336
|
HTML
|
|
197
337
|
end
|
|
198
338
|
|
|
339
|
+
def copy_icon(size = 13)
|
|
340
|
+
<<~HTML
|
|
341
|
+
<svg class="ultra-settings-copy-icon" width="#{size}" height="#{size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
342
|
+
<rect x="9" y="9" width="13" height="13" rx="2"/>
|
|
343
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
|
344
|
+
</svg>
|
|
345
|
+
HTML
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def check_icon(size = 13)
|
|
349
|
+
<<~HTML
|
|
350
|
+
<svg class="ultra-settings-copy-check" width="#{size}" height="#{size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
351
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
352
|
+
</svg>
|
|
353
|
+
HTML
|
|
354
|
+
end
|
|
355
|
+
|
|
199
356
|
def close_icon(size = 16)
|
|
200
357
|
<<~HTML
|
|
201
358
|
<svg width="#{size}" height="#{size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
@@ -76,26 +76,30 @@ module UltraSettings
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# Load every JSON file from the locales directory, keyed by filename
|
|
79
|
-
# stem (e.g. "en").
|
|
79
|
+
# stem (e.g. "en"). The cache is built into a local hash and then
|
|
80
|
+
# published with a single assignment so that concurrent readers never
|
|
81
|
+
# see a partially loaded cache.
|
|
80
82
|
def load_all_locales
|
|
81
|
-
if development_mode?
|
|
82
|
-
@mutex.synchronize { @cache = {} }
|
|
83
|
-
end
|
|
83
|
+
clear_cache! if development_mode?
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
cached = @cache
|
|
86
|
+
return cached unless cached.empty?
|
|
86
87
|
|
|
87
88
|
@mutex.synchronize do
|
|
88
|
-
|
|
89
|
+
cached = @cache
|
|
90
|
+
return cached unless cached.empty?
|
|
89
91
|
|
|
92
|
+
cache = {}
|
|
90
93
|
Dir.glob(File.join(locales_dir, "*.json")).each do |path|
|
|
91
94
|
code = File.basename(path, ".json").downcase
|
|
92
|
-
|
|
95
|
+
cache[code] = JSON.parse(File.read(path, encoding: Encoding::UTF_8))
|
|
93
96
|
rescue JSON::ParserError
|
|
94
97
|
# Skip malformed locale files
|
|
95
98
|
end
|
|
96
|
-
end
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
@cache = cache
|
|
101
|
+
cache
|
|
102
|
+
end
|
|
99
103
|
end
|
|
100
104
|
|
|
101
105
|
def locales_dir
|
|
@@ -103,7 +107,7 @@ module UltraSettings
|
|
|
103
107
|
end
|
|
104
108
|
|
|
105
109
|
def development_mode?
|
|
106
|
-
|
|
110
|
+
UltraSettings.__development_mode__?
|
|
107
111
|
end
|
|
108
112
|
end
|
|
109
113
|
end
|
|
@@ -26,9 +26,9 @@ module UltraSettings
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
28
|
def webview
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
# Don't cache the view in development mode so that changes to the app files
|
|
30
|
+
# are picked up without having to restart the server.
|
|
31
|
+
@webview = nil if UltraSettings.__development_mode__?
|
|
32
32
|
@webview ||= WebView.new(color_scheme: @color_scheme)
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -62,7 +62,7 @@ module UltraSettings
|
|
|
62
62
|
parts = entry.strip.split(";")
|
|
63
63
|
tag = parts[0].to_s.strip.downcase.tr("_", "-")
|
|
64
64
|
q = 1.0
|
|
65
|
-
parts[1
|
|
65
|
+
parts[1..].each do |p|
|
|
66
66
|
if p.strip.start_with?("q=")
|
|
67
67
|
q = p.strip.sub("q=", "").to_f
|
|
68
68
|
end
|
|
@@ -4,6 +4,7 @@ module UltraSettings
|
|
|
4
4
|
# Base class for rendering views.
|
|
5
5
|
module ViewHelper
|
|
6
6
|
@cache = {}
|
|
7
|
+
@mutex = Mutex.new
|
|
7
8
|
|
|
8
9
|
class << self
|
|
9
10
|
# Get an ERB template for rendering.
|
|
@@ -11,8 +12,7 @@ module UltraSettings
|
|
|
11
12
|
# @param path [String] The path to the template file.
|
|
12
13
|
# @return [ERB] The compiled ERB template.
|
|
13
14
|
def erb_template(path)
|
|
14
|
-
|
|
15
|
-
@cache["erb:#{path}"] ||= ERB.new(read_app_file(path))
|
|
15
|
+
fetch("erb:#{path}") { ERB.new(read_app_file(path)) }
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Read a file from the app directory.
|
|
@@ -20,8 +20,7 @@ module UltraSettings
|
|
|
20
20
|
# @param path [String] The path to the file relative to the app directory.
|
|
21
21
|
# @return [String] The contents of the file.
|
|
22
22
|
def read_app_file(path)
|
|
23
|
-
|
|
24
|
-
@cache["file:#{path}"] ||= File.read(File.join(app_dir, path))
|
|
23
|
+
fetch("file:#{path}") { File.read(File.join(app_dir, path), encoding: Encoding::UTF_8) }
|
|
25
24
|
end
|
|
26
25
|
|
|
27
26
|
# Get the app directory path.
|
|
@@ -33,8 +32,31 @@ module UltraSettings
|
|
|
33
32
|
|
|
34
33
|
private
|
|
35
34
|
|
|
35
|
+
# Fetch a value from the cache, generating it with the block if needed.
|
|
36
|
+
# The value is generated outside of the lock since the block may itself
|
|
37
|
+
# fetch other cached values. The cache hash is never mutated in place; a
|
|
38
|
+
# copy is published with a single assignment so concurrent readers always
|
|
39
|
+
# see a consistent hash. Two threads may generate the same value
|
|
40
|
+
# concurrently; the first one to publish wins.
|
|
41
|
+
def fetch(key, &block)
|
|
42
|
+
return yield if development_mode?
|
|
43
|
+
|
|
44
|
+
cached = @cache
|
|
45
|
+
return cached[key] if cached.include?(key)
|
|
46
|
+
|
|
47
|
+
value = yield
|
|
48
|
+
@mutex.synchronize do
|
|
49
|
+
if @cache.include?(key)
|
|
50
|
+
@cache[key]
|
|
51
|
+
else
|
|
52
|
+
@cache = @cache.merge(key => value)
|
|
53
|
+
value
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
36
58
|
def development_mode?
|
|
37
|
-
|
|
59
|
+
UltraSettings.__development_mode__?
|
|
38
60
|
end
|
|
39
61
|
end
|
|
40
62
|
end
|
|
@@ -9,23 +9,38 @@ module UltraSettings
|
|
|
9
9
|
|
|
10
10
|
# Initialize a new WebView with the specified color scheme.
|
|
11
11
|
#
|
|
12
|
-
# @param color_scheme [Symbol] The color scheme to use in the UI. This can be `:light`,
|
|
13
|
-
# `:dark`, or `:system`.
|
|
12
|
+
# @param color_scheme [Symbol, nil] The color scheme to use in the UI. This can be `:light`,
|
|
13
|
+
# `:dark`, or `:system`. When `nil`, a toggle control is rendered and
|
|
14
|
+
# `[data-theme=dark]` is used as the dark mode CSS selector.
|
|
14
15
|
def initialize(color_scheme: :light)
|
|
15
|
-
@color_scheme =
|
|
16
|
+
@color_scheme = color_scheme&.to_sym
|
|
17
|
+
@dark_mode_selector = @color_scheme.nil? ? "[data-theme=dark]" : nil
|
|
16
18
|
@layout_template = ViewHelper.erb_template("layout.html.erb")
|
|
17
|
-
@layout_css = scheme_layout_css(@color_scheme)
|
|
19
|
+
@layout_css = scheme_layout_css(@color_scheme, @dark_mode_selector)
|
|
20
|
+
@locale = nil
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
# Render the complete settings page HTML.
|
|
21
24
|
#
|
|
22
|
-
#
|
|
25
|
+
# This instance may be shared between concurrent requests, so per-request
|
|
26
|
+
# state is set on a copy of the view rather than on the shared instance.
|
|
27
|
+
#
|
|
28
|
+
# @param request [Rack::Request, nil] The current Rack request. It is accepted
|
|
29
|
+
# for backward compatibility but is not used by the view; access control and
|
|
30
|
+
# locale resolution are the caller's responsibility.
|
|
23
31
|
# @param locale [String] The locale code for translations.
|
|
24
32
|
# @return [String] The rendered HTML page.
|
|
25
33
|
def render_settings(request = nil, locale: UltraSettings::MiniI18n::DEFAULT_LOCALE)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
renderer = dup
|
|
35
|
+
renderer.instance_variable_set(:@locale, locale)
|
|
36
|
+
renderer.render_layout
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Render the layout template in the context of this instance.
|
|
40
|
+
#
|
|
41
|
+
# @return [String] The rendered HTML page.
|
|
42
|
+
# @api private
|
|
43
|
+
def render_layout
|
|
29
44
|
@layout_template.result(binding)
|
|
30
45
|
end
|
|
31
46
|
|
|
@@ -34,7 +49,8 @@ module UltraSettings
|
|
|
34
49
|
# @return [String] The HTML content for the settings.
|
|
35
50
|
def content
|
|
36
51
|
UltraSettings::ApplicationView.new(
|
|
37
|
-
color_scheme: @color_scheme,
|
|
52
|
+
color_scheme: @color_scheme || :light,
|
|
53
|
+
dark_mode_selector: @dark_mode_selector,
|
|
38
54
|
locale: @locale || UltraSettings::MiniI18n::DEFAULT_LOCALE
|
|
39
55
|
).render
|
|
40
56
|
end
|
|
@@ -56,16 +72,10 @@ module UltraSettings
|
|
|
56
72
|
|
|
57
73
|
private
|
|
58
74
|
|
|
59
|
-
def scheme_layout_css(color_scheme)
|
|
75
|
+
def scheme_layout_css(color_scheme, dark_mode_selector)
|
|
60
76
|
vars = ViewHelper.erb_template("layout_vars.css.erb").result(binding)
|
|
61
77
|
css = ViewHelper.read_app_file("layout.css")
|
|
62
78
|
"#{vars}\n#{css}"
|
|
63
79
|
end
|
|
64
|
-
|
|
65
|
-
def refresh_super_settings!
|
|
66
|
-
return unless defined?(SuperSettings) && UltraSettings.__runtime_settings__ == SuperSettings
|
|
67
|
-
|
|
68
|
-
SuperSettings.refresh_settings
|
|
69
|
-
end
|
|
70
80
|
end
|
|
71
81
|
end
|
|
@@ -52,17 +52,25 @@ module UltraSettings
|
|
|
52
52
|
private
|
|
53
53
|
|
|
54
54
|
def load_yaml(path)
|
|
55
|
-
yaml = File.read(path)
|
|
55
|
+
yaml = File.read(path, encoding: Encoding::UTF_8)
|
|
56
56
|
|
|
57
57
|
if yaml.include?("<%")
|
|
58
58
|
yaml = ERB.new(yaml).result
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
hash =
|
|
61
|
+
hash = parse_yaml(yaml)
|
|
62
62
|
hash = {} unless hash.is_a?(Hash)
|
|
63
63
|
hash
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
def parse_yaml(yaml)
|
|
67
|
+
YAML.load(yaml, aliases: true, permitted_classes: [Symbol, Date, Time]) # rubocop:disable Security/YAMLLoad
|
|
68
|
+
rescue ArgumentError
|
|
69
|
+
# Psych 3 does not support the aliases or permitted_classes options; its
|
|
70
|
+
# YAML.load already allows aliases and arbitrary classes.
|
|
71
|
+
YAML.load(yaml) # rubocop:disable Security/YAMLLoad
|
|
72
|
+
end
|
|
73
|
+
|
|
66
74
|
def environment_config(yaml, environment)
|
|
67
75
|
shared = flatten_hash(yaml.fetch("shared", {}))
|
|
68
76
|
env = flatten_hash(yaml.fetch(environment, {}))
|
data/lib/ultra_settings.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
4
|
require "yaml"
|
|
5
|
+
require "date"
|
|
5
6
|
require "time"
|
|
6
7
|
require "pathname"
|
|
7
8
|
require "singleton"
|
|
@@ -32,6 +33,10 @@ module UltraSettings
|
|
|
32
33
|
|
|
33
34
|
VALID_NAME_PATTERN = /\A[a-z_][a-zA-Z0-9_]*\z/
|
|
34
35
|
|
|
36
|
+
# Thread local key used to make the runtime settings reload in the web views reentrant.
|
|
37
|
+
RELOAD_GUARD_KEY = :ultra_settings_runtime_settings_reloaded
|
|
38
|
+
private_constant :RELOAD_GUARD_KEY
|
|
39
|
+
|
|
35
40
|
@configurations = {}
|
|
36
41
|
@mutex = Mutex.new
|
|
37
42
|
@runtime_settings = nil
|
|
@@ -177,6 +182,57 @@ module UltraSettings
|
|
|
177
182
|
@runtime_settings
|
|
178
183
|
end
|
|
179
184
|
|
|
185
|
+
# Reload the runtime settings cache and then yield to the block. The web views call
|
|
186
|
+
# this when rendering so that a setting changed from the UI is displayed on the next
|
|
187
|
+
# page load rather than whenever the runtime settings engine gets around to
|
|
188
|
+
# refreshing itself.
|
|
189
|
+
#
|
|
190
|
+
# The runtime settings object is reloaded if it responds to `load_settings`. The
|
|
191
|
+
# `super_settings` gem does, as does anything that delegates to it.
|
|
192
|
+
#
|
|
193
|
+
# Calls are reentrant, so a block that renders several views only reloads once.
|
|
194
|
+
# Wrap a page that embeds more than one `ConfigurationView` in this method to avoid
|
|
195
|
+
# reloading the runtime settings once per view.
|
|
196
|
+
#
|
|
197
|
+
# Errors are not fatal; the page can still show values from the other sources.
|
|
198
|
+
#
|
|
199
|
+
# @example Rendering several configurations with a single reload.
|
|
200
|
+
# UltraSettings.with_runtime_settings_reloaded do
|
|
201
|
+
# configurations.each { |config| output << UltraSettings::ConfigurationView.new(config).render }
|
|
202
|
+
# end
|
|
203
|
+
#
|
|
204
|
+
# @return [Object] The result of the block.
|
|
205
|
+
def with_runtime_settings_reloaded
|
|
206
|
+
return yield if Thread.current[RELOAD_GUARD_KEY]
|
|
207
|
+
|
|
208
|
+
Thread.current[RELOAD_GUARD_KEY] = true
|
|
209
|
+
begin
|
|
210
|
+
settings = __runtime_settings__
|
|
211
|
+
if settings.respond_to?(:load_settings)
|
|
212
|
+
begin
|
|
213
|
+
settings.load_settings
|
|
214
|
+
rescue => e
|
|
215
|
+
warn("UltraSettings: unable to reload runtime settings: #{e.class}: #{e.message}")
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
yield
|
|
220
|
+
ensure
|
|
221
|
+
Thread.current[RELOAD_GUARD_KEY] = nil
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Returns true if the application is running in development mode. This is used by the
|
|
226
|
+
# web UI to decide if templates, stylesheets, and translations can be cached in memory
|
|
227
|
+
# or if they need to be re-read from disk on every request.
|
|
228
|
+
#
|
|
229
|
+
# @return [Boolean]
|
|
230
|
+
# @api private
|
|
231
|
+
def __development_mode__?
|
|
232
|
+
env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || ENV["APP_ENV"] || "development"
|
|
233
|
+
env == "development"
|
|
234
|
+
end
|
|
235
|
+
|
|
180
236
|
# Set the URL for changing runtime settings. If this is set, then a link to the
|
|
181
237
|
# URL will be displayed in the web view for fields that support runtime settings.
|
|
182
238
|
# The URL may contain a `${name}` placeholder that will be replaced with the name
|
|
@@ -266,7 +322,7 @@ module UltraSettings
|
|
|
266
322
|
settings = settings.to_a
|
|
267
323
|
config_name, values = settings.first
|
|
268
324
|
config_name = config_name.to_s
|
|
269
|
-
other_settings = settings[1
|
|
325
|
+
other_settings = settings[1..]
|
|
270
326
|
|
|
271
327
|
unless @configurations.include?(config_name)
|
|
272
328
|
raise ArgumentError.new("Unknown configuration: #{config_name.inspect}")
|
|
@@ -299,7 +355,15 @@ module UltraSettings
|
|
|
299
355
|
__load_config__(name, class_name)
|
|
300
356
|
end
|
|
301
357
|
|
|
302
|
-
config_classes = ObjectSpace.each_object(Class).select
|
|
358
|
+
config_classes = ObjectSpace.each_object(Class).select do |klass|
|
|
359
|
+
next false unless klass < Configuration
|
|
360
|
+
next false if klass.name.nil?
|
|
361
|
+
begin
|
|
362
|
+
constantize(klass.name).equal?(klass)
|
|
363
|
+
rescue NameError
|
|
364
|
+
false
|
|
365
|
+
end
|
|
366
|
+
end
|
|
303
367
|
config_classes.collect(&:instance)
|
|
304
368
|
end
|
|
305
369
|
|
data/ultra_settings.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ultra_settings
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.10.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Durand
|
|
@@ -110,7 +110,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
110
110
|
requirements:
|
|
111
111
|
- - ">="
|
|
112
112
|
- !ruby/object:Gem::Version
|
|
113
|
-
version: '2.
|
|
113
|
+
version: '2.6'
|
|
114
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
115
|
requirements:
|
|
116
116
|
- - ">="
|