Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2# This file is part of the lib3to6 project 

3# https://github.com/mbarkhau/lib3to6 

4# 

5# Copyright (c) 2019-2021 Manuel Barkhau (mbarkhau@gmail.com) - MIT License 

6# SPDX-License-Identifier: MIT 

7 

8import io 

9import re 

10import sys 

11import typing as typ 

12import difflib 

13import logging 

14 

15import click 

16 

17from . import common 

18from . import packaging 

19from . import transpile 

20 

21try: 

22 import pretty_traceback 

23 

24 pretty_traceback.install(envvar='ENABLE_PRETTY_TRACEBACK') 

25except ImportError: 

26 pass # no need to fail because of missing dev dependency 

27 

28 

29logger = logging.getLogger("lib3to6") 

30 

31 

32def _configure_logging(verbose: int = 0) -> None: 

33 if verbose >= 2: 

34 log_format = "%(asctime)s.%(msecs)03d %(levelname)-7s %(name)-17s - %(message)s" 

35 log_level = logging.DEBUG 

36 elif verbose == 1: 

37 log_format = "%(levelname)-7s - %(message)s" 

38 log_level = logging.INFO 

39 else: 

40 log_format = "%(levelname)-7s - %(message)s" 

41 log_level = logging.INFO 

42 

43 logging.basicConfig(level=log_level, format=log_format, datefmt="%Y-%m-%dT%H:%M:%S") 

44 logger.debug("Logging configured.") 

45 

46 

47click.disable_unicode_literals_warning = True # type: ignore[attr-defined] 

48 

49 

50def _print_diff(source_text: str, fixed_source_text: str) -> None: 

51 differ = difflib.Differ() 

52 

53 source_lines = source_text.splitlines() 

54 fixed_source_lines = fixed_source_text.splitlines() 

55 diff_lines = differ.compare(source_lines, fixed_source_lines) 

56 if not sys.stdout.isatty(): 

57 click.echo("\n".join(diff_lines)) 

58 return 

59 

60 for line in diff_lines: 

61 if line.startswith("+ "): 

62 click.echo("\u001b[32m" + line + "\u001b[0m") 

63 elif line.startswith("- "): 

64 click.echo("\u001b[31m" + line + "\u001b[0m") 

65 elif line.startswith("? "): 

66 click.echo("\u001b[36m" + line + "\u001b[0m") 

67 else: 

68 click.echo(line) 

69 print() 

70 

71 

72__INSTALL_REQUIRES_HELP = """ 

73install_requires package dependencies (space separated). 

74Functions as a whitelist for backported modules. 

75""" 

76 

77__DEFAULT_MODE_HELP = """ 

78[enabled/disabled] Default transpile mode. 

79To transpile some files but not others. 

80""" 

81 

82 

83@click.command() 

84@click.option( 

85 '-v', 

86 '--verbose', 

87 count=True, 

88 help="Control log level. -vv for debug level.", 

89) 

90@click.option( 

91 "--target-version", 

92 default="2.7", 

93 metavar="<version>", 

94 help="Target version of python.", 

95) 

96@click.option( 

97 "--diff", 

98 default=False, 

99 is_flag=True, 

100 help="Output diff instead of transpiled source.", 

101) 

102@click.option( 

103 "--in-place", 

104 default=False, 

105 is_flag=True, 

106 help="Write result back to input file.", 

107) 

108@click.option( 

109 "--install-requires", 

110 default=None, 

111 metavar="<packages>", 

112 help=__INSTALL_REQUIRES_HELP.strip(), 

113) 

114@click.option( 

115 "--default-mode", 

116 default='enabled', 

117 metavar="<mode>", 

118 help=__DEFAULT_MODE_HELP.strip(), 

119) 

120@click.argument( 

121 "source_files", 

122 metavar="<source_file>", 

123 nargs=-1, 

124 type=click.File(mode="r"), 

125) 

126def main( 

127 target_version : str, 

128 diff : bool, 

129 in_place : bool, 

130 install_requires: typ.Optional[str], 

131 source_files : typ.Sequence[io.TextIOWrapper], 

132 default_mode : str = 'enabled', 

133 verbose : int = 0, 

134) -> None: 

135 _configure_logging(verbose) 

136 

137 has_opt_error = False 

138 

139 if target_version and not re.match(r"[0-9]+\.[0-9]+", target_version): 

140 print(f"Invalid argument --target-version={target_version}") 

141 has_opt_error = True 

142 

143 if default_mode not in ('enabled', 'disabled'): 

144 print(f"Invalid argument --default-mode={default_mode}") 

145 print(" Must be either 'enabled' or 'disabled'") 

146 has_opt_error = True 

147 

148 if not any(source_files): 

149 print("No files.") 

150 has_opt_error = True 

151 

152 if has_opt_error: 

153 sys.exit(1) 

154 

155 cfg = packaging.eval_build_config( 

156 target_version=target_version, 

157 install_requires=install_requires, 

158 default_mode=default_mode, 

159 ) 

160 for src_file in source_files: 

161 ctx = common.BuildContext(cfg, src_file.name) 

162 source_text = src_file.read() 

163 try: 

164 fixed_source_text = transpile.transpile_module(ctx, source_text) 

165 except common.CheckError as err: 

166 loc = src_file.name 

167 if err.lineno >= 0: 

168 loc += "@" + str(err.lineno) 

169 

170 err.args = (loc + " - " + err.args[0],) + err.args[1:] 

171 raise 

172 

173 if diff: 

174 _print_diff(source_text, fixed_source_text) 

175 elif in_place: 

176 with io.open(src_file.name, mode="w", encoding="utf-8") as fobj: 

177 fobj.write(fixed_source_text) 

178 else: 

179 print(fixed_source_text) 

180 

181 

182if __name__ == '__main__': 

183 # NOTE (mb 2020-07-18): click supplies the parameters 

184 # pylint:disable=no-value-for-parameter 

185 main()