-
-
Notifications
You must be signed in to change notification settings - Fork 60
Add fake f-strings to blurb. Works with 3.5! #288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,6 +112,32 @@ | |
sections.append(section.strip()) | ||
|
||
|
||
def f(s): | ||
""" | ||
Basic support for 3.6's f-strings, in 3.5! | ||
|
||
Formats "s" using appropriate globals and locals | ||
dictionaries. This f-string: | ||
f"hello a is {a}" | ||
simply becomes | ||
f("hello a is {a}") | ||
In other words, just throw parentheses around the | ||
string, and you're done! | ||
|
||
Implemented internally using str.format_map(). | ||
This means it doesn't support expressions: | ||
f("two minus three is {2-3}") | ||
And it doesn't support function calls: | ||
f("how many elements? {len(my_list)}") | ||
But most other f-string features work. | ||
""" | ||
frame = sys._getframe(1) | ||
d = dict(builtins.__dict__) | ||
d.update(frame.f_globals) | ||
d.update(frame.f_locals) | ||
return s.format_map(d) | ||
|
||
|
||
def sanitize_section(section): | ||
""" | ||
Cleans up a section string, making it viable as a directory name. | ||
|
@@ -224,10 +250,10 @@ def sortable_datetime(): | |
|
||
|
||
def prompt(prompt): | ||
return input("[{}> ".format(prompt)) | ||
return input(f("[{prompt}> ")) | ||
|
||
def require_ok(prompt): | ||
prompt = "[{}> ".format(prompt) | ||
prompt = f("[{prompt}> ") | ||
while True: | ||
s = input(prompt).strip() | ||
if s == 'ok': | ||
|
@@ -457,7 +483,7 @@ def parse(self, text, *, metadata=None, filename="input"): | |
line_number = None | ||
|
||
def throw(s): | ||
raise BlurbError("Error in {}:{}:\n{}".format(filename, line_number, s)) | ||
raise BlurbError(f("Error in {filename}:{line_number}:\n{s}")) | ||
|
||
def finish_entry(): | ||
nonlocal body | ||
|
@@ -522,8 +548,8 @@ def load(self, filename, *, metadata=None): | |
|
||
Broadly equivalent to blurb.parse(open(filename).read()). | ||
""" | ||
with open(filename, "rt", encoding="utf-8") as f: | ||
text = f.read() | ||
with open(filename, "rt", encoding="utf-8") as file: | ||
text = file.read() | ||
self.parse(text, metadata=metadata, filename=filename) | ||
|
||
def __str__(self): | ||
|
@@ -537,7 +563,7 @@ def __str__(self): | |
add_separator = True | ||
if metadata: | ||
for name, value in sorted(metadata.items()): | ||
add(".. {}: {}\n".format(name, value)) | ||
add(f(".. {name}: {value}\n")) | ||
add("\n") | ||
add(textwrap_body(body)) | ||
return "".join(output) | ||
|
@@ -547,8 +573,8 @@ def save(self, path): | |
safe_mkdir(dirname) | ||
|
||
text = str(self) | ||
with open(path, "wt", encoding="utf-8") as f: | ||
f.write(text) | ||
with open(path, "wt", encoding="utf-8") as file: | ||
file.write(text) | ||
|
||
@staticmethod | ||
def _parse_next_filename(filename): | ||
|
@@ -559,10 +585,10 @@ def _parse_next_filename(filename): | |
components = filename.split(os.sep) | ||
section, filename = components[-2:] | ||
section = unsanitize_section(section) | ||
assert section in sections, "Unknown section {}".format(section) | ||
assert section in sections, f("Unknown section {section}") | ||
|
||
fields = [x.strip() for x in filename.split(".")] | ||
assert len(fields) >= 4, "Can't parse 'next' filename! filename {!r} fields {}".format(filename, fields) | ||
assert len(fields) >= 4, f("Can't parse 'next' filename! filename {filename!r} fields {fields}") | ||
assert fields[-1] == "rst" | ||
|
||
metadata = {"date": fields[0], "nonce": fields[-2], "section": section} | ||
|
@@ -656,8 +682,8 @@ def filename_test(self, filename): | |
b.load(filename) | ||
self.assertTrue(b) | ||
if os.path.exists(filename + '.res'): | ||
with open(filename + '.res', encoding='utf-8') as f: | ||
expected = f.read() | ||
with open(filename + '.res', encoding='utf-8') as file: | ||
expected = file.read() | ||
self.assertEqual(str(b), expected) | ||
|
||
def test_files(self): | ||
|
@@ -712,8 +738,8 @@ def chdir_to_repo_root(): | |
def test_first_line(filename, test): | ||
if not os.path.exists(filename): | ||
return False | ||
with open(filename, "rt") as f: | ||
lines = f.read().split('\n') | ||
with open(filename, "rt") as file: | ||
lines = file.read().split('\n') | ||
if not (lines and test(lines[0])): | ||
return False | ||
return True | ||
|
@@ -751,7 +777,7 @@ def subcommand(fn): | |
def get_subcommand(subcommand): | ||
fn = subcommands.get(subcommand) | ||
if not fn: | ||
error("Unknown subcommand: {}\nRun 'blurb help' for help.".format(subcommand)) | ||
error(f("Unknown subcommand: {subcommand}\nRun 'blurb help' for help.")) | ||
return fn | ||
|
||
|
||
|
@@ -813,19 +839,19 @@ def help(subcommand=None): | |
for name, p in inspect.signature(fn).parameters.items(): | ||
if p.kind == inspect.Parameter.KEYWORD_ONLY: | ||
short_option = name[0] | ||
options.append(" [-{}|--{}]".format(short_option, name)) | ||
options.append(f(" [-{short_option}|--{name}]")) | ||
elif p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD: | ||
positionals.append(" ") | ||
has_default = (p.default != inspect._empty) | ||
if has_default: | ||
positionals.append("[") | ||
nesting += 1 | ||
positionals.append("<{}>".format(name)) | ||
positionals.append(f("<{name}>")) | ||
positionals.append("]" * nesting) | ||
|
||
|
||
parameters = "".join(options + positionals) | ||
print("blurb {}{}".format(subcommand, parameters)) | ||
print(f("blurb {subcommand}{parameters}")) | ||
print() | ||
print(doc) | ||
sys.exit(0) | ||
|
@@ -888,7 +914,7 @@ def add(): | |
atexit.register(lambda : os.unlink(tmp_path)) | ||
|
||
def init_tmp_with_template(): | ||
with open(tmp_path, "wt", encoding="utf-8") as f: | ||
with open(tmp_path, "wt", encoding="utf-8") as file: | ||
# hack: | ||
# my editor likes to strip trailing whitespace from lines. | ||
# normally this is a good idea. but in the case of the template | ||
|
@@ -902,7 +928,7 @@ def init_tmp_with_template(): | |
if without_space not in text: | ||
sys.exit("Can't find BPO line to ensure there's a space on the end!") | ||
text = text.replace(without_space, with_space) | ||
f.write(text) | ||
file.write(text) | ||
|
||
init_tmp_with_template() | ||
|
||
|
@@ -916,7 +942,7 @@ def init_tmp_with_template(): | |
else: | ||
args = list(shlex.split(editor)) | ||
if not shutil.which(args[0]): | ||
sys.exit("Invalid GIT_EDITOR / EDITOR value: {}".format(editor)) | ||
sys.exit(f("Invalid GIT_EDITOR / EDITOR value: {editor}")) | ||
args.append(tmp_path) | ||
|
||
while True: | ||
|
@@ -936,7 +962,7 @@ def init_tmp_with_template(): | |
|
||
if failure: | ||
print() | ||
print("Error: {}".format(failure)) | ||
print(f("Error: {failure}")) | ||
print() | ||
try: | ||
prompt("Hit return to retry (or Ctrl-C to abort)") | ||
|
@@ -970,20 +996,20 @@ def release(version): | |
if existing_filenames: | ||
error("Sorry, can't handle appending 'next' files to an existing version (yet).") | ||
|
||
output = "Misc/NEWS.d/{}.rst".format(version) | ||
output = f("Misc/NEWS.d/{version}.rst") | ||
filenames = glob_blurbs("next") | ||
blurbs = Blurbs() | ||
date = current_date() | ||
|
||
if not filenames: | ||
print("No blurbs found. Setting {} as having no changes.".format(version)) | ||
body = "There were no new changes in version {}.\n".format(version) | ||
print(f("No blurbs found. Setting {version} as having no changes.")) | ||
body = f("There were no new changes in version {version}.\n") | ||
metadata = {"no changes": "True", "bpo": "0", "section": "Library", "date": date, "nonce": nonceify(body)} | ||
blurbs.append((metadata, body)) | ||
else: | ||
no_changes = None | ||
count = len(filenames) | ||
print('Merging {} blurbs to "{}".'.format(count, output)) | ||
print(f('Merging {count} blurbs to "{output}".')) | ||
|
||
for filename in filenames: | ||
if not filename.endswith(".rst"): | ||
|
@@ -999,14 +1025,15 @@ def release(version): | |
git_add_files.append(output) | ||
flush_git_add_files() | ||
|
||
print("Removing {} 'next' files from git.".format(len(filenames))) | ||
how_many = len(filenames) | ||
print(f("Removing {how_many} 'next' files from git.")) | ||
git_rm_files.extend(filenames) | ||
flush_git_rm_files() | ||
|
||
# sanity check: ensuring that saving/reloading the merged blurb file works. | ||
blurbs2 = Blurbs() | ||
blurbs2.load(output) | ||
assert blurbs2 == blurbs, "Reloading {} isn't reproducible?!".format(output) | ||
assert blurbs2 == blurbs, f("Reloading {output} isn't reproducible?!") | ||
|
||
print() | ||
print("Ready for commit.") | ||
|
@@ -1077,7 +1104,7 @@ def print(*a, sep=" "): | |
metadata, body = blurbs[0] | ||
release_date = metadata["release date"] | ||
|
||
print("*Release date: {}*".format(release_date)) | ||
print(f("*Release date: {release_date}*")) | ||
print() | ||
|
||
if "no changes" in metadata: | ||
|
@@ -1145,11 +1172,11 @@ def populate(): | |
|
||
for section in sections: | ||
dir_name = sanitize_section(section) | ||
dir_path = "NEWS.d/next/{}".format(dir_name) | ||
dir_path = f("NEWS.d/next/{dir_name}") | ||
safe_mkdir(dir_path) | ||
readme_path = "NEWS.d/next/{}/README.rst".format(dir_name) | ||
with open(readme_path, "wt", encoding="utf-8") as f: | ||
f.write("Put news entry ``blurb`` files for the *{}* section in this directory.\n".format(section)) | ||
readme_path = f("NEWS.d/next/{dir_name}/README.rst") | ||
with open(readme_path, "wt", encoding="utf-8") as readme: | ||
readme.write(f("Put news entry ``blurb`` files for the *{section}* section in this directory.\n")) | ||
git_add_files.append(dir_path) | ||
git_add_files.append(readme_path) | ||
flush_git_add_files() | ||
|
@@ -1170,7 +1197,7 @@ def export(): | |
# """ | ||
# Test function for blurb command-line processing. | ||
# """ | ||
# print("arg: boolean {} option {}".format(boolean, option)) | ||
# print(f("arg: boolean {boolean} option {option}")) | ||
|
||
|
||
@subcommand | ||
|
@@ -1255,7 +1282,7 @@ def flush_blurb(): | |
fields.append(field) | ||
see_also = ", ".join(fields) | ||
# print("see_also: ", repr(see_also)) | ||
accumulator.append("(See also: {})".format(see_also)) | ||
accumulator.append(f("(See also: {see_also})")) | ||
see_also = None | ||
if not accumulator: | ||
return | ||
|
@@ -1301,8 +1328,8 @@ def flush_version(): | |
if version is None: | ||
assert not blurbs, "version should only be None initially, we shouldn't have blurbs yet" | ||
return | ||
assert blurbs, "No blurbs defined when flushing version {}!".format(version) | ||
output = "NEWS.d/{}.rst".format(version) | ||
assert blurbs, f("No blurbs defined when flushing version {version}!") | ||
output = f("NEWS.d/{version}.rst") | ||
|
||
if released: | ||
# saving merged blurb file for version, e.g. Misc/NEWS.d/3.7.0a1.rst | ||
|
@@ -1318,8 +1345,8 @@ def flush_version(): | |
blurbs.clear() | ||
version_count += 1 | ||
|
||
with open("NEWS", "rt", encoding="utf-8") as f: | ||
for line_number, line in enumerate(f): | ||
with open("NEWS", "rt", encoding="utf-8") as file: | ||
for line_number, line in enumerate(file): | ||
line = line.rstrip() | ||
|
||
if line.startswith("\ufeff"): | ||
|
@@ -1420,11 +1447,11 @@ def flush_version(): | |
elif line.startswith("- Issue #9516: Issue #9516: avoid errors in sysconfig when MACOSX_DEPLOYMENT_TARGET"): | ||
line = "- Issue #9516 and Issue #9516: avoid errors in sysconfig when MACOSX_DEPLOYMENT_TARGET" | ||
elif line.title().startswith(("- Request #", "- Bug #", "- Patch #", "- Patches #")): | ||
# print("FIXING LINE {}: {!r}".format(line_number), line) | ||
# print(f("FIXING LINE {line_number}: {line!r}")) | ||
line = "- Issue #" + line.partition('#')[2] | ||
# print("FIXED LINE {!r}".format(line)) | ||
# print(f("FIXED LINE {line_number}: {line!r}")) | ||
# else: | ||
# print("NOT FIXING LINE {}: {!r}".format(line_number, line)) | ||
# print(f("NOT FIXING LINE {line_number}: {line!r}")) | ||
|
||
|
||
# 4. determine the actual content of the line | ||
|
@@ -1489,7 +1516,7 @@ def flush_version(): | |
line = line[4:] | ||
parse_bpo = True | ||
else: | ||
# print("[[{:8} no bpo]] {}".format(line_number, line)) | ||
# print(f("[[{line_number:8} no bpo]] {line}")) | ||
parse_bpo = False | ||
if parse_bpo: | ||
# GAAAH | ||
|
@@ -1529,9 +1556,9 @@ def flush_version(): | |
try: | ||
int(bpo) # this will throw if it's not a legal int | ||
except ValueError: | ||
sys.exit("Couldn't convert bpo number to int on line {}! ".format(line_number) + repr(bpo)) | ||
sys.exit(f("Couldn't convert bpo number to int on line {line_number}! {bpo!r}")) | ||
if see_also == "partially": | ||
sys.exit("What the hell on line {}! ".format(line_number) + repr(bpo)) | ||
sys.exit(f("What the hell on line {line_number}! {bpo!r}")) | ||
|
||
# 4.6.1 continuation of blurb | ||
elif line.startswith(" "): | ||
|
@@ -1540,7 +1567,7 @@ def flush_version(): | |
elif line.startswith(" * "): | ||
line = line[3:] | ||
elif line: | ||
sys.exit("Didn't recognize line {}! ".format(line_number) + repr(line)) | ||
sys.exit(f("Didn't recognize line {line_number}! {line!r}")) | ||
# only add blank lines if we have an initial line in the accumulator | ||
if line or accumulator: | ||
accumulator.append(line) | ||
|
@@ -1552,7 +1579,7 @@ def flush_version(): | |
git_rm_files.append("NEWS") | ||
flush_git_rm_files() | ||
|
||
print("Wrote {} news items across {} versions.".format(blurb_count, version_count)) | ||
print(f("Wrote {blurb_count} news items across {version_count} versions.")) | ||
print() | ||
print("Ready for commit.") | ||
|
||
|
@@ -1601,10 +1628,10 @@ def main(): | |
def handle_option(s, dict): | ||
name = dict.get(s, None) | ||
if not name: | ||
sys.exit('blurb: Unknown option for {}: "{}"'.format(subcommand, s)) | ||
sys.exit(f('blurb: Unknown option for {subcommand}: "{s}"')) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would probably use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You would change the name of the variable from s to str? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, I would use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh! Sorry. Tiny font made |
||
kwargs[name] = not kwargs[name] | ||
|
||
# print("short_options {} long_options {}".format(short_options, long_options)) | ||
# print(f("short_options {short_options} long_options {long_options}")) | ||
for a in args: | ||
if done_with_options: | ||
filtered_args.append(a) | ||
|
@@ -1638,7 +1665,7 @@ def handle_option(s, dict): | |
# whoops, must be a real type error, reraise | ||
raise e | ||
|
||
how_many = "{} argument".format(specified) | ||
how_many = f("{specified} argument") | ||
if specified != 1: | ||
how_many += "s" | ||
|
||
|
@@ -1649,12 +1676,12 @@ def handle_option(s, dict): | |
middle = "requires" | ||
else: | ||
plural = "" if required == 1 else "s" | ||
middle = "requires at least {} argument{} and at most".format(required, plural) | ||
middle += " {} argument".format(total) | ||
middle = f("requires at least {required} argument{plural} and at most") | ||
middle += f(" {total} argument") | ||
if total != 1: | ||
middle += "s" | ||
|
||
print('Error: Wrong number of arguments!\n\nblurb {} {},\nand you specified {}.'.format(subcommand, middle, how_many)) | ||
print(f('Error: Wrong number of arguments!\n\nblurb {subcommand} {middle},\nand you specified {how_many}.')) | ||
print() | ||
print("usage: ", end="") | ||
help(subcommand) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I admire your tenacity for even fixing the comments!