commit_wizard/engine/config/
env.rs1use std::env;
9
10use crate::engine::{
11 config::base::{
12 BaseConfig, BranchConfig, BranchNamingConfig, CheckCommitsConfig, CheckConfig,
13 CommitConfig, CommitScopesConfig, PrBranchConfig, PrConfig, PrTitleConfig, PushAllowConfig,
14 PushCheckConfig, PushConfig, TicketConfig, VersioningConfig,
15 },
16 constants::env::*,
17 models::policy::enforcement::{CommitEnforcementScope, ScopeMode, TicketSource},
18};
19
20macro_rules! some_if_any {
31 ([$($field:expr),+ $(,)?] => $value:expr) => {
32 if $($field.is_some())||+ {
33 Some($value)
34 } else {
35 None
36 }
37 };
38}
39
40fn get_string(key: &str) -> Option<String> {
41 env::var(key).ok().filter(|s| !s.is_empty())
42}
43
44fn get_bool(key: &str) -> Option<bool> {
45 get_string(key).and_then(|val| parse_bool_val(&val, key))
46}
47
48fn parse_bool_val(val: &str, key: &str) -> Option<bool> {
49 match val.to_lowercase().as_str() {
50 "true" | "1" | "yes" | "on" => Some(true),
51 "false" | "0" | "no" | "off" => Some(false),
52 _ => {
53 eprintln!(
54 "[warn] CW: invalid boolean for {key}: {val:?} — expected true/false/1/0/yes/no/on/off"
55 );
56 None
57 }
58 }
59}
60
61fn get_u32(key: &str) -> Option<u32> {
62 get_string(key).and_then(|val| {
63 val.parse::<u32>()
64 .map_err(|_| {
65 eprintln!("[warn] CW: invalid integer for {key}: {val:?}");
66 })
67 .ok()
68 })
69}
70
71fn get_list(key: &str) -> Option<Vec<String>> {
72 get_string(key).map(|val| {
73 val.split(',')
74 .map(|s| s.trim().to_string())
75 .filter(|s| !s.is_empty())
76 .collect()
77 })
78}
79
80fn parse_scope_mode(val: &str, key: &str) -> Option<ScopeMode> {
85 match val.to_lowercase().as_str() {
86 "disabled" => Some(ScopeMode::Disabled),
87 "optional" => Some(ScopeMode::Optional),
88 "required" => Some(ScopeMode::Required),
89 _ => {
90 eprintln!(
91 "[warn] CW: invalid ScopeMode for {key}: {val:?} — expected disabled/optional/required"
92 );
93 None
94 }
95 }
96}
97
98fn parse_ticket_source(val: &str, key: &str) -> Option<TicketSource> {
99 match val.to_lowercase().as_str() {
100 "branch" => Some(TicketSource::Branch),
101 "prompt" => Some(TicketSource::Prompt),
102 "branch_or_prompt" => Some(TicketSource::BranchOrPrompt),
103 "disabled" => Some(TicketSource::Disabled),
104 _ => {
105 eprintln!(
106 "[warn] CW: invalid TicketSource for {key}: {val:?} — expected branch/prompt/branch_or_prompt/disabled"
107 );
108 None
109 }
110 }
111}
112
113fn parse_enforcement_scope(val: &str, key: &str) -> Option<CommitEnforcementScope> {
114 match val.to_lowercase().as_str() {
115 "all_branches" | "all" => Some(CommitEnforcementScope::AllBranches),
116 "protected_branches" | "protected" => Some(CommitEnforcementScope::ProtectedBranches),
117 "none" => Some(CommitEnforcementScope::None),
118 _ => {
119 eprintln!(
120 "[warn] CW: invalid CommitEnforcementScope for {key}: {val:?} — expected all_branches/protected_branches/none"
121 );
122 None
123 }
124 }
125}
126
127pub fn build_env_config() -> Option<BaseConfig> {
136 if let Some(val) = get_string(ENV_ALLOW_ENV_OVERRIDE)
138 && !parse_bool_val(&val, ENV_ALLOW_ENV_OVERRIDE).unwrap_or(true)
139 {
140 return None;
141 }
142
143 let commit = build_commit();
144 let branch = build_branch();
145 let pr = build_pr();
146 let check = build_check();
147 let push = build_push();
148 let versioning = build_versioning();
149
150 if commit.is_none()
151 && branch.is_none()
152 && pr.is_none()
153 && check.is_none()
154 && push.is_none()
155 && versioning.is_none()
156 {
157 return None;
158 }
159
160 Some(BaseConfig {
161 commit,
162 branch,
163 pr,
164 check,
165 push,
166 versioning,
167 changelog: None,
168 release: None,
169 ai: None,
170 hooks: None,
171 registry: None,
172 registries: None,
173 })
174}
175
176fn build_commit() -> Option<CommitConfig> {
181 let subject_max_length = get_u32(ENV_COMMIT_SUBJECT_MAX_LENGTH);
182 let scopes = {
183 let mode = get_string(ENV_COMMIT_SCOPES_MODE)
184 .and_then(|v| parse_scope_mode(&v, ENV_COMMIT_SCOPES_MODE));
185 let restrict_to_defined = get_bool(ENV_COMMIT_SCOPES_RESTRICT_TO_DEFINED);
186 some_if_any!([mode, restrict_to_defined] =>
187 CommitScopesConfig { mode, restrict_to_defined, definitions: None })
188 };
189 let ticket = {
190 let required = get_bool(ENV_COMMIT_TICKET_REQUIRED);
191 let pattern = get_string(ENV_COMMIT_TICKET_PATTERN);
192 let source = get_string(ENV_COMMIT_TICKET_SOURCE)
193 .and_then(|v| parse_ticket_source(&v, ENV_COMMIT_TICKET_SOURCE));
194 some_if_any!([required, pattern, source] =>
195 TicketConfig { required, pattern, source, header_format: None })
196 };
197 some_if_any!([subject_max_length, scopes, ticket] =>
198 CommitConfig {
199 subject_max_length,
200 use_emojis: None,
201 types: None,
202 scopes,
203 breaking: None,
204 protected: None,
205 ticket,
206 })
207}
208
209fn build_branch() -> Option<BranchConfig> {
210 let remote = get_string(ENV_BRANCH_REMOTE);
211 let protected = get_list(ENV_BRANCH_PROTECTED);
212 let naming = get_string(ENV_BRANCH_NAMING_PATTERN).map(|pattern| BranchNamingConfig {
213 pattern: Some(pattern),
214 });
215 some_if_any!([remote, protected, naming] => BranchConfig { remote, protected, naming })
216}
217
218fn build_pr() -> Option<PrConfig> {
219 let title = {
220 let require_conventional = get_bool(ENV_PR_TITLE_REQUIRE_CONVENTIONAL);
221 let require_ticket = get_bool(ENV_PR_TITLE_REQUIRE_TICKET);
222 let scope_mode = get_string(ENV_PR_TITLE_SCOPE_MODE)
223 .and_then(|v| parse_scope_mode(&v, ENV_PR_TITLE_SCOPE_MODE));
224 some_if_any!([require_conventional, require_ticket, scope_mode] =>
225 PrTitleConfig { require_conventional, require_ticket, scope_mode })
226 };
227 let branch = {
228 let source_pattern = get_string(ENV_PR_BRANCH_SOURCE_PATTERN);
229 let target_allowed = get_list(ENV_PR_BRANCH_TARGET_ALLOWED);
230 some_if_any!([source_pattern, target_allowed] =>
231 PrBranchConfig {
232 check_source: None,
233 check_target: None,
234 source_pattern,
235 target_allowed,
236 })
237 };
238 some_if_any!([title, branch] => PrConfig { enabled: None, title, branch })
239}
240
241fn build_check() -> Option<CheckConfig> {
242 let require_conventional = get_bool(ENV_CHECK_REQUIRE_CONVENTIONAL);
243 let commits = {
244 let enabled = get_bool(ENV_CHECK_COMMITS_ENABLED);
245 let enforce_on = get_string(ENV_CHECK_COMMITS_ENFORCE_ON)
246 .and_then(|v| parse_enforcement_scope(&v, ENV_CHECK_COMMITS_ENFORCE_ON));
247 some_if_any!([enabled, enforce_on] => CheckCommitsConfig { enabled, enforce_on })
248 };
249 some_if_any!([require_conventional, commits] => CheckConfig { require_conventional, commits })
250}
251
252fn build_push() -> Option<PushConfig> {
253 let allow = {
254 let protected = get_bool(ENV_PUSH_ALLOW_PROTECTED);
255 let force = get_bool(ENV_PUSH_ALLOW_FORCE);
256 some_if_any!([protected, force] => PushAllowConfig { protected, force })
257 };
258 let check = {
259 let commits = get_bool(ENV_PUSH_CHECK_COMMITS);
260 let branch_policy = get_bool(ENV_PUSH_CHECK_BRANCH_POLICY);
261 some_if_any!([commits, branch_policy] => PushCheckConfig { commits, branch_policy })
262 };
263 some_if_any!([allow, check] => PushConfig { allow, check })
264}
265
266fn build_versioning() -> Option<VersioningConfig> {
267 get_string(ENV_VERSIONING_TAG_PREFIX).map(|tag_prefix| VersioningConfig {
268 tag_prefix: Some(tag_prefix),
269 })
270}
271
272pub struct EnvRegistryParams {
278 pub url: Option<String>,
279 pub r#ref: Option<String>,
280 pub section: Option<String>,
281}
282
283pub fn get_env_registry_params() -> EnvRegistryParams {
284 EnvRegistryParams {
285 url: get_string(ENV_REGISTRY_URL),
286 r#ref: get_string(ENV_REGISTRY_REF),
287 section: get_string(ENV_REGISTRY_SECTION),
288 }
289}