Coverage for sources/mimeogram/cli.py: 31%
58 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-07 04:07 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-07 04:07 +0000
1# vim: set filetype=python fileencoding=utf-8:
2# -*- coding: utf-8 -*-
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#============================================================================#
21''' Command-line interface. '''
24from . import __
25from . import apply as _apply
26from . import create as _create
27from . import interfaces as _interfaces
28from . import prompt as _prompt
31_scribe = __.produce_scribe( __name__ )
34class VersionCommand(
35 _interfaces.CliCommand,
36 decorators = ( __.standard_dataclass, __.standard_tyro_class ),
37):
38 ''' Prints version information. '''
40 async def __call__( self, auxdata: __.Globals ) -> None:
41 ''' Executes command to print version information. '''
42 from . import __version__
43 print( f"{__package__} {__version__}" )
44 raise SystemExit( 0 )
46 def provide_configuration_edits( self ) -> __.DictionaryEdits:
47 ''' Provides edits against configuration from options. '''
48 return ( )
51class Cli(
52 metaclass = __.ImmutableStandardDataclass,
53 decorators = ( __.standard_dataclass, __.simple_tyro_class ),
54):
55 ''' Mimeogram: hierarchical data exchange between humans and LLMs. '''
57 application: __.ApplicationInformation
58 configfile: __.typx.Optional[ str ] = None
59 # display: ConsoleDisplay
60 inscription: __.InscriptionControl = (
61 __.InscriptionControl( mode = __.InscriptionModes.Rich ) )
62 command: __.typx.Union[
63 __.typx.Annotated[
64 _create.Command,
65 __.tyro.conf.subcommand(
66 'create', prefix_name = False ),
67 ],
68 __.typx.Annotated[
69 _apply.Command,
70 __.tyro.conf.subcommand(
71 'apply', prefix_name = False ),
72 ],
73 __.typx.Annotated[
74 _prompt.Command,
75 __.tyro.conf.subcommand(
76 'provide-prompt', prefix_name = False ),
77 ],
78 __.typx.Annotated[
79 VersionCommand,
80 __.tyro.conf.subcommand(
81 'version', prefix_name = False ),
82 ],
83 ]
85 async def __call__( self ):
86 ''' Invokes command after library preparation. '''
87 nomargs = self.prepare_invocation_args( )
88 async with __.ExitsAsync( ) as exits:
89 auxdata = await _prepare( exits = exits, **nomargs )
90 await self.command( auxdata = auxdata )
91 # await self.command( auxdata = auxdata, display = self.display )
93 def prepare_invocation_args(
94 self,
95 ) -> __.cabc.Mapping[ str, __.typx.Any ]:
96 ''' Prepares arguments for initial configuration. '''
97 configedits: __.DictionaryEdits = (
98 self.command.provide_configuration_edits( ) )
99 args: dict[ str, __.typx.Any ] = dict(
100 application = self.application,
101 configedits = configedits,
102 environment = True,
103 inscription = self.inscription,
104 )
105 if self.configfile: args[ 'configfile' ] = self.configfile
106 return args
109def execute( ):
110 ''' Entrypoint for CLI execution. '''
111 from asyncio import run
112 config = (
113 __.tyro.conf.EnumChoicesFromValues,
114 __.tyro.conf.HelptextFromCommentsOff,
115 )
116 # default = Cli(
117 # application = _application.Information( ),
118 # display = ConsoleDisplay( ),
119 # inscription = _inscription.Control( mode = _inscription.Modes.Rich ),
120 # command = InspectCommand( ),
121 # )
122 try: run( __.tyro.cli( Cli, config = config )( ) )
123 except SystemExit: raise
124 except BaseException:
125 _scribe.exception(
126 "Program terminated from uncaught exception. "
127 "Please file a bug report." )
128 raise SystemExit( 1 ) from None
131def _discover_inscription_level_name(
132 application: __.ApplicationInformation,
133 control: __.InscriptionControl,
134) -> str:
135 if control.level is None:
136 from os import environ
137 for envvar_name_base in ( 'INSCRIPTION', 'LOG' ):
138 envvar_name = (
139 "{name}_{base}_LEVEL".format(
140 base = envvar_name_base,
141 name = application.name.upper( ) ) )
142 if envvar_name not in environ: continue
143 return environ[ envvar_name ]
144 return 'INFO'
145 return control.level
148async def _prepare(
149 application: __.ApplicationInformation,
150 configedits: __.DictionaryEdits,
151 environment: bool,
152 exits: __.ExitsAsync,
153 inscription: __.InscriptionControl,
154) -> __.Globals:
155 ''' Configures logging based on verbosity. '''
156 auxdata = await __.prepare(
157 application = application,
158 configedits = configedits,
159 environment = environment,
160 exits = exits,
161 inscription = inscription )
162 _prepare_scribes( application, inscription )
163 return auxdata
166def _prepare_scribes(
167 application: __.ApplicationInformation,
168 inscription: __.InscriptionControl,
169) -> None:
170 import logging
171 from rich.console import Console
172 from rich.logging import RichHandler
173 level_name = _discover_inscription_level_name( application, inscription )
174 level = getattr( logging, level_name.upper( ) )
175 handler = RichHandler(
176 console = Console( stderr = True ),
177 rich_tracebacks = True,
178 show_time = False )
179 logging.basicConfig(
180 format = '%(name)s: %(message)s',
181 level = level,
182 handlers = [ handler ] )
183 logging.captureWarnings( True )