@@ -766,7 +766,14 @@ def help(subcommand=None):
766
766
for name , p in inspect .signature (fn ).parameters .items ():
767
767
if p .kind == inspect .Parameter .KEYWORD_ONLY :
768
768
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 } ]" )
770
777
elif p .kind == inspect .Parameter .POSITIONAL_OR_KEYWORD :
771
778
positionals .append (" " )
772
779
has_default = (p .default != inspect ._empty )
@@ -817,25 +824,65 @@ def find_editor():
817
824
error ('Could not find an editor! Set the EDITOR environment variable.' )
818
825
819
826
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
+
821
857
text = template
822
858
823
859
# 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.
825
862
issue_line = ".. gh-issue:"
826
863
without_space = "\n " + issue_line + "\n "
827
- with_space = "\n " + issue_line + " \n "
828
864
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 )
831
872
832
873
return text
833
874
834
875
835
876
@subcommand
836
- def add ():
877
+ def add (* , issue = None ):
837
878
"""
838
879
Add 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
839
886
"""
840
887
841
888
editor = find_editor ()
@@ -844,7 +891,7 @@ def add():
844
891
os .close (handle )
845
892
atexit .register (lambda : os .unlink (tmp_path ))
846
893
847
- text = _template_text_for_temp_file ( )
894
+ text = _blurb_template_text ( issue = issue )
848
895
with open (tmp_path , "w" , encoding = "utf-8" ) as file :
849
896
file .write (text )
850
897
@@ -1169,22 +1216,37 @@ def main():
1169
1216
kwargs = {}
1170
1217
for name , p in inspect .signature (fn ).parameters .items ():
1171
1218
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
+
1173
1224
kwargs [name ] = p .default
1174
1225
short_options [name [0 ]] = name
1175
1226
long_options [name ] = name
1176
1227
1177
1228
filtered_args = []
1178
1229
done_with_options = False
1230
+ consume_after = None
1179
1231
1180
1232
def handle_option (s , dict ):
1233
+ nonlocal consume_after
1181
1234
name = dict .get (s , None )
1182
1235
if not name :
1183
1236
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
1185
1243
1186
1244
# print(f"short_options {short_options} long_options {long_options}")
1187
1245
for a in args :
1246
+ if consume_after :
1247
+ kwargs [consume_after ] = a
1248
+ consume_after = None
1249
+ continue
1188
1250
if done_with_options :
1189
1251
filtered_args .append (a )
1190
1252
continue
@@ -1199,6 +1261,9 @@ def handle_option(s, dict):
1199
1261
continue
1200
1262
filtered_args .append (a )
1201
1263
1264
+ if consume_after :
1265
+ sys .exit (f"Error: blurb: { subcommand } { consume_after } "
1266
+ f"must be followed by an option argument" )
1202
1267
1203
1268
sys .exit (fn (* filtered_args , ** kwargs ))
1204
1269
except TypeError as e :
0 commit comments