Class: Prdigest::Config
- Inherits:
-
Object
- Object
- Prdigest::Config
- Defined in:
- lib/prdigest/config.rb
Constant Summary collapse
- REPOSITORY_SEGMENT =
/\A[A-Za-z0-9_.-]+\z/- ENVIRONMENT_VARIABLE =
/\A[A-Za-z_][A-Za-z0-9_]*\z/- IPV4_LOOPBACK =
IPAddr.new("127.0.0.0/8").freeze
- IPV6_LOOPBACK =
IPAddr.new("::1").freeze
- PROSE_BASE_URL_ERROR =
"prose.base_url must be an absolute HTTPS URL (or HTTP loopback URL) " \ "without credentials, query, or fragment"
Instance Attribute Summary collapse
-
#path ⇒ Object
readonly
Returns the value of attribute path.
-
#raw ⇒ Object
readonly
Returns the value of attribute raw.
Class Method Summary collapse
- .load(path, capability: :run) ⇒ Object
- .normalize_repos(values, label: "repository") ⇒ Object
- .parse_prose_base_uri(value) ⇒ Object
- .resolve_path(explicit: nil, env: ENV, system_path: "/etc/prdigest/config.yml") ⇒ Object
Instance Method Summary collapse
- #chat_id ⇒ Object
- #chat_id_allowlist ⇒ Object
- #delivery_state_path ⇒ Object
- #empty_message ⇒ Object
- #github_token(env = ENV) ⇒ Object
-
#initialize(raw, path: nil) ⇒ Config
constructor
A new instance of Config.
- #line_stats? ⇒ Boolean
- #max_catchup_days ⇒ Object
- #prose_api_key(env = ENV) ⇒ Object
- #prose_api_key_env ⇒ Object
- #prose_base_url ⇒ Object
- #prose_model ⇒ Object
- #prose_provider ⇒ Object
- #repos ⇒ Object
- #schedule_cron ⇒ Object
- #send_empty? ⇒ Boolean
- #state_path ⇒ Object
- #telegram_token(env = ENV) ⇒ Object
- #timezone ⇒ Object
- #validate!(capability: :run) ⇒ Object
Constructor Details
#initialize(raw, path: nil) ⇒ Config
Returns a new instance of Config.
87 88 89 90 |
# File 'lib/prdigest/config.rb', line 87 def initialize(raw, path: nil) @raw = raw @path = path end |
Instance Attribute Details
#path ⇒ Object (readonly)
Returns the value of attribute path.
11 12 13 |
# File 'lib/prdigest/config.rb', line 11 def path @path end |
#raw ⇒ Object (readonly)
Returns the value of attribute raw.
11 12 13 |
# File 'lib/prdigest/config.rb', line 11 def raw @raw end |
Class Method Details
.load(path, capability: :run) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/prdigest/config.rb', line 31 def self.load(path, capability: :run) path = File.(path) raise ConfigError, "config not found: #{path}" unless File.file?(path) raw = YAML.safe_load_file(path, aliases: true) raise ConfigError, "config root must be a mapping" unless raw.is_a?(Hash) new(raw, path: path).tap { |config| config.validate!(capability: capability) } rescue ConfigError raise rescue StandardError => e raise ConfigError, "cannot load config: #{e.class}" end |
.normalize_repos(values, label: "repository") ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/prdigest/config.rb', line 45 def self.normalize_repos(values, label: "repository") normalized = Array(values).map { |value| value.to_s.strip } raise ConfigError, "#{label} must list at least one owner/name" if normalized.empty? invalid = normalized.find do |repository| parts = repository.split("/", -1) parts.length != 2 || parts.any? { |part| part.empty? || %w[. ..].include?(part) || !part.match?(REPOSITORY_SEGMENT) } end raise ConfigError, "#{label} entry must be owner/name; got #{invalid.inspect}" if invalid normalized.each_with_object({}) do |repository, unique| unique[repository.downcase] ||= repository end.values.freeze end |
.parse_prose_base_uri(value) ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/prdigest/config.rb', line 61 def self.parse_prose_base_uri(value) uri = URI.parse(value.to_s.strip) valid = %w[http https].include?(uri.scheme) && !uri.host.to_s.empty? && uri.userinfo.nil? && uri.query.nil? && uri.fragment.nil? && (uri.scheme == "https" || loopback_host?(uri.host)) raise ConfigError, PROSE_BASE_URL_ERROR unless valid uri rescue URI::InvalidURIError raise ConfigError, PROSE_BASE_URL_ERROR end |
.resolve_path(explicit: nil, env: ENV, system_path: "/etc/prdigest/config.yml") ⇒ Object
21 22 23 24 25 26 27 28 29 |
# File 'lib/prdigest/config.rb', line 21 def self.resolve_path(explicit: nil, env: ENV, system_path: "/etc/prdigest/config.yml") return File.(explicit) if explicit && !explicit.to_s.empty? from_env = env["PRDIGEST_CONFIG"].to_s return File.(from_env) unless from_env.empty? return system_path if File.file?(system_path) raise ConfigError, "config path is required (--config, PRDIGEST_CONFIG, or /etc/prdigest/config.yml)" end |
Instance Method Details
#chat_id ⇒ Object
130 131 132 133 134 |
# File 'lib/prdigest/config.rb', line 130 def chat_id Integer(raw.dig("telegram", "chat_id")) rescue TypeError, ArgumentError nil end |
#chat_id_allowlist ⇒ Object
136 137 138 139 140 |
# File 'lib/prdigest/config.rb', line 136 def chat_id_allowlist Array(raw.dig("telegram", "chat_id_allowlist")).map { |id| Integer(id) } rescue TypeError, ArgumentError [] end |
#delivery_state_path ⇒ Object
158 159 160 |
# File 'lib/prdigest/config.rb', line 158 def delivery_state_path raw.dig("state", "delivery_path") || File.join(File.dirname(state_path), "deliveries") end |
#empty_message ⇒ Object
150 151 152 |
# File 'lib/prdigest/config.rb', line 150 def raw.dig("digest", "empty_message") || "Merged PR digest — {date}\nTotal: 0 PRs" end |
#github_token(env = ENV) ⇒ Object
100 101 102 103 |
# File 'lib/prdigest/config.rb', line 100 def github_token(env = ENV) env_name = raw.dig("github", "token_env") || "GITHUB_TOKEN" env[env_name.to_s].to_s end |
#line_stats? ⇒ Boolean
142 143 144 |
# File 'lib/prdigest/config.rb', line 142 def line_stats? raw.dig("digest", "line_stats") != false end |
#max_catchup_days ⇒ Object
166 167 168 169 170 |
# File 'lib/prdigest/config.rb', line 166 def max_catchup_days Integer(raw.dig("schedule", "max_catchup_days") || 7) rescue TypeError, ArgumentError raise ConfigError, "schedule.max_catchup_days must be an integer from 1 to 30" end |
#prose_api_key(env = ENV) ⇒ Object
126 127 128 |
# File 'lib/prdigest/config.rb', line 126 def prose_api_key(env = ENV) env[prose_api_key_env].to_s end |
#prose_api_key_env ⇒ Object
122 123 124 |
# File 'lib/prdigest/config.rb', line 122 def prose_api_key_env prose_config["api_key_env"].to_s.strip end |
#prose_base_url ⇒ Object
114 115 116 |
# File 'lib/prdigest/config.rb', line 114 def prose_base_url prose_config["base_url"].to_s.strip end |
#prose_model ⇒ Object
118 119 120 |
# File 'lib/prdigest/config.rb', line 118 def prose_model prose_config["model"].to_s.strip end |
#prose_provider ⇒ Object
110 111 112 |
# File 'lib/prdigest/config.rb', line 110 def prose_provider prose_config["provider"].to_s end |
#repos ⇒ Object
96 97 98 |
# File 'lib/prdigest/config.rb', line 96 def repos self.class.normalize_repos(raw.dig("github", "repos"), label: "github.repos") end |
#schedule_cron ⇒ Object
162 163 164 |
# File 'lib/prdigest/config.rb', line 162 def schedule_cron raw.dig("schedule", "cron") || "5 9 * * *" end |
#send_empty? ⇒ Boolean
146 147 148 |
# File 'lib/prdigest/config.rb', line 146 def send_empty? raw.dig("digest", "send_empty") != false end |
#state_path ⇒ Object
154 155 156 |
# File 'lib/prdigest/config.rb', line 154 def state_path raw.dig("state", "path") || File.("~/.local/share/prdigest/state.json") end |
#telegram_token(env = ENV) ⇒ Object
105 106 107 108 |
# File 'lib/prdigest/config.rb', line 105 def telegram_token(env = ENV) env_name = raw.dig("telegram", "token_env") || "TELEGRAM_BOT_TOKEN" env[env_name.to_s].to_s end |
#timezone ⇒ Object
92 93 94 |
# File 'lib/prdigest/config.rb', line 92 def timezone raw.fetch("timezone", "UTC").to_s end |
#validate!(capability: :run) ⇒ Object
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/prdigest/config.rb', line 172 def validate!(capability: :run) capability = capability.to_sym unless %i[facts run prose prose_delivery].include?(capability) raise ArgumentError, "unknown configuration capability: #{capability}" end begin TZInfo::Timezone.get(timezone) rescue TZInfo::InvalidTimezoneIdentifier raise ConfigError, "timezone must be a resolvable IANA identifier" end unless (1..30).cover?(max_catchup_days) raise ConfigError, "schedule.max_catchup_days must be from 1 to 30" end repos return self if capability == :facts validate_prose! if %i[prose prose_delivery].include?(capability) return self if capability == :prose validate_telegram! self end |