112112 sections .append (section .strip ())
113113
114114
115+ def f (s ):
116+ """
117+ Basic support for 3.6's f-strings, in 3.5!
118+
119+ Formats "s" using appropriate globals and locals
120+ dictionaries. This f-string:
121+ f"hello a is {a}"
122+ simply becomes
123+ f("hello a is {a}")
124+ In other words, just throw parentheses around the
125+ string, and you're done!
126+
127+ Implemented internally using str.format_map().
128+ This means it doesn't support expressions:
129+ f("two minus three is {2-3}")
130+ And it doesn't support function calls:
131+ f("how many elements? {len(my_list)}")
132+ But most other f-string features work.
133+ """
134+ frame = sys ._getframe (1 )
135+ d = dict (builtins .__dict__ )
136+ d .update (frame .f_globals )
137+ d .update (frame .f_locals )
138+ return s .format_map (d )
139+
140+
115141def sanitize_section (section ):
116142 """
117143Cleans up a section string, making it viable as a directory name.
@@ -224,10 +250,10 @@ def sortable_datetime():
224250
225251
226252def prompt (prompt ):
227- return input ("[{}> " . format ( prompt ))
253+ return input (f ( "[{prompt }> " ))
228254
229255def require_ok (prompt ):
230- prompt = "[{}> " . format ( prompt )
256+ prompt = f ( "[{prompt }> " )
231257 while True :
232258 s = input (prompt ).strip ()
233259 if s == 'ok' :
@@ -457,7 +483,7 @@ def parse(self, text, *, metadata=None, filename="input"):
457483 line_number = None
458484
459485 def throw (s ):
460- raise BlurbError ("Error in {}:{}:\n {}" . format ( filename , line_number , s ))
486+ raise BlurbError (f ( "Error in {filename }:{line_number }:\n {s}" ))
461487
462488 def finish_entry ():
463489 nonlocal body
@@ -522,8 +548,8 @@ def load(self, filename, *, metadata=None):
522548
523549Broadly equivalent to blurb.parse(open(filename).read()).
524550 """
525- with open (filename , "rt" , encoding = "utf-8" ) as f :
526- text = f .read ()
551+ with open (filename , "rt" , encoding = "utf-8" ) as file :
552+ text = file .read ()
527553 self .parse (text , metadata = metadata , filename = filename )
528554
529555 def __str__ (self ):
@@ -537,7 +563,7 @@ def __str__(self):
537563 add_separator = True
538564 if metadata :
539565 for name , value in sorted (metadata .items ()):
540- add (".. {}: {}\n " . format ( name , value ))
566+ add (f ( ".. {name }: {value }\n " ))
541567 add ("\n " )
542568 add (textwrap_body (body ))
543569 return "" .join (output )
@@ -547,8 +573,8 @@ def save(self, path):
547573 safe_mkdir (dirname )
548574
549575 text = str (self )
550- with open (path , "wt" , encoding = "utf-8" ) as f :
551- f .write (text )
576+ with open (path , "wt" , encoding = "utf-8" ) as file :
577+ file .write (text )
552578
553579 @staticmethod
554580 def _parse_next_filename (filename ):
@@ -559,10 +585,10 @@ def _parse_next_filename(filename):
559585 components = filename .split (os .sep )
560586 section , filename = components [- 2 :]
561587 section = unsanitize_section (section )
562- assert section in sections , "Unknown section {}" . format ( section )
588+ assert section in sections , f ( "Unknown section {section}" )
563589
564590 fields = [x .strip () for x in filename .split ("." )]
565- assert len (fields ) >= 4 , "Can't parse 'next' filename! filename {!r} fields {}" . format ( filename , fields )
591+ assert len (fields ) >= 4 , f ( "Can't parse 'next' filename! filename {filename !r} fields {fields}" )
566592 assert fields [- 1 ] == "rst"
567593
568594 metadata = {"date" : fields [0 ], "nonce" : fields [- 2 ], "section" : section }
@@ -656,8 +682,8 @@ def filename_test(self, filename):
656682 b .load (filename )
657683 self .assertTrue (b )
658684 if os .path .exists (filename + '.res' ):
659- with open (filename + '.res' , encoding = 'utf-8' ) as f :
660- expected = f .read ()
685+ with open (filename + '.res' , encoding = 'utf-8' ) as file :
686+ expected = file .read ()
661687 self .assertEqual (str (b ), expected )
662688
663689 def test_files (self ):
@@ -712,8 +738,8 @@ def chdir_to_repo_root():
712738 def test_first_line (filename , test ):
713739 if not os .path .exists (filename ):
714740 return False
715- with open (filename , "rt" ) as f :
716- lines = f .read ().split ('\n ' )
741+ with open (filename , "rt" ) as file :
742+ lines = file .read ().split ('\n ' )
717743 if not (lines and test (lines [0 ])):
718744 return False
719745 return True
@@ -751,7 +777,7 @@ def subcommand(fn):
751777def get_subcommand (subcommand ):
752778 fn = subcommands .get (subcommand )
753779 if not fn :
754- error ("Unknown subcommand: {}\n Run 'blurb help' for help." . format ( subcommand ))
780+ error (f ( "Unknown subcommand: {subcommand }\n Run 'blurb help' for help." ))
755781 return fn
756782
757783
@@ -813,19 +839,19 @@ def help(subcommand=None):
813839 for name , p in inspect .signature (fn ).parameters .items ():
814840 if p .kind == inspect .Parameter .KEYWORD_ONLY :
815841 short_option = name [0 ]
816- options .append (" [-{}|--{}]" . format ( short_option , name ))
842+ options .append (f ( " [-{short_option }|--{name }]" ))
817843 elif p .kind == inspect .Parameter .POSITIONAL_OR_KEYWORD :
818844 positionals .append (" " )
819845 has_default = (p .default != inspect ._empty )
820846 if has_default :
821847 positionals .append ("[" )
822848 nesting += 1
823- positionals .append ("<{}>" . format ( name ))
849+ positionals .append (f ( "<{name }>" ))
824850 positionals .append ("]" * nesting )
825851
826852
827853 parameters = "" .join (options + positionals )
828- print ("blurb {}{}" . format ( subcommand , parameters ))
854+ print (f ( "blurb {subcommand}{parameters}" ))
829855 print ()
830856 print (doc )
831857 sys .exit (0 )
@@ -888,7 +914,7 @@ def add():
888914 atexit .register (lambda : os .unlink (tmp_path ))
889915
890916 def init_tmp_with_template ():
891- with open (tmp_path , "wt" , encoding = "utf-8" ) as f :
917+ with open (tmp_path , "wt" , encoding = "utf-8" ) as file :
892918 # hack:
893919 # my editor likes to strip trailing whitespace from lines.
894920 # normally this is a good idea. but in the case of the template
@@ -902,7 +928,7 @@ def init_tmp_with_template():
902928 if without_space not in text :
903929 sys .exit ("Can't find BPO line to ensure there's a space on the end!" )
904930 text = text .replace (without_space , with_space )
905- f .write (text )
931+ file .write (text )
906932
907933 init_tmp_with_template ()
908934
@@ -916,7 +942,7 @@ def init_tmp_with_template():
916942 else :
917943 args = list (shlex .split (editor ))
918944 if not shutil .which (args [0 ]):
919- sys .exit ("Invalid GIT_EDITOR / EDITOR value: {}" . format ( editor ))
945+ sys .exit (f ( "Invalid GIT_EDITOR / EDITOR value: {editor}" ))
920946 args .append (tmp_path )
921947
922948 while True :
@@ -936,7 +962,7 @@ def init_tmp_with_template():
936962
937963 if failure :
938964 print ()
939- print ("Error: {}" . format ( failure ))
965+ print (f ( "Error: {failure}" ))
940966 print ()
941967 try :
942968 prompt ("Hit return to retry (or Ctrl-C to abort)" )
@@ -970,20 +996,20 @@ def release(version):
970996 if existing_filenames :
971997 error ("Sorry, can't handle appending 'next' files to an existing version (yet)." )
972998
973- output = "Misc/NEWS.d/{}.rst" . format ( version )
999+ output = f ( "Misc/NEWS.d/{version }.rst" )
9741000 filenames = glob_blurbs ("next" )
9751001 blurbs = Blurbs ()
9761002 date = current_date ()
9771003
9781004 if not filenames :
979- print ("No blurbs found. Setting {} as having no changes." . format ( version ))
980- body = "There were no new changes in version {}.\n " . format ( version )
1005+ print (f ( "No blurbs found. Setting {version } as having no changes." ))
1006+ body = f ( "There were no new changes in version {version }.\n " )
9811007 metadata = {"no changes" : "True" , "bpo" : "0" , "section" : "Library" , "date" : date , "nonce" : nonceify (body )}
9821008 blurbs .append ((metadata , body ))
9831009 else :
9841010 no_changes = None
9851011 count = len (filenames )
986- print ('Merging {} blurbs to "{}".' . format ( count , output ))
1012+ print (f ( 'Merging {count } blurbs to "{output }".' ))
9871013
9881014 for filename in filenames :
9891015 if not filename .endswith (".rst" ):
@@ -999,14 +1025,15 @@ def release(version):
9991025 git_add_files .append (output )
10001026 flush_git_add_files ()
10011027
1002- print ("Removing {} 'next' files from git." .format (len (filenames )))
1028+ how_many = len (filenames )
1029+ print (f ("Removing {how_many} 'next' files from git." ))
10031030 git_rm_files .extend (filenames )
10041031 flush_git_rm_files ()
10051032
10061033 # sanity check: ensuring that saving/reloading the merged blurb file works.
10071034 blurbs2 = Blurbs ()
10081035 blurbs2 .load (output )
1009- assert blurbs2 == blurbs , "Reloading {} isn't reproducible?!" . format ( output )
1036+ assert blurbs2 == blurbs , f ( "Reloading {output } isn't reproducible?!" )
10101037
10111038 print ()
10121039 print ("Ready for commit." )
@@ -1077,7 +1104,7 @@ def print(*a, sep=" "):
10771104 metadata , body = blurbs [0 ]
10781105 release_date = metadata ["release date" ]
10791106
1080- print ("*Release date: {}*" . format ( release_date ))
1107+ print (f ( "*Release date: {release_date }*" ))
10811108 print ()
10821109
10831110 if "no changes" in metadata :
@@ -1145,11 +1172,11 @@ def populate():
11451172
11461173 for section in sections :
11471174 dir_name = sanitize_section (section )
1148- dir_path = "NEWS.d/next/{}" . format ( dir_name )
1175+ dir_path = f ( "NEWS.d/next/{dir_name}" )
11491176 safe_mkdir (dir_path )
1150- readme_path = "NEWS.d/next/{}/README.rst" . format ( dir_name )
1151- with open (readme_path , "wt" , encoding = "utf-8" ) as f :
1152- f .write ("Put news entry ``blurb`` files for the *{}* section in this directory.\n " . format ( section ))
1177+ readme_path = f ( "NEWS.d/next/{dir_name }/README.rst" )
1178+ with open (readme_path , "wt" , encoding = "utf-8" ) as readme :
1179+ readme .write (f ( "Put news entry ``blurb`` files for the *{section }* section in this directory.\n " ))
11531180 git_add_files .append (dir_path )
11541181 git_add_files .append (readme_path )
11551182 flush_git_add_files ()
@@ -1170,7 +1197,7 @@ def export():
11701197# """
11711198# Test function for blurb command-line processing.
11721199# """
1173- # print("arg: boolean {} option {}".format(boolean, option ))
1200+ # print(f( "arg: boolean {boolean } option {option}" ))
11741201
11751202
11761203@subcommand
@@ -1255,7 +1282,7 @@ def flush_blurb():
12551282 fields .append (field )
12561283 see_also = ", " .join (fields )
12571284 # print("see_also: ", repr(see_also))
1258- accumulator .append ("(See also: {})" . format ( see_also ))
1285+ accumulator .append (f ( "(See also: {see_also })" ))
12591286 see_also = None
12601287 if not accumulator :
12611288 return
@@ -1301,8 +1328,8 @@ def flush_version():
13011328 if version is None :
13021329 assert not blurbs , "version should only be None initially, we shouldn't have blurbs yet"
13031330 return
1304- assert blurbs , "No blurbs defined when flushing version {}!" . format ( version )
1305- output = "NEWS.d/{}.rst" . format ( version )
1331+ assert blurbs , f ( "No blurbs defined when flushing version {version }!" )
1332+ output = f ( "NEWS.d/{version }.rst" )
13061333
13071334 if released :
13081335 # saving merged blurb file for version, e.g. Misc/NEWS.d/3.7.0a1.rst
@@ -1318,8 +1345,8 @@ def flush_version():
13181345 blurbs .clear ()
13191346 version_count += 1
13201347
1321- with open ("NEWS" , "rt" , encoding = "utf-8" ) as f :
1322- for line_number , line in enumerate (f ):
1348+ with open ("NEWS" , "rt" , encoding = "utf-8" ) as file :
1349+ for line_number , line in enumerate (file ):
13231350 line = line .rstrip ()
13241351
13251352 if line .startswith ("\ufeff " ):
@@ -1420,11 +1447,11 @@ def flush_version():
14201447 elif line .startswith ("- Issue #9516: Issue #9516: avoid errors in sysconfig when MACOSX_DEPLOYMENT_TARGET" ):
14211448 line = "- Issue #9516 and Issue #9516: avoid errors in sysconfig when MACOSX_DEPLOYMENT_TARGET"
14221449 elif line .title ().startswith (("- Request #" , "- Bug #" , "- Patch #" , "- Patches #" )):
1423- # print("FIXING LINE {}: {!r}".format(line_number), line )
1450+ # print(f( "FIXING LINE {line_number }: {line !r}") )
14241451 line = "- Issue #" + line .partition ('#' )[2 ]
1425- # print("FIXED LINE {!r}".format(line ))
1452+ # print(f( "FIXED LINE {line_number}: {line !r}"))
14261453 # else:
1427- # print("NOT FIXING LINE {}: {!r}".format(line_number, line ))
1454+ # print(f( "NOT FIXING LINE {line_number }: {line !r}"))
14281455
14291456
14301457 # 4. determine the actual content of the line
@@ -1489,7 +1516,7 @@ def flush_version():
14891516 line = line [4 :]
14901517 parse_bpo = True
14911518 else :
1492- # print("[[{:8} no bpo]] {}".format(line_number, line ))
1519+ # print(f( "[[{line_number :8} no bpo]] {line}" ))
14931520 parse_bpo = False
14941521 if parse_bpo :
14951522 # GAAAH
@@ -1529,9 +1556,9 @@ def flush_version():
15291556 try :
15301557 int (bpo ) # this will throw if it's not a legal int
15311558 except ValueError :
1532- sys .exit ("Couldn't convert bpo number to int on line {}! " . format ( line_number ) + repr ( bpo ))
1559+ sys .exit (f ( "Couldn't convert bpo number to int on line {line_number }! { bpo!r}" ))
15331560 if see_also == "partially" :
1534- sys .exit ("What the hell on line {}! " . format ( line_number ) + repr ( bpo ))
1561+ sys .exit (f ( "What the hell on line {line_number }! { bpo!r}" ))
15351562
15361563 # 4.6.1 continuation of blurb
15371564 elif line .startswith (" " ):
@@ -1540,7 +1567,7 @@ def flush_version():
15401567 elif line .startswith (" * " ):
15411568 line = line [3 :]
15421569 elif line :
1543- sys .exit ("Didn't recognize line {}! " . format ( line_number ) + repr ( line ))
1570+ sys .exit (f ( "Didn't recognize line {line_number }! { line!r}" ))
15441571 # only add blank lines if we have an initial line in the accumulator
15451572 if line or accumulator :
15461573 accumulator .append (line )
@@ -1552,7 +1579,7 @@ def flush_version():
15521579 git_rm_files .append ("NEWS" )
15531580 flush_git_rm_files ()
15541581
1555- print ("Wrote {} news items across {} versions." . format ( blurb_count , version_count ))
1582+ print (f ( "Wrote {blurb_count } news items across {version_count } versions." ))
15561583 print ()
15571584 print ("Ready for commit." )
15581585
@@ -1601,10 +1628,10 @@ def main():
16011628 def handle_option (s , dict ):
16021629 name = dict .get (s , None )
16031630 if not name :
1604- sys .exit ('blurb: Unknown option for {}: "{}"' . format ( subcommand , s ))
1631+ sys .exit (f ( 'blurb: Unknown option for {subcommand }: "{s }"' ))
16051632 kwargs [name ] = not kwargs [name ]
16061633
1607- # print("short_options {} long_options {}".format(short_options, long_options ))
1634+ # print(f( "short_options {short_options } long_options {long_options}" ))
16081635 for a in args :
16091636 if done_with_options :
16101637 filtered_args .append (a )
@@ -1638,7 +1665,7 @@ def handle_option(s, dict):
16381665 # whoops, must be a real type error, reraise
16391666 raise e
16401667
1641- how_many = "{ } argument". format ( specified )
1668+ how_many = f ( "{specified } argument" )
16421669 if specified != 1 :
16431670 how_many += "s"
16441671
@@ -1649,12 +1676,12 @@ def handle_option(s, dict):
16491676 middle = "requires"
16501677 else :
16511678 plural = "" if required == 1 else "s"
1652- middle = "requires at least {} argument{} and at most" . format ( required , plural )
1653- middle += " {} argument" . format ( total )
1679+ middle = f ( "requires at least {required } argument{plural } and at most" )
1680+ middle += f ( " {total } argument" )
16541681 if total != 1 :
16551682 middle += "s"
16561683
1657- print ('Error: Wrong number of arguments!\n \n blurb {} {},\n and you specified {}.' . format ( subcommand , middle , how_many ))
1684+ print (f ( 'Error: Wrong number of arguments!\n \n blurb {subcommand } {middle },\n and you specified {how_many }.' ))
16581685 print ()
16591686 print ("usage: " , end = "" )
16601687 help (subcommand )
0 commit comments