Coverage for sources/mimeogram/display.py: 31%

39 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-19 23:01 +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''' System pager interaction. ''' 

22 

23 

24from __future__ import annotations 

25 

26from . import __ 

27 

28 

29_scribe = __.produce_scribe( __name__ ) 

30 

31 

32def discover_pager( ) -> __.cabc.Callable[ [ str ], None ]: 

33 ''' Discovers pager and returns executor function. ''' 

34 from shutil import which 

35 from subprocess import run # nosec B404 

36 pager = __.os.environ.get( 'PAGER', 'less' ) 

37 for pager_ in ( pager, 'less', 'more' ): 

38 if ( pager := which( pager_ ) ): 

39 match __.sys.platform: 

40 case 'win32': 

41 # Windows 'more.com' does not support UTF-8. 

42 if pager.lower( ).endswith( '\\more.com' ): continue 

43 case _: pass 

44 break 

45 else: pager = '' 

46 

47 if pager: 

48 

49 # TODO? async 

50 def pager_executor( filename: str ) -> None: 

51 ''' Executes pager with file. ''' 

52 run( ( pager, filename ), check = True ) # nosec B603 

53 

54 return pager_executor 

55 

56 # TODO? async 

57 def console_display( filename: str ) -> None: 

58 ''' Prints file to stdout and waits for ENTER key. ''' 

59 with open( filename, 'r', encoding = 'utf-8' ) as stream: 

60 content = stream.read( ) 

61 print( f"\n\n{content}\n\n" ) 

62 if __.sys.stdin.isatty( ): input( "Press Enter to continue..." ) 

63 

64 _scribe.warning( "Could not find pager program for display." ) 

65 return console_display 

66 

67 

68def display_content( 

69 content: str, *, 

70 suffix: str = '.txt', 

71 pager_discoverer: __.cabc.Callable[ 

72 [ ], __.cabc.Callable[ [ str ], None ] ] = discover_pager, 

73) -> None: 

74 ''' Displays content via discovered pager. ''' 

75 from .exceptions import PagerFailure 

76 pager = pager_discoverer( ) 

77 import tempfile 

78 from pathlib import Path 

79 # Using delete = False to handle file cleanup manually. This ensures 

80 # the file handle is properly closed before the pager attempts to read it, 

81 # which is particularly important on Windows where open files cannot be 

82 # simultaneously accessed by other processes without a read share. 

83 with tempfile.NamedTemporaryFile( 

84 mode = 'w', suffix = suffix, delete = False, encoding = 'utf-8' 

85 ) as tmp: 

86 filename = tmp.name 

87 tmp.write( content ) 

88 try: pager( filename ) 

89 except Exception as exc: raise PagerFailure( cause = exc ) from exc 

90 finally: 

91 try: Path( filename ).unlink( ) 

92 except Exception: # pylint: disable=broad-exception-caught 

93 _scribe.exception( f"Failed to cleanup {filename}" )