Coverage for sources/mimeogram/cli.py: 32%
59 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-03 00:13 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-03 00:13 +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 __future__ import annotations
26from . import __
27from . import apply as _apply
28from . import create as _create
29from . import interfaces as _interfaces
30from . import prompt as _prompt
33_scribe = __.produce_scribe( __name__ )
36class VersionCommand(
37 _interfaces.CliCommand,
38 decorators = ( __.standard_dataclass, __.standard_tyro_class ),
39):
40 ''' Prints version information. '''
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 )
48 def provide_configuration_edits( self ) -> __.DictionaryEdits:
49 ''' Provides edits against configuration from options. '''
50 return ( )
53class Cli(
54 metaclass = __.ImmutableStandardDataclass,
55 decorators = ( __.standard_dataclass, __.simple_tyro_class ),
56):
57 ''' Mimeogram: hierarchical data exchange between humans and LLMs. '''
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 ]
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 )
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
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
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
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
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 )