Coverage for src/cli/main.py: 100.00%

86 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-22 10:53 +0000

1#!/usr/bin/env python3 

2 

3# Standard libraries 

4from argparse import ( 

5 _ArgumentGroup, 

6 ArgumentParser, 

7 Namespace, 

8 RawTextHelpFormatter, 

9) 

10from os import environ 

11from shutil import get_terminal_size 

12from sys import exit as sys_exit 

13 

14# Components 

15from ..features.actions import ActionsFeature 

16from ..lib.executor import Executor 

17from ..package.bundle import Bundle 

18from ..package.settings import Settings 

19from ..package.updates import Updates 

20from ..package.version import Version 

21from ..prints.colors import Colors 

22from ..system.platform import Platform 

23from .entrypoint import Entrypoint 

24 

25# Constants 

26HELP_POSITION: int = 23 

27 

28# Main, pylint: disable=too-many-branches,too-many-statements 

29def main() -> None: 

30 

31 # Variables 

32 group: _ArgumentGroup 

33 result: Entrypoint.Result 

34 

35 # Arguments creation 

36 parser: ArgumentParser = ArgumentParser( 

37 prog=Bundle.NAME, 

38 description=f'{Bundle.NAME}: {Bundle.DESCRIPTION}', 

39 add_help=False, 

40 formatter_class=lambda prog: RawTextHelpFormatter( 

41 prog, 

42 max_help_position=HELP_POSITION, 

43 width=min( 

44 120, 

45 get_terminal_size().columns - 2, 

46 ), 

47 ), 

48 ) 

49 

50 # Arguments internal definitions 

51 group = parser.add_argument_group('internal arguments') 

52 group.add_argument( 

53 '-h', 

54 '--help', 

55 dest='help', 

56 action='store_true', 

57 help='Show this help message', 

58 ) 

59 group.add_argument( 

60 '--version', 

61 dest='version', 

62 action='store_true', 

63 help='Show the current version', 

64 ) 

65 group.add_argument( 

66 '--no-color', 

67 dest='no_color', 

68 action='store_true', 

69 help=f'Disable colors outputs with \'{Bundle.ENV_NO_COLOR}=1\'\n' 

70 '(or default settings: [themes] > no_color)', 

71 ) 

72 group.add_argument( 

73 '--update-check', 

74 dest='update_check', 

75 action='store_true', 

76 help='Check for newer package updates', 

77 ) 

78 group.add_argument( 

79 '--settings', 

80 dest='settings', 

81 action='store_true', 

82 help='Show the current settings path and contents', 

83 ) 

84 group.add_argument( 

85 '--set', 

86 dest='set', 

87 action='store', 

88 metavar=('GROUP', 'KEY', 'VAL'), 

89 nargs=3, 

90 help='Set settings specific \'VAL\' value to [GROUP] > KEY\n' \ 

91 'or unset by using \'UNSET\' as \'VAL\'', 

92 ) 

93 

94 # Arguments configuration definitions 

95 group = parser.add_argument_group('configuration arguments') 

96 group.add_argument( 

97 '--delay-init', 

98 dest='delay_init', 

99 type=float, 

100 default=Executor.DELAY_INIT, 

101 help='Delay the initial action execution (in s, default: %(default)s)', 

102 metavar='SECS', 

103 ) 

104 group.add_argument( 

105 '--delay-press', 

106 dest='delay_press', 

107 type=float, 

108 default=Executor.DELAY_PRESS, 

109 help='Delay the press actions execution (in s, default: %(default)s)', 

110 metavar='SECS', 

111 ) 

112 group.add_argument( 

113 '--delay-prompt', 

114 dest='delay_prompt', 

115 type=float, 

116 default=Executor.DELAY_PROMPT, 

117 help='Delay the prompt actions execution (in s, default: %(default)s)', 

118 metavar='SECS', 

119 ) 

120 group.add_argument( 

121 '--hold-prompt', 

122 dest='hold_prompt', 

123 action='store_true', 

124 help='Hold the prompt execution without a new line', 

125 ) 

126 group.add_argument( 

127 '--host', 

128 dest='host', 

129 action='store', 

130 default=Executor.LABEL_HOST, 

131 help='Configure the host name (default: %(default)s' + ', env: ' + 

132 Bundle.ENV_HOST + ')', 

133 ) 

134 group.add_argument( 

135 '--tool', 

136 dest='tool', 

137 action='store', 

138 default=Executor.LABEL_TOOL, 

139 help='Configure the tool name (default: %(default)s' + ', env: ' + 

140 Bundle.ENV_TOOL + ')', 

141 ) 

142 group.add_argument( 

143 '--mask', 

144 dest='masks', 

145 action='append', 

146 help='Mask specific strings from console outputs (credentials for example)', 

147 metavar='STRINGS', 

148 ) 

149 group.add_argument( 

150 '--workdir', 

151 dest='workdir', 

152 action='store', 

153 help='Use a specific working directory path', 

154 ) 

155 

156 # Arguments actions definitions 

157 group = parser.add_argument_group('actions arguments') 

158 group.add_argument( 

159 '--up', 

160 dest='actions', 

161 action='append_const', 

162 const=ActionsFeature.ActionKey(Executor.KEY_UP), 

163 help='Press the <UP> key', 

164 ) 

165 group.add_argument( 

166 '--down', 

167 dest='actions', 

168 action='append_const', 

169 const=ActionsFeature.ActionKey(Executor.KEY_DOWN), 

170 help='Press the <DOWN> key', 

171 ) 

172 group.add_argument( 

173 '--left', 

174 dest='actions', 

175 action='append_const', 

176 const=ActionsFeature.ActionKey(Executor.KEY_LEFT), 

177 help='Press the <LEFT> key', 

178 ) 

179 group.add_argument( 

180 '--right', 

181 dest='actions', 

182 action='append_const', 

183 const=ActionsFeature.ActionKey(Executor.KEY_RIGHT), 

184 help='Press the <RIGHT> key', 

185 ) 

186 group.add_argument( 

187 '--enter', 

188 dest='actions', 

189 action='append_const', 

190 const=ActionsFeature.ActionKey(Executor.KEY_ENTER), 

191 help='Press the <ENTER> key', 

192 ) 

193 group.add_argument( 

194 '--space', 

195 dest='actions', 

196 action='append_const', 

197 const=ActionsFeature.ActionKey(Executor.KEY_SPACE), 

198 help='Press the <SPACE> key', 

199 ) 

200 group.add_argument( 

201 '--press', 

202 dest='actions', 

203 action='append', 

204 type=ActionsFeature.ActionKey, 

205 help='Press the specified <KEY>', 

206 metavar='KEY', 

207 ) 

208 group.add_argument( 

209 '--ctrl', 

210 dest='actions', 

211 action='append', 

212 type=ActionsFeature.ActionKeyControl, 

213 help='Press the specified Ctrl+<KEY>', 

214 metavar='KEY', 

215 ) 

216 group.add_argument( 

217 '--read', 

218 dest='actions', 

219 action='append_const', 

220 const=ActionsFeature.ActionRead(), 

221 help='Read the buffered data output (forced)', 

222 ) 

223 group.add_argument( 

224 '--wait', 

225 dest='actions', 

226 action='append', 

227 type=ActionsFeature.ActionWait, 

228 help='Wait the specified time (in s, example: 1.0)', 

229 metavar='SECS', 

230 ) 

231 group.add_argument( 

232 '--finish', 

233 dest='actions', 

234 action='append_const', 

235 const=ActionsFeature.ActionFinish(), 

236 help='Finish the execution (forced)', 

237 ) 

238 

239 # Arguments positional definitions 

240 group = parser.add_argument_group('positional arguments') 

241 group.add_argument( 

242 '--', 

243 dest='double_dash', 

244 action='store_true', 

245 help='Positional arguments separator (recommended)', 

246 ) 

247 group.add_argument( 

248 dest='command', 

249 nargs='*', 

250 help='Command arguments to execute (use -- to separate)', 

251 ) 

252 

253 # Arguments parser 

254 options: Namespace = parser.parse_args() 

255 

256 # Help informations 

257 if options.help: 

258 print(' ') 

259 parser.print_help() 

260 print(' ') 

261 Platform.flush() 

262 sys_exit(0) 

263 

264 # Instantiate settings 

265 settings: Settings = Settings(name=Bundle.NAME) 

266 

267 # Prepare no_color 

268 if not options.no_color: 

269 if settings.has('themes', 'no_color'): 

270 options.no_color = settings.get_bool('themes', 'no_color') 

271 else: 

272 options.no_color = False 

273 settings.set_bool('themes', 'no_color', options.no_color) 

274 

275 # Configure no_color 

276 if options.no_color: 

277 environ[Bundle.ENV_FORCE_COLOR] = '0' 

278 environ[Bundle.ENV_NO_COLOR] = '1' 

279 

280 # Prepare colors 

281 Colors.prepare() 

282 

283 # Settings setter 

284 if options.set: 

285 settings.set(options.set[0], options.set[1], options.set[2]) 

286 settings.show() 

287 sys_exit(0) 

288 

289 # Settings informations 

290 if options.settings: 

291 settings.show() 

292 sys_exit(0) 

293 

294 # Instantiate updates 

295 updates: Updates = Updates( 

296 name=Bundle.PACKAGE, 

297 settings=settings, 

298 ) 

299 

300 # Version informations 

301 if options.version: 

302 print( 

303 f'{Bundle.NAME} {Version.get()} from {Version.path()} (python {Version.python()})' 

304 ) 

305 Platform.flush() 

306 sys_exit(0) 

307 

308 # Check for current updates 

309 if options.update_check: 

310 if not updates.check(): 

311 updates.check(older=True) 

312 sys_exit(0) 

313 

314 # CLI entrypoint 

315 result = Entrypoint.cli(options) 

316 

317 # Check for daily updates 

318 if updates.enabled and updates.daily: 

319 updates.check() 

320 

321 # Result 

322 if result in [ 

323 Entrypoint.Result.SUCCESS, 

324 Entrypoint.Result.FINALIZE, 

325 ]: 

326 sys_exit(0) 

327 else: 

328 sys_exit(1) 

329 

330# Entrypoint 

331if __name__ == '__main__': # pragma: no cover 

332 main()