Coverage for sources/mimeogram/cli.py: 30%

51 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-17 22:34 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Command-line interface. ''' 

22 

23 

24from __future__ import annotations 

25 

26from . import __ 

27from . import apply as _apply 

28from . import create as _create 

29from . import prompt as _prompt 

30 

31 

32_scribe = __.produce_scribe( __name__ ) 

33 

34 

35class Cli( 

36 metaclass = __.ImmutableStandardDataclass, 

37 decorators = ( __.standard_dataclass, __.simple_tyro_class ), 

38): 

39 ''' Mimeogram: hierarchical data exchange between humans and LLMs. ''' 

40 

41 application: __.ApplicationInformation 

42 configfile: __.typx.Optional[ str ] = None 

43 # display: ConsoleDisplay 

44 inscription: __.InscriptionControl = ( 

45 __.InscriptionControl( mode = __.InscriptionModes.Rich ) ) 

46 command: __.typx.Union[ 

47 __.typx.Annotated[ 

48 _create.Command, 

49 __.tyro.conf.subcommand( # pyright: ignore 

50 'create', prefix_name = False ), 

51 ], 

52 __.typx.Annotated[ 

53 _apply.Command, 

54 __.tyro.conf.subcommand( # pyright: ignore 

55 'apply', prefix_name = False ), 

56 ], 

57 __.typx.Annotated[ 

58 _prompt.Command, 

59 __.tyro.conf.subcommand( # pyright: ignore 

60 'provide-prompt', prefix_name = False ), 

61 ], 

62 ] 

63 

64 async def __call__( self ): 

65 ''' Invokes command after library preparation. ''' 

66 nomargs = self.prepare_invocation_args( ) 

67 async with __.ExitsAsync( ) as exits: 

68 auxdata = await _prepare( exits = exits, **nomargs ) 

69 await self.command( auxdata = auxdata ) 

70 # await self.command( auxdata = auxdata, display = self.display ) 

71 

72 def prepare_invocation_args( 

73 self, 

74 ) -> __.cabc.Mapping[ str, __.typx.Any ]: 

75 ''' Prepares arguments for initial configuration. ''' 

76 configedits: __.DictionaryEdits = ( 

77 self.command.provide_configuration_edits( ) ) 

78 args: dict[ str, __.typx.Any ] = dict( 

79 application = self.application, 

80 configedits = configedits, 

81 environment = True, 

82 inscription = self.inscription, 

83 ) 

84 if self.configfile: args[ 'configfile' ] = self.configfile 

85 return args 

86 

87 

88def execute( ): 

89 ''' Entrypoint for CLI execution. ''' 

90 from asyncio import run 

91 config = ( 

92 __.tyro.conf.EnumChoicesFromValues, 

93 __.tyro.conf.HelptextFromCommentsOff, 

94 ) 

95 # default = Cli( 

96 # application = _application.Information( ), 

97 # display = ConsoleDisplay( ), 

98 # inscription = _inscription.Control( mode = _inscription.Modes.Rich ), 

99 # command = InspectCommand( ), 

100 # ) 

101 try: run( __.tyro.cli( Cli, config = config )( ) ) 

102 except SystemExit: raise 

103 except BaseException: 

104 _scribe.exception( 

105 "Program terminated from uncaught exception. " 

106 "Please file a bug report." ) 

107 raise SystemExit( 1 ) from None 

108 

109 

110def _discover_inscription_level_name( 

111 application: __.ApplicationInformation, 

112 control: __.InscriptionControl, 

113) -> str: 

114 if control.level is None: 

115 from os import environ 

116 for envvar_name_base in ( 'INSCRIPTION', 'LOG' ): 

117 envvar_name = ( 

118 "{name}_{base}_LEVEL".format( 

119 base = envvar_name_base, 

120 name = application.name.upper( ) ) ) 

121 if envvar_name not in environ: continue 

122 return environ[ envvar_name ] 

123 return 'INFO' 

124 return control.level 

125 

126 

127async def _prepare( 

128 application: __.ApplicationInformation, 

129 configedits: __.DictionaryEdits, 

130 environment: bool, 

131 exits: __.ExitsAsync, 

132 inscription: __.InscriptionControl, 

133) -> __.Globals: 

134 ''' Configures logging based on verbosity. ''' 

135 auxdata = await __.prepare( 

136 application = application, 

137 configedits = configedits, 

138 environment = environment, 

139 exits = exits, 

140 inscription = inscription ) 

141 _prepare_scribes( application, inscription ) 

142 return auxdata 

143 

144 

145def _prepare_scribes( 

146 application: __.ApplicationInformation, 

147 inscription: __.InscriptionControl, 

148) -> None: 

149 import logging 

150 from rich.console import Console 

151 from rich.logging import RichHandler 

152 level_name = _discover_inscription_level_name( application, inscription ) 

153 level = getattr( logging, level_name.upper( ) ) 

154 handler = RichHandler( 

155 console = Console( stderr = True ), 

156 rich_tracebacks = True, 

157 show_time = False ) 

158 logging.basicConfig( 

159 format = '%(name)s: %(message)s', 

160 level = level, 

161 handlers = [ handler ] ) 

162 logging.captureWarnings( True )