-
-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathlocal_config.rb
More file actions
142 lines (120 loc) · 4.31 KB
/
local_config.rb
File metadata and controls
142 lines (120 loc) · 4.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# frozen_string_literal: true
require 'yaml'
begin
require 'html2rss/configs'
rescue LoadError
nil
end
module Html2rss
module Web
##
# Loads and normalizes feed configuration from disk.
#
# Keeping lookup/defaulting here gives the rest of the app one predictable
# config shape instead of repeating file parsing and fallback logic.
module LocalConfig
##
# raised when the local config wasn't found
class NotFound < RuntimeError; end
##
# raised when the local config shape is invalid
class InvalidConfig < RuntimeError; end
FEED_EXTENSION_PATTERN = /\.(json|rss|xml)\z/
# Path to local feed configuration file.
CONFIG_FILE = 'config/feeds.yml'
class << self
##
# @param name [String, Symbol, #to_sym]
# @return [Hash<Symbol, Any>]
def find(name)
normalized_name = normalize_name(name)
config_hash = local_feed_config(normalized_name) || embedded_feed_config(normalized_name)
raise NotFound, "Did not find local feed config at '#{normalized_name}'" unless config_hash
apply_global_defaults(config_hash)
end
##
# @return [Hash<Symbol, Any>]
def feeds
snapshot.feeds.transform_values { |feed| deep_dup(feed.raw) }
end
##
# @return [Hash<Symbol, Any>]
def global
deep_dup(snapshot.global)
end
##
# @return [Hash<Symbol, Any>]
def yaml
YAML.safe_load_file(CONFIG_FILE, symbolize_names: true).freeze
rescue Errno::ENOENT => error
raise NotFound, "Configuration file not found: #{error.message}"
end
##
# @return [Html2rss::Web::ConfigSnapshot::Snapshot]
def snapshot
return @snapshot if @snapshot # rubocop:disable ThreadSafety/ClassInstanceVariable
@snapshot = ConfigSnapshot.load(yaml) # rubocop:disable ThreadSafety/ClassInstanceVariable
rescue KeyError, TypeError, ArgumentError => error
raise InvalidConfig, "Invalid local config: #{error.message}"
end
##
# @param reason [String]
# @return [nil]
def reload!(reason: 'manual')
@snapshot = nil # rubocop:disable ThreadSafety/ClassInstanceVariable
SecurityLogger.log_cache_lifecycle('local_config', 'reload', reason: reason)
nil
end
private
# @param normalized_name [String]
# @return [Hash{Symbol=>Object}, nil]
def local_feed_config(normalized_name)
config = snapshot.feeds[normalized_name.to_sym]
return nil unless config
deep_dup(config.raw)
end
# @param normalized_name [String]
# @return [Hash{Symbol=>Object}, nil]
def embedded_feed_config(normalized_name)
return nil unless defined?(Html2rss::Configs)
return nil unless normalized_name.include?('/')
deep_dup(Html2rss::Configs.find_by_name(normalized_name))
rescue Html2rss::Configs::ConfigNotFound
nil
rescue RuntimeError => error
return nil if error.message == 'name must be in folder/file format'
raise
end
# Applies global defaults only when feed-level keys are absent.
#
# @param config [Hash{Symbol=>Object}]
# @return [Hash{Symbol=>Object}]
def apply_global_defaults(config)
global_config = global
config[:stylesheets] ||= deep_dup(global_config[:stylesheets]) if global_config[:stylesheets]
config[:headers] ||= deep_dup(global_config[:headers]) if global_config[:headers]
config
end
# @param name [String, Symbol, #to_s]
# @return [String] path without feed extension for feed lookup.
def normalize_name(name)
name.to_s.delete_prefix('/').sub(FEED_EXTENSION_PATTERN, '')
end
# Deep-duplicates nested config structures to avoid mutating shared data.
#
# @param value [Object]
# @return [Object]
def deep_dup(value)
case value
when Hash
value.transform_values { |val| deep_dup(val) }
when Array
value.map { |element| deep_dup(element) }
else
value
end
end
end
end
end
end