I know you asked how to do it in PyCharm, but I'll propose a tool instead, since in my tests it works flawlessly. You can still run it from within PyCharm's terminal window, which makes this a valid answer to your question ;-).
The tool (source code below) is based on Python's tokenizer. This guarantees that only single quotes enclosing string literals can be replaced.
Furthermore, conversions that would not be straightforward and safe, and possibly not even desirable, are avoided. The file positions that may need manual intervention are reported.
With the help of the find
command you can apply this tool to a whole source tree at once.
import argparse
import re
from tokenize import generate_tokens, STRING, untokenize
# Tool for the automatic (and manual) conversion of single-quoted strings to double-quoted ones
# Reference for string literals: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals
LONG_STRING_REGEX = re.compile(r"([fruFRU]*)'''(.*)'''", re.DOTALL)
SHORT_STRING_REGEX = re.compile(r"([fruFRU]*)'(.*)'") # only if LONG_STRING_REGEX doesn't match
def single_to_double_quotes(infilename, outfilename, long_too):
convs_done = 0
manual_conv_starts = []
output_tokens = []
with open(infilename) as infile:
for t in generate_tokens(infile.readline):
if t.type == STRING:
if m := LONG_STRING_REGEX.fullmatch(t.string):
if long_too:
prefix, value = m.groups()
if '"""' in value:
manual_conv_starts.append(t.start)
else:
t = (STRING, f'{prefix}"""{value}"""', t.start, t.end, t.line)
convs_done += 1
elif m := SHORT_STRING_REGEX.fullmatch(t.string):
prefix, value = m.groups()
if '"' in value or "'" in value:
manual_conv_starts.append(t.start)
else:
t = (STRING, f'{prefix}"{value}"', t.start, t.end, t.line)
convs_done += 1
output_tokens.append(t)
with open(outfilename, 'w') as outfile:
outfile.write(untokenize(output_tokens))
return convs_done, manual_conv_starts
parser = argparse.ArgumentParser(description='safely convert single quotes to double quotes')
parser.add_argument('infilename', metavar='INPUT_FILE', help='the input file')
parser.add_argument('outfilename', metavar='OUTPUT_FILE', help='the output file')
parser.add_argument('--long', '-l', action='store_true', help='also convert "long" (triple-quoted) strings')
args = parser.parse_args() # type: argparse.Namespace
infilename = args.infilename # type: str
outfilename = args.outfilename # type: str
long_too = bool(args.long)
print(f"input file: {infilename}")
print(f"long string conversion {('disabled', 'enabled')[long_too]}")
convs_done, manual_conv_starts = single_to_double_quotes(infilename, outfilename, long_too)
print(f"strings converted: {convs_done}")
print(f"possible manual conversions: {len(manual_conv_starts)}")
for start in manual_conv_starts:
print(f" {outfilename} line {start[0]} char {start[1] + 1}")
print(f"output file: {outfilename}")
Example output (without -l
option):
input file: single_to_double_quotes_TEST.py
long string conversion disabled
strings converted: 5
possible manual conversions: 3
single_to_double_quotes_CONV.py line 3 char 5
single_to_double_quotes_CONV.py line 4 char 5
single_to_double_quotes_CONV.py line 12 char 7
output file: single_to_double_quotes_CONV.py
single_to_double_quotes_TEST.py
:
n = 123
s = 'abc'
t = 'd"e"f'
u = 'g\'h\'i'
l = '''
"let's see"
'''
w, x, y, z = '''xyz''', '''"""yz''', '''x"""z''', '''xy"""'''
print(f'{s=}')
d = {'a': 1, 'b': 2, 'c': 3}
print(f"{d['a']=}")
print(f'{d["a"]=}')
single_to_double_quotes_CONV.py
:
n = 123
s = "abc"
t = 'd"e"f'
u = 'g\'h\'i'
l = '''
"let's see"
'''
w, x, y, z = '''xyz''', '''"""yz''', '''x"""z''', '''xy"""'''
print(f"{s=}")
d = {"a": 1, "b": 2, "c": 3}
print(f"{d['a']=}")
print(f'{d["a"]=}')
On a copy of your source tree, like on a git checkout, you can do the following:
find . -name \*.py -exec python3 PATH/TO/TOOL.py {} {} \;
This will convert each file onto itself.
In case you are not familiar with argparse: start from
python3 TOOL.py -h
I recommend you modify the tool to best fit your needs.
black
python formatter, it opts for double-quoted strings by default and can easily be integrated with PyCharm. – Strangeblack
– Restful:
, then%s/'/"/g
and hit enter. – Lacerationlib2to3
, which ships with CPython. Another option is libcst. Putting aside the handling of f-strings and triple quoted strings, you should be able to parse a module into CST, change every string's quote type, and generate the source code again, with nothing else changing. – Flyweight