Coverage for tsfpga/system_utils.py: 97%

68 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-29 20:01 +0000

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the tsfpga project, a project platform for modern FPGA development. 

5# https://tsfpga.com 

6# https://gitlab.com/tsfpga/tsfpga 

7# -------------------------------------------------------------------------------------------------- 

8 

9# Standard libraries 

10import importlib.util 

11import os 

12import subprocess 

13from os.path import commonpath, relpath 

14from pathlib import Path 

15from platform import system 

16from shutil import rmtree 

17 

18# First party libraries 

19from tsfpga import DEFAULT_FILE_ENCODING 

20 

21 

22def create_file(file, contents=None): 

23 create_directory(file.parent, empty=False) 

24 

25 contents = "" if contents is None else contents 

26 with open(file, "w", encoding=DEFAULT_FILE_ENCODING) as file_handle: 

27 file_handle.write(contents) 

28 

29 return file 

30 

31 

32def read_file(file): 

33 with open(file, encoding=DEFAULT_FILE_ENCODING) as file_handle: 

34 return file_handle.read() 

35 

36 

37def read_last_lines_of_file(file, num_lines): 

38 """ 

39 Read a number of lines from the end of a file, without buffering the whole file. 

40 Similar to unix ``tail`` command. 

41 

42 Arguments: 

43 file (pathlib.Path): The file that shall be read. 

44 num_lines (int): The number of lines to read. 

45 

46 Return: 

47 str: The last lines of the file. 

48 """ 

49 result_lines = [] 

50 blocks_to_read = 0 

51 

52 with open(file, encoding=DEFAULT_FILE_ENCODING) as file_handle: 

53 while len(result_lines) < num_lines: 

54 # Since we do not know the line lengths, there is some guessing involved. Keep reading 

55 # larger and larger blocks until we have all the lines that are requested. 

56 blocks_to_read += 1 

57 

58 try: 

59 # Read a block from the end 

60 file_handle.seek(-blocks_to_read * 4096, os.SEEK_END) 

61 except IOError: 

62 # Tried to read more data than what is available. Read whatever we have and return 

63 # to user. 

64 file_handle.seek(0) 

65 result_lines = file_handle.readlines() 

66 break 

67 

68 result_lines = file_handle.readlines() 

69 

70 result = "".join(result_lines[-num_lines:]) 

71 return result 

72 

73 

74def delete(path, wait_until_deleted=False): 

75 """ 

76 Delete a file or directory from the filesystem. 

77 

78 Arguments: 

79 path (pathlib.Path): The file/directory to be deleted. 

80 wait_until_deleted (bool): When set to ``True``, the function will poll the filesystem 

81 after initiating the deletion, and not return until the path is in fact deleted. 

82 Is needed on some filesystems/mounts in a situation where we delete a path and 

83 then directly want to write to it afterwards. 

84 

85 Returns: 

86 pathlib.Path: The path that was deleted (i.e. the original ``path`` argument). 

87 """ 

88 if path.exists(): 

89 if path.is_dir(): 

90 rmtree(path) 

91 else: 

92 path.unlink() 

93 

94 if wait_until_deleted: 

95 while path.exists(): 

96 pass 

97 

98 return path 

99 

100 

101def create_directory(directory, empty=True): 

102 if empty: 

103 delete(directory) 

104 elif directory.exists(): 

105 return directory 

106 

107 directory.mkdir(parents=True) 

108 return directory 

109 

110 

111def file_is_in_directory(file_path, directories): 

112 """ 

113 Check if the file is in any of the directories. 

114 

115 Arguments: 

116 file_path (pathlib.Path): The file to be checked. 

117 directories (list(pathlib.Path)): The directories to be controlled. 

118 

119 Returns: 

120 bool: True if there is a common path. 

121 """ 

122 for directory in directories: 

123 if commonpath([str(file_path), str(directory)]) == str(directory): 

124 return True 

125 return False 

126 

127 

128def path_relative_to(path, other): 

129 """ 

130 Note Path.relative_to() does not support the use case where e.g. readme.md should get 

131 relative path "../readme.md". Hence we have to use os.path. 

132 """ 

133 assert path.exists(), path 

134 return Path(relpath(str(path), str(other))) 

135 

136 

137def run_command(cmd, cwd=None, env=None): 

138 if not isinstance(cmd, list): 

139 raise ValueError("Must be called with a list, not a string") 

140 

141 subprocess.check_call(cmd, cwd=cwd, env=env) 

142 

143 

144def load_python_module(file): 

145 python_module_name = file.stem 

146 

147 spec = importlib.util.spec_from_file_location(python_module_name, file) 

148 module = importlib.util.module_from_spec(spec) 

149 spec.loader.exec_module(module) 

150 

151 return module 

152 

153 

154def system_is_windows(): 

155 return system() == "Windows"