@@ -766,7 +766,14 @@ def help(subcommand=None):
766766 for name , p in inspect .signature (fn ).parameters .items ():
767767 if p .kind == inspect .Parameter .KEYWORD_ONLY :
768768 short_option = name [0 ]
769- options .append (f" [-{ short_option } |--{ name } ]" )
769+ if isinstance (p .default , bool ):
770+ options .append (f" [-{ short_option } |--{ name } ]" )
771+ else :
772+ if p .default is None :
773+ metavar = f'{ name .upper ()} '
774+ else :
775+ metavar = f'{ name .upper ()} [={ p .default } ]'
776+ options .append (f" [-{ short_option } |--{ name } { metavar } ]" )
770777 elif p .kind == inspect .Parameter .POSITIONAL_OR_KEYWORD :
771778 positionals .append (" " )
772779 has_default = (p .default != inspect ._empty )
@@ -817,25 +824,65 @@ def find_editor():
817824 error ('Could not find an editor! Set the EDITOR environment variable.' )
818825
819826
820- def _template_text_for_temp_file ():
827+ def _extract_issue_number (issue , / ):
828+ if issue is None :
829+ return None
830+ issue = issue .strip ()
831+
832+ if issue .startswith (('GH-' , 'gh-' )):
833+ stripped = issue [3 :]
834+ else :
835+ stripped = issue .removeprefix ('#' )
836+ try :
837+ if stripped .isdecimal ():
838+ return int (stripped )
839+ except ValueError :
840+ pass
841+
842+ # Allow GitHub URL with or without the scheme
843+ stripped = issue .removeprefix ('https://' )
844+ stripped = stripped .removeprefix ('github.com/python/cpython/issues/' )
845+ try :
846+ if stripped .isdecimal ():
847+ return int (stripped )
848+ except ValueError :
849+ pass
850+
851+ sys .exit (f"Invalid GitHub issue number: { issue } " )
852+
853+
854+ def _blurb_template_text (* , issue ):
855+ issue_number = _extract_issue_number (issue )
856+
821857 text = template
822858
823859 # Ensure that there is a trailing space after '.. gh-issue:' to make
824- # filling in the template easier.
860+ # filling in the template easier, unless an issue number was given
861+ # through the --issue command-line flag.
825862 issue_line = ".. gh-issue:"
826863 without_space = "\n " + issue_line + "\n "
827- with_space = "\n " + issue_line + " \n "
828864 if without_space not in text :
829- sys .exit ("Can't find gh-issue line to ensure there's a space on the end!" )
830- text = text .replace (without_space , with_space )
865+ sys .exit ("Can't find gh-issue line in the template!" )
866+ if issue_number is None :
867+ with_space = "\n " + issue_line + " \n "
868+ text = text .replace (without_space , with_space )
869+ else :
870+ with_issue_number = f"\n { issue_line } { issue_number } \n "
871+ text = text .replace (without_space , with_issue_number )
831872
832873 return text
833874
834875
835876@subcommand
836- def add ():
877+ def add (* , issue = None ):
837878 """
838879Add a blurb (a Misc/NEWS.d/next entry) to the current CPython repo.
880+
881+ Use -i/--issue to specify a GitHub issue number or link, e.g.:
882+
883+ blurb add -i 12345
884+ # or
885+ blurb add -i https://github.com/python/cpython/issues/12345
839886 """
840887
841888 editor = find_editor ()
@@ -844,7 +891,7 @@ def add():
844891 os .close (handle )
845892 atexit .register (lambda : os .unlink (tmp_path ))
846893
847- text = _template_text_for_temp_file ( )
894+ text = _blurb_template_text ( issue = issue )
848895 with open (tmp_path , "w" , encoding = "utf-8" ) as file :
849896 file .write (text )
850897
@@ -1169,22 +1216,37 @@ def main():
11691216 kwargs = {}
11701217 for name , p in inspect .signature (fn ).parameters .items ():
11711218 if p .kind == inspect .Parameter .KEYWORD_ONLY :
1172- assert isinstance (p .default , bool ), "blurb command-line processing only handles boolean options"
1219+ if (p .default is not None
1220+ and not isinstance (p .default , (bool , str ))):
1221+ sys .exit ("blurb command-line processing cannot handle "
1222+ f"options of type { type (p .default ).__qualname__ } " )
1223+
11731224 kwargs [name ] = p .default
11741225 short_options [name [0 ]] = name
11751226 long_options [name ] = name
11761227
11771228 filtered_args = []
11781229 done_with_options = False
1230+ consume_after = None
11791231
11801232 def handle_option (s , dict ):
1233+ nonlocal consume_after
11811234 name = dict .get (s , None )
11821235 if not name :
11831236 sys .exit (f'blurb: Unknown option for { subcommand } : "{ s } "' )
1184- kwargs [name ] = not kwargs [name ]
1237+
1238+ value = kwargs [name ]
1239+ if isinstance (value , bool ):
1240+ kwargs [name ] = not value
1241+ else :
1242+ consume_after = name
11851243
11861244 # print(f"short_options {short_options} long_options {long_options}")
11871245 for a in args :
1246+ if consume_after :
1247+ kwargs [consume_after ] = a
1248+ consume_after = None
1249+ continue
11881250 if done_with_options :
11891251 filtered_args .append (a )
11901252 continue
@@ -1199,6 +1261,9 @@ def handle_option(s, dict):
11991261 continue
12001262 filtered_args .append (a )
12011263
1264+ if consume_after :
1265+ sys .exit (f"Error: blurb: { subcommand } { consume_after } "
1266+ f"must be followed by an option argument" )
12021267
12031268 sys .exit (fn (* filtered_args , ** kwargs ))
12041269 except TypeError as e :
0 commit comments