Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlparse/cli.py: 12%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

85 statements  

1# Copyright (C) 2009-2020 the sqlparse authors and contributors 

2# <see AUTHORS file> 

3# 

4# This module is part of python-sqlparse and is released under 

5# the BSD License: https://opensource.org/licenses/BSD-3-Clause 

6 

7"""Module that contains the command line app. 

8 

9Why does this file exist, and why not put this in __main__? 

10 You might be tempted to import things from __main__ later, but that will 

11 cause problems: the code will get executed twice: 

12 - When you run `python -m sqlparse` python will execute 

13 ``__main__.py`` as a script. That means there won't be any 

14 ``sqlparse.__main__`` in ``sys.modules``. 

15 - When you import __main__ it will get executed again (as a module) because 

16 there's no ``sqlparse.__main__`` in ``sys.modules``. 

17 Also see (1) from http://click.pocoo.org/5/setuptools/#setuptools-integration 

18""" 

19 

20import argparse 

21import sys 

22from io import TextIOWrapper 

23 

24import sqlparse 

25from sqlparse.exceptions import SQLParseError 

26 

27 

28# TODO: Add CLI Tests 

29# TODO: Simplify formatter by using argparse `type` arguments 

30def create_parser(): 

31 _CASE_CHOICES = ['upper', 'lower', 'capitalize'] 

32 

33 parser = argparse.ArgumentParser( 

34 prog='sqlformat', 

35 description='Format FILE according to OPTIONS. Use "-" as FILE ' 

36 'to read from stdin.', 

37 usage='%(prog)s [OPTIONS] FILE [FILE ...]', 

38 ) 

39 

40 parser.add_argument( 

41 'filename', 

42 nargs='+', 

43 help='file(s) to format (use "-" for stdin)') 

44 

45 parser.add_argument( 

46 '-o', '--outfile', 

47 dest='outfile', 

48 metavar='FILE', 

49 help='write output to FILE (defaults to stdout)') 

50 

51 parser.add_argument( 

52 '--in-place', 

53 dest='inplace', 

54 action='store_true', 

55 default=False, 

56 help='format files in-place (overwrite existing files)') 

57 

58 parser.add_argument( 

59 '--version', 

60 action='version', 

61 version=sqlparse.__version__) 

62 

63 group = parser.add_argument_group('Formatting Options') 

64 

65 group.add_argument( 

66 '-k', '--keywords', 

67 metavar='CHOICE', 

68 dest='keyword_case', 

69 choices=_CASE_CHOICES, 

70 help='change case of keywords, CHOICE is one of {}'.format( 

71 ', '.join(f'"{x}"' for x in _CASE_CHOICES))) 

72 

73 group.add_argument( 

74 '-i', '--identifiers', 

75 metavar='CHOICE', 

76 dest='identifier_case', 

77 choices=_CASE_CHOICES, 

78 help='change case of identifiers, CHOICE is one of {}'.format( 

79 ', '.join(f'"{x}"' for x in _CASE_CHOICES))) 

80 

81 group.add_argument( 

82 '-l', '--language', 

83 metavar='LANG', 

84 dest='output_format', 

85 choices=['python', 'php'], 

86 help='output a snippet in programming language LANG, ' 

87 'choices are "python", "php"') 

88 

89 group.add_argument( 

90 '--strip-comments', 

91 dest='strip_comments', 

92 action='store_true', 

93 default=False, 

94 help='remove comments') 

95 

96 group.add_argument( 

97 '-r', '--reindent', 

98 dest='reindent', 

99 action='store_true', 

100 default=False, 

101 help='reindent statements') 

102 

103 group.add_argument( 

104 '--indent_width', 

105 dest='indent_width', 

106 default=2, 

107 type=int, 

108 help='indentation width (defaults to 2 spaces)') 

109 

110 group.add_argument( 

111 '--indent_after_first', 

112 dest='indent_after_first', 

113 action='store_true', 

114 default=False, 

115 help='indent after first line of statement (e.g. SELECT)') 

116 

117 group.add_argument( 

118 '--indent_columns', 

119 dest='indent_columns', 

120 action='store_true', 

121 default=False, 

122 help='indent all columns by indent_width instead of keyword length') 

123 

124 group.add_argument( 

125 '-a', '--reindent_aligned', 

126 action='store_true', 

127 default=False, 

128 help='reindent statements to aligned format') 

129 

130 group.add_argument( 

131 '-s', '--use_space_around_operators', 

132 action='store_true', 

133 default=False, 

134 help='place spaces around mathematical operators') 

135 

136 group.add_argument( 

137 '--wrap_after', 

138 dest='wrap_after', 

139 default=0, 

140 type=int, 

141 help='Column after which lists should be wrapped') 

142 

143 group.add_argument( 

144 '--comma_first', 

145 dest='comma_first', 

146 default=False, 

147 type=bool, 

148 help='Insert linebreak before comma (default False)') 

149 

150 group.add_argument( 

151 '--compact', 

152 dest='compact', 

153 default=False, 

154 type=bool, 

155 help='Try to produce more compact output (default False)') 

156 

157 group.add_argument( 

158 '--encoding', 

159 dest='encoding', 

160 default='utf-8', 

161 help='Specify the input encoding (default utf-8)') 

162 

163 return parser 

164 

165 

166def _error(msg): 

167 """Print msg and optionally exit with return code exit_.""" 

168 sys.stderr.write(f'[ERROR] {msg}\n') 

169 return 1 

170 

171 

172def _process_file(filename, args): 

173 """Process a single file with the given formatting options. 

174 

175 Returns 0 on success, 1 on error. 

176 """ 

177 # Check for incompatible option combinations first 

178 if filename == '-' and args.inplace: 

179 return _error('Cannot use --in-place with stdin') 

180 

181 # Read input 

182 if filename == '-': # read from stdin 

183 wrapper = TextIOWrapper(sys.stdin.buffer, encoding=args.encoding) 

184 try: 

185 data = wrapper.read() 

186 finally: 

187 wrapper.detach() 

188 else: 

189 try: 

190 with open(filename, encoding=args.encoding) as f: 

191 data = ''.join(f.readlines()) 

192 except OSError as e: 

193 return _error(f'Failed to read {filename}: {e}') 

194 

195 # Determine output destination 

196 close_stream = False 

197 if args.inplace: 

198 try: 

199 stream = open(filename, 'w', encoding=args.encoding) 

200 close_stream = True 

201 except OSError as e: 

202 return _error(f'Failed to open {filename}: {e}') 

203 elif args.outfile: 

204 try: 

205 stream = open(args.outfile, 'w', encoding=args.encoding) 

206 close_stream = True 

207 except OSError as e: 

208 return _error(f'Failed to open {args.outfile}: {e}') 

209 else: 

210 stream = sys.stdout 

211 

212 # Format the SQL 

213 formatter_opts = vars(args) 

214 try: 

215 formatter_opts = sqlparse.formatter.validate_options(formatter_opts) 

216 except SQLParseError as e: 

217 return _error(f'Invalid options: {e}') 

218 

219 s = sqlparse.format(data, **formatter_opts) 

220 stream.write(s) 

221 stream.flush() 

222 if close_stream: 

223 stream.close() 

224 return 0 

225 

226 

227def main(args=None): 

228 parser = create_parser() 

229 args = parser.parse_args(args) 

230 

231 # Validate argument combinations 

232 if len(args.filename) > 1: 

233 if args.outfile: 

234 return _error('Cannot use -o/--outfile with multiple files') 

235 if not args.inplace: 

236 return _error('Multiple files require --in-place flag') 

237 

238 # Process all files 

239 exit_code = 0 

240 for filename in args.filename: 

241 result = _process_file(filename, args) 

242 if result != 0: 

243 exit_code = result 

244 # Continue processing remaining files even if one fails 

245 

246 return exit_code