Coverage for bookbuilderpy/preprocessor_code.py: 81%

62 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-17 23:15 +0000

1"""A preprocessor for loading code.""" 

2 

3from os.path import basename 

4from typing import Final 

5 

6import bookbuilderpy.constants as bc 

7from bookbuilderpy.format_python import preprocess_python 

8from bookbuilderpy.logger import logger 

9from bookbuilderpy.path import Path 

10from bookbuilderpy.strings import ( 

11 enforce_non_empty_str_without_ws, 

12 lines_to_str, 

13) 

14from bookbuilderpy.types import type_error 

15 

16 

17def get_programming_language(path: str) -> str | None: 

18 """ 

19 Get the programming language corresponding to a path. 

20 

21 :param path: the path to the source file 

22 :return: a string identifying the programming language, or None if none 

23 detected. 

24 """ 

25 _, suffix = Path.split_prefix_suffix(basename(Path.path(path))) 

26 suffix = suffix.lower() 

27 if suffix == "py": 

28 return bc.LANG_PYTHON 

29 return bc.LANG_UNDEFINED 

30 

31 

32def load_code(path: str, lines: str, labels: str, args: str) -> str: 

33 """ 

34 Load a piece of code from the given path. 

35 

36 :param path: the path 

37 :param lines: a line definition string 

38 :param labels: a label definition string 

39 :param args: a string of arguments to be passed to the formatter 

40 :return: the code 

41 """ 

42 src = Path.file(path) 

43 logger(f"Now loading code from '{src}'.") 

44 

45 keep_lines: list[int] | None = None 

46 if lines is not None: 

47 if not isinstance(lines, str): 

48 raise type_error(lines, "lines", str) 

49 

50 if len(lines) > 0: 

51 keep_lines = [] 

52 for the_line in lines.split(","): 

53 line = the_line.strip() 

54 if "-" in line: 

55 ab = line.split("-") 

56 if len(ab) != 2: 

57 raise ValueError(f"Invalid lines: {lines}.") 

58 keep_lines.extend(range(int(ab[0]) - 1, int(ab[1]))) 

59 else: 

60 keep_lines.append(int(line) - 1) 

61 

62 keep_labels: set[str] | None = None 

63 if labels is not None: 

64 if not isinstance(labels, str): 

65 raise type_error(labels, "labels", str) 

66 if len(labels) > 0: 

67 keep_labels = set() 

68 for label in labels.split(","): 

69 ll = enforce_non_empty_str_without_ws(label.strip()) 

70 if ll in keep_labels: 

71 raise ValueError(f"duplicate label: '{ll}'") 

72 keep_labels.add(ll) 

73 if len(keep_labels) <= 0: 

74 raise ValueError(f"labels='{labels}'.") 

75 

76 arg_set: Final[set[str]] = set() 

77 if args is not None: 

78 if not isinstance(args, str): 

79 raise type_error(args, "args", str) 

80 if len(args) > 0: 

81 for arg in args.split(","): 

82 aa = enforce_non_empty_str_without_ws(arg.strip()) 

83 if aa in arg_set: 

84 raise ValueError(f"duplicate argument: '{aa}'") 

85 arg_set.add(aa) 

86 

87 text: Final[list[str]] = src.read_all_list() 

88 if len(text) <= 0: 

89 raise ValueError(f"File '{path}' is empty.") 

90 

91 if get_programming_language(path) == bc.LANG_PYTHON: 

92 return preprocess_python(text, keep_lines, keep_labels, arg_set) 

93 

94 if keep_lines is None: 

95 return lines_to_str([t.rstrip() for t in text]) 

96 

97 return lines_to_str([text[i].rstrip() for i in keep_lines])