Coverage for sources/appcore/cli/standard.py: 41%

43 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-19 00:58 +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''' Standard renderers for basic CLI display presentation modes. ''' 

22 

23 

24import json as _json 

25 

26from . import __ 

27from . import core as _core 

28 

29 

30class Presentations( __.enum.Enum ): # TODO: Python 3.11: StrEnum 

31 ''' Basic presentation modes (formats). ''' 

32 

33 Json = 'json' 

34 Markdown = 'markdown' 

35 Toml = 'toml' 

36 

37 

38class DisplayOptions( _core.DisplayOptions ): 

39 ''' Display options with presentation modes. ''' 

40 

41 compact: __.typx.Annotated[ 

42 bool, __.ddoc.Doc( ''' Display CLI results compactly? ''' ), 

43 ] = False 

44 presentation: __.typx.Annotated[ 

45 Presentations, 

46 __.ddoc.Doc( ''' Presentation mode for CLI display. ''' ), 

47 ] = Presentations.Markdown 

48 

49 

50class Globals( __.Globals ): 

51 ''' Application state with display options. ''' 

52 

53 display: DisplayOptions = __.dcls.field( default_factory = DisplayOptions ) 

54 

55 

56class Renderable( 

57 __.immut.DataclassProtocol, __.typx.Protocol, 

58 decorators = ( __.typx.runtime_checkable, ), 

59): 

60 ''' Base class for objects which can render themselves. ''' 

61 

62 @__.abc.abstractmethod 

63 def render_dictionary( self ) -> dict[ str, __.typx.Any ]: 

64 ''' Returns dictionary representation of object attributes. ''' 

65 raise NotImplementedError 

66 

67 def render_json( self, compact: bool = False, indent: int = 2 ) -> str: 

68 ''' Renders object as JSON into string. ''' 

69 dictionary = self.render_dictionary( ) 

70 if compact: 

71 return _json.dumps( 

72 dictionary, ensure_ascii = False, separators = ( ',', ':' ) ) 

73 return _json.dumps( dictionary, ensure_ascii = False, indent = indent ) 

74 

75 def render_markdown( self, display: DisplayOptions ) -> tuple[ str, ... ]: 

76 ''' Renders object as Markdown into sequence of lines. ''' 

77 # TODO: Implement. 

78 return ( ) 

79 

80 def render_toml( self ) -> str: 

81 ''' Renders object as TOML into string. ''' 

82 return __.tomli_w.dumps( self.render_dictionary( ) ) 

83 

84 

85@__.ctxl.asynccontextmanager 

86async def intercept_errors( 

87 auxdata: Globals 

88) -> __.cabc.AsyncIterator[ None ]: 

89 # TODO? Utilize mapping of exceptions to exit codes. 

90 ''' Context manager which intercepts and renders exceptions. ''' 

91 try: yield 

92 # TODO: Handle renderable exceptions. 

93 except ( KeyboardInterrupt, SystemExit ): raise 

94 except BaseException as exc: 

95 # TODO: Implement. 

96 raise SystemExit( 1 ) from exc 

97 

98 

99async def render_and_print( auxdata: Globals, renderable: Renderable ) -> None: 

100 ''' Renders and prints object according to display options. ''' 

101 display = auxdata.display 

102 exits = auxdata.exits 

103 stream = await display.provide_stream( exits ) 

104 match display.presentation: 

105 case Presentations.Json: 

106 text = renderable.render_json( compact = display.compact ) 

107 case Presentations.Markdown: 

108 text = '\n'.join( renderable.render_markdown( display ) ) 

109 case Presentations.Toml: 

110 text = renderable.render_toml( ) 

111 print( text, file = stream )