Skip to content

Commit eb3e9d7

Browse files
AA-Turnerpicnixzhugovk
authored
Add a -s / --section option to blurb add (#49)
Co-authored-by: Bénédikt Tran <[email protected]> Co-authored-by: Hugo van Kemenade <[email protected]>
1 parent 589bfba commit eb3e9d7

File tree

4 files changed

+137
-8
lines changed

4 files changed

+137
-8
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- Add the `-i` / `--issue` option to the 'blurb add' command.
66
This lets you pre-fill the `gh-issue` field in the template.
7+
- Add the `-s` / `--section` option to the 'blurb add' command.
8+
This lets you pre-fill the `section` field in the template.
79

810
## 2.0.0
911

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ Here's how you interact with the file:
120120
For example, if this should go in the `Library` section, uncomment
121121
the line reading `#.. section: Library`. To uncomment, just delete
122122
the `#` at the front of the line.
123+
The section can also be specified via the ``-s`` / ``--section`` option:
124+
125+
```shell
126+
$ blurb add -s Library
127+
# or
128+
$ blurb add -s library
129+
```
123130

124131
* Finally, go to the end of the file, and enter your `NEWS` entry.
125132
This should be a single paragraph of English text using

src/blurb/blurb.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -851,8 +851,36 @@ def _extract_issue_number(issue, /):
851851
sys.exit(f"Invalid GitHub issue number: {issue}")
852852

853853

854-
def _blurb_template_text(*, issue):
854+
def _extract_section_name(section, /):
855+
if section is None:
856+
return None
857+
858+
section = section.strip()
859+
if not section:
860+
sys.exit("Empty section name!")
861+
862+
matches = []
863+
# Try an exact or lowercase match
864+
for section_name in sections:
865+
if section in {section_name, section_name.lower()}:
866+
matches.append(section_name)
867+
868+
if not matches:
869+
section_list = '\n'.join(f'* {s}' for s in sections)
870+
sys.exit(f"Invalid section name: {section!r}\n\n"
871+
f"Valid names are:\n\n{section_list}")
872+
873+
if len(matches) > 1:
874+
multiple_matches = ', '.join(f'* {m}' for m in sorted(matches))
875+
sys.exit(f"More than one match for {section!r}:\n\n"
876+
f"{multiple_matches}")
877+
878+
return matches[0]
879+
880+
881+
def _blurb_template_text(*, issue, section):
855882
issue_number = _extract_issue_number(issue)
883+
section_name = _extract_section_name(section)
856884

857885
text = template
858886

@@ -870,11 +898,16 @@ def _blurb_template_text(*, issue):
870898
with_issue_number = f"\n{issue_line} {issue_number}\n"
871899
text = text.replace(without_space, with_issue_number)
872900

901+
# Uncomment the section if needed.
902+
if section_name is not None:
903+
pattern = f'.. section: {section_name}'
904+
text = text.replace(f'#{pattern}', pattern)
905+
873906
return text
874907

875908

876909
@subcommand
877-
def add(*, issue=None):
910+
def add(*, issue=None, section=None):
878911
"""
879912
Add a blurb (a Misc/NEWS.d/next entry) to the current CPython repo.
880913
@@ -883,6 +916,17 @@ def add(*, issue=None):
883916
blurb add -i 12345
884917
# or
885918
blurb add -i https://github.com/python/cpython/issues/12345
919+
920+
Use -s/--section to specify the section name (case-insensitive), e.g.:
921+
922+
blurb add -s Library
923+
# or
924+
blurb add -s library
925+
926+
The known sections names are defined as follows and
927+
spaces in names can be substituted for underscores:
928+
929+
{sections}
886930
"""
887931

888932
editor = find_editor()
@@ -891,7 +935,7 @@ def add(*, issue=None):
891935
os.close(handle)
892936
atexit.register(lambda : os.unlink(tmp_path))
893937

894-
text = _blurb_template_text(issue=issue)
938+
text = _blurb_template_text(issue=issue, section=section)
895939
with open(tmp_path, "w", encoding="utf-8") as file:
896940
file.write(text)
897941

@@ -940,7 +984,7 @@ def add(*, issue=None):
940984
git_add_files.append(path)
941985
flush_git_add_files()
942986
print("Ready for commit.")
943-
987+
add.__doc__ = add.__doc__.format(sections='\n'.join(f'* {s}' for s in sections))
944988

945989

946990
@subcommand

tests/test_blurb_add.py

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
def test_valid_no_issue_number():
99
assert blurb._extract_issue_number(None) is None
10-
res = blurb._blurb_template_text(issue=None)
10+
res = blurb._blurb_template_text(issue=None, section=None)
1111
lines = frozenset(res.splitlines())
1212
assert '.. gh-issue:' not in lines
1313
assert '.. gh-issue: ' in lines
@@ -37,7 +37,7 @@ def test_valid_issue_number_12345(issue):
3737
actual = blurb._extract_issue_number(issue)
3838
assert actual == 12345
3939

40-
res = blurb._blurb_template_text(issue=issue)
40+
res = blurb._blurb_template_text(issue=issue, section=None)
4141
lines = frozenset(res.splitlines())
4242
assert '.. gh-issue:' not in lines
4343
assert '.. gh-issue: ' not in lines
@@ -70,7 +70,7 @@ def test_valid_issue_number_12345(issue):
7070
def test_invalid_issue_number(issue):
7171
error_message = re.escape(f'Invalid GitHub issue number: {issue}')
7272
with pytest.raises(SystemExit, match=error_message):
73-
blurb._blurb_template_text(issue=issue)
73+
blurb._blurb_template_text(issue=issue, section=None)
7474

7575

7676
@pytest.mark.parametrize('invalid', (
@@ -84,4 +84,80 @@ def test_malformed_gh_issue_line(invalid, monkeypatch):
8484
with monkeypatch.context() as cm:
8585
cm.setattr(blurb, 'template', template)
8686
with pytest.raises(SystemExit, match=error_message):
87-
blurb._blurb_template_text(issue='1234')
87+
blurb._blurb_template_text(issue='1234', section=None)
88+
89+
90+
def _check_section_name(section_name, expected):
91+
actual = blurb._extract_section_name(section_name)
92+
assert actual == expected
93+
94+
res = blurb._blurb_template_text(issue=None, section=section_name)
95+
res = res.splitlines()
96+
for section_name in blurb.sections:
97+
if section_name == expected:
98+
assert f'.. section: {section_name}' in res
99+
else:
100+
assert f'#.. section: {section_name}' in res
101+
assert f'.. section: {section_name}' not in res
102+
103+
104+
@pytest.mark.parametrize(
105+
('section_name', 'expected'),
106+
[(name, name) for name in blurb.sections],
107+
)
108+
def test_exact_names(section_name, expected):
109+
_check_section_name(section_name, expected)
110+
111+
112+
@pytest.mark.parametrize(
113+
('section_name', 'expected'),
114+
[(name.lower(), name) for name in blurb.sections],
115+
)
116+
def test_exact_names_lowercase(section_name, expected):
117+
_check_section_name(section_name, expected)
118+
119+
120+
@pytest.mark.parametrize('section', (
121+
'',
122+
' ',
123+
'\t',
124+
'\n',
125+
'\r\n',
126+
' ',
127+
))
128+
def test_empty_section_name(section):
129+
error_message = re.escape('Empty section name!')
130+
with pytest.raises(SystemExit, match=error_message):
131+
blurb._extract_section_name(section)
132+
133+
with pytest.raises(SystemExit, match=error_message):
134+
blurb._blurb_template_text(issue=None, section=section)
135+
136+
137+
@pytest.mark.parametrize('section', [
138+
# Wrong capitalisation
139+
'C api',
140+
'c API',
141+
'LibrarY',
142+
# Invalid
143+
'_',
144+
'-',
145+
'/',
146+
'invalid',
147+
'Not a section',
148+
# Non-special names
149+
'c?api',
150+
'cXapi',
151+
'C+API',
152+
# Super-strings
153+
'Library and more',
154+
'library3',
155+
'librari',
156+
])
157+
def test_invalid_section_name(section):
158+
error_message = rf"(?m)Invalid section name: '{re.escape(section)}'\n\n.+"
159+
with pytest.raises(SystemExit, match=error_message):
160+
blurb._extract_section_name(section)
161+
162+
with pytest.raises(SystemExit, match=error_message):
163+
blurb._blurb_template_text(issue=None, section=section)

0 commit comments

Comments
 (0)