Coverage for src/features/gcil.py: 50.67%
75 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-08 13:49 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-08 13:49 +0000
1#!/usr/bin/env python3
3# Standard libraries
4from pathlib import Path
5from re import search
6from typing import List
8# Components
9from ..features.git import Git
10from ..features.precommit import PreCommit
11from ..prints.colors import Colors
12from ..system.commands import Commands
13from ..system.platform import Platform
15# Gcil class
16class Gcil:
18 # Constants
19 BADGE_IMAGE: str = 'https://img.shields.io/badge/gcil-enabled-brightgreen?logo=gitlab'
20 BADGE_URL: str = 'https://radiandevcore.gitlab.io/tools/gcil'
21 BADGE_TEXT: str = 'gcil'
22 BINARY: str = 'gcil'
23 EOL: str = '\n'
24 FILE: str = '.gitlab-ci.yml'
25 HOOK_DESCRIPTION: str = 'Automatically run GitLab CI job with gcil'
26 HOOK_ID: str = 'run-gcil-push'
27 HOOK_NAME: str = 'Run GitLab CI job with gcil'
28 HOOKS_LABEL: str = 'gcil'
29 HOOKS_URL: str = 'https://gitlab.com/RadianDevCore/tools/gcil'
30 HOOKS_VERSION_MINIMAL: str = '13.1.0'
31 MARKER_NODE: str = '.local:'
32 MARKER_VARIABLE: str = 'CI_LOCAL'
34 # Jobs
35 JOBS_WHITELIST: List[str] = [
36 r'codestyle[:]?.*',
37 r'lint[:]?.*',
38 r'typings[:]?.*',
39 ]
41 # Configure unused hooks, pylint: disable=too-many-branches,too-many-statements
42 @staticmethod
43 def configure_hooks(
44 configuration_file: str,
45 autoupdate: bool,
46 ) -> bool:
48 # Validate gcil support
49 if not Gcil.exists() or not Gcil.installed():
50 return False
52 # Validate pre-commit support
53 if not Path(configuration_file).exists():
54 return False # pragma: no cover
56 # Configure pre-commit unused hooks
57 print(' ')
58 print(f'{Colors.GREEN} ===['
59 f'{Colors.YELLOW} Configure sources: '
60 f'{Colors.YELLOW_LIGHT}{Gcil.HOOK_ID}'
61 f'{Colors.GREEN} ]==='
62 f'{Colors.RESET}')
63 print(' ')
65 # Detect supported jobs
66 jobs_list: List[str] = Gcil.jobs_list()
67 jobs_supported: List[str] = []
68 for job in jobs_list:
69 if any(search(fr'^{whitelist}$', job) for whitelist in Gcil.JOBS_WHITELIST):
70 jobs_supported += [job]
72 # Show supported jobs hooks
73 print(
74 f'detected supported jobs hooks in configuration: {", ".join(jobs_supported) or "-"}'
75 )
76 Platform.flush()
78 # Append hooks configuration
79 if jobs_supported:
80 with open(
81 configuration_file,
82 encoding='utf8',
83 mode='a',
84 ) as file:
86 # Inject repository header
87 file.write(
88 PreCommit.CONFIGURATION_EOL.join([
89 '',
90 f' # Repository: {Gcil.HOOKS_LABEL}',
91 f' - repo: {Gcil.HOOKS_URL}',
92 f' rev: {Gcil.HOOKS_VERSION_MINIMAL}',
93 ' hooks:',
94 '',
95 ]))
97 # Inject repository hooks
98 for job in jobs_supported:
99 file.write(
100 PreCommit.CONFIGURATION_EOL.join([
101 f' - id: {Gcil.HOOK_ID}',
102 f' name: {Gcil.HOOK_NAME} ({job})',
103 f' description: {Gcil.HOOK_DESCRIPTION} ({job})',
104 ' args:',
105 f" - '{job}'",
106 '',
107 ]))
109 # Autoupdate repository
110 if autoupdate and Git.is_repository():
111 print(f'updating {Gcil.HOOKS_LABEL} repository with pre-commit: ', end='')
112 Platform.flush()
113 PreCommit.configure()
114 PreCommit.autoupdate(Gcil.HOOKS_URL)
115 Platform.flush()
117 # Result
118 return True
120 # Configured
121 @staticmethod
122 def configured() -> bool:
124 # Ignore missing file
125 if not Gcil.exists():
126 return False
128 # Detect gcil configurations
129 return Commands.grep(
130 Path(Gcil.FILE),
131 Gcil.MARKER_NODE,
132 ) or Commands.grep(
133 Path(Gcil.FILE),
134 Gcil.MARKER_VARIABLE,
135 )
137 # Jobs list
138 @staticmethod
139 def jobs_list() -> List[str]:
141 # Validate gcil support
142 if not Gcil.exists() or not Gcil.installed():
143 return []
145 # Dump jobs configuration
146 gcil_dump: str = Commands.output(Gcil.BINARY, [
147 '--dump',
148 ])
149 if not gcil_dump or gcil_dump == '{}':
150 return []
152 # Extract job names
153 jobs: List[str] = []
154 for line in [
155 line for line in gcil_dump.splitlines()
156 if line.strip() and not line.startswith(' ')
157 ]:
158 job: str = line.strip().rstrip(':')
159 if not job.startswith('.'):
160 jobs += [job]
162 # Result
163 return jobs
165 # Exists
166 @staticmethod
167 def exists() -> bool:
169 # Check if configuration exists
170 return Path(Gcil.FILE).exists()
172 # Installed
173 @staticmethod
174 def installed() -> bool:
176 # Check installed gcil
177 return Commands.exists(Gcil.BINARY)