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

59 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-22 20:12 +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 interfaces as _interfaces 

30from . import prompt as _prompt 

31 

32 

33_scribe = __.produce_scribe( __name__ ) 

34 

35 

36class VersionCommand( 

37 _interfaces.CliCommand, 

38 decorators = ( __.standard_dataclass, __.standard_tyro_class ), 

39): 

40 ''' Prints version information. ''' 

41 

42 async def __call__( self, auxdata: __.Globals ) -> None: 

43 ''' Executes command to print version information. ''' 

44 from . import __version__ # pylint: disable=cyclic-import 

45 print( f"{__package__} {__version__}" ) 

46 raise SystemExit( 0 ) 

47 

48 def provide_configuration_edits( self ) -> __.DictionaryEdits: 

49 ''' Provides edits against configuration from options. ''' 

50 return ( ) 

51 

52 

53class Cli( 

54 metaclass = __.ImmutableStandardDataclass, 

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

56): 

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

58 

59 application: __.ApplicationInformation 

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

61 # display: ConsoleDisplay 

62 inscription: __.InscriptionControl = ( 

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

64 command: __.typx.Union[ 

65 __.typx.Annotated[ 

66 _create.Command, 

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

68 'create', prefix_name = False ), 

69 ], 

70 __.typx.Annotated[ 

71 _apply.Command, 

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

73 'apply', prefix_name = False ), 

74 ], 

75 __.typx.Annotated[ 

76 _prompt.Command, 

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

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

79 ], 

80 __.typx.Annotated[ 

81 VersionCommand, 

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

83 'version', prefix_name = False ), 

84 ], 

85 ] 

86 

87 async def __call__( self ): 

88 ''' Invokes command after library preparation. ''' 

89 nomargs = self.prepare_invocation_args( ) 

90 async with __.ExitsAsync( ) as exits: 

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

92 await self.command( auxdata = auxdata ) 

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

94 

95 def prepare_invocation_args( 

96 self, 

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

98 ''' Prepares arguments for initial configuration. ''' 

99 configedits: __.DictionaryEdits = ( 

100 self.command.provide_configuration_edits( ) ) 

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

102 application = self.application, 

103 configedits = configedits, 

104 environment = True, 

105 inscription = self.inscription, 

106 ) 

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

108 return args 

109 

110 

111def execute( ): 

112 ''' Entrypoint for CLI execution. ''' 

113 from asyncio import run 

114 config = ( 

115 __.tyro.conf.EnumChoicesFromValues, 

116 __.tyro.conf.HelptextFromCommentsOff, 

117 ) 

118 # default = Cli( 

119 # application = _application.Information( ), 

120 # display = ConsoleDisplay( ), 

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

122 # command = InspectCommand( ), 

123 # ) 

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

125 except SystemExit: raise 

126 except BaseException: 

127 _scribe.exception( 

128 "Program terminated from uncaught exception. " 

129 "Please file a bug report." ) 

130 raise SystemExit( 1 ) from None 

131 

132 

133def _discover_inscription_level_name( 

134 application: __.ApplicationInformation, 

135 control: __.InscriptionControl, 

136) -> str: 

137 if control.level is None: 

138 from os import environ 

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

140 envvar_name = ( 

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

142 base = envvar_name_base, 

143 name = application.name.upper( ) ) ) 

144 if envvar_name not in environ: continue 

145 return environ[ envvar_name ] 

146 return 'INFO' 

147 return control.level 

148 

149 

150async def _prepare( 

151 application: __.ApplicationInformation, 

152 configedits: __.DictionaryEdits, 

153 environment: bool, 

154 exits: __.ExitsAsync, 

155 inscription: __.InscriptionControl, 

156) -> __.Globals: 

157 ''' Configures logging based on verbosity. ''' 

158 auxdata = await __.prepare( 

159 application = application, 

160 configedits = configedits, 

161 environment = environment, 

162 exits = exits, 

163 inscription = inscription ) 

164 _prepare_scribes( application, inscription ) 

165 return auxdata 

166 

167 

168def _prepare_scribes( 

169 application: __.ApplicationInformation, 

170 inscription: __.InscriptionControl, 

171) -> None: 

172 import logging 

173 from rich.console import Console 

174 from rich.logging import RichHandler 

175 level_name = _discover_inscription_level_name( application, inscription ) 

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

177 handler = RichHandler( 

178 console = Console( stderr = True ), 

179 rich_tracebacks = True, 

180 show_time = False ) 

181 logging.basicConfig( 

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

183 level = level, 

184 handlers = [ handler ] ) 

185 logging.captureWarnings( True )