Skip to content

Commit a4f867f

Browse files
authored
Move 'blurb merge' to blurb._merge (#55)
1 parent 5dd62ee commit a4f867f

File tree

7 files changed

+275
-276
lines changed

7 files changed

+275
-276
lines changed

src/blurb/_cli.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ def prompt(prompt: str, /) -> str:
2727
return input(f'[{prompt}> ')
2828

2929

30+
def require_ok(prompt: str, /) -> str:
31+
prompt = f"[{prompt}> "
32+
while True:
33+
s = input(prompt).strip()
34+
if s == 'ok':
35+
return s
36+
37+
3038
def subcommand(fn: CommandFunc):
3139
global subcommands
3240
subcommands[fn.__name__] = fn
@@ -138,7 +146,6 @@ def _blurb_help() -> None:
138146

139147

140148
def main() -> None:
141-
global original_dir
142149

143150
args = sys.argv[1:]
144151

@@ -157,8 +164,9 @@ def main() -> None:
157164
if fn in (help, version):
158165
raise SystemExit(fn(*args))
159166

167+
import blurb._merge
168+
blurb._merge.original_dir = os.getcwd()
160169
try:
161-
original_dir = os.getcwd()
162170
chdir_to_repo_root()
163171

164172
# map keyword arguments to options

src/blurb/_merge.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import os
2+
import sys
3+
from pathlib import Path
4+
5+
from blurb._cli import require_ok, subcommand
6+
from blurb._versions import glob_versions, printable_version
7+
from blurb.blurb import Blurbs, glob_blurbs, textwrap_body
8+
9+
original_dir: str = os.getcwd()
10+
11+
12+
@subcommand
13+
def merge(output: str | None = None, *, forced: bool = False) -> None:
14+
"""Merge all blurbs together into a single Misc/NEWS file.
15+
16+
Optional output argument specifies where to write to.
17+
Default is <cpython-root>/Misc/NEWS.
18+
19+
If overwriting, blurb merge will prompt you to make sure it's okay.
20+
To force it to overwrite, use -f.
21+
"""
22+
if output:
23+
output = os.path.join(original_dir, output)
24+
else:
25+
output = 'Misc/NEWS'
26+
27+
versions = glob_versions()
28+
if not versions:
29+
sys.exit("You literally don't have ANY blurbs to merge together!")
30+
31+
if os.path.exists(output) and not forced:
32+
print(f'You already have a {output!r} file.')
33+
require_ok('Type ok to overwrite')
34+
35+
write_news(output, versions=versions)
36+
37+
38+
def write_news(output: str, *, versions: list[str]) -> None:
39+
buff = []
40+
41+
def prnt(msg: str = '', /):
42+
buff.append(msg)
43+
44+
prnt("""
45+
+++++++++++
46+
Python News
47+
+++++++++++
48+
49+
""".strip())
50+
51+
for version in versions:
52+
filenames = glob_blurbs(version)
53+
54+
blurbs = Blurbs()
55+
if version == 'next':
56+
for filename in filenames:
57+
if os.path.basename(filename) == 'README.rst':
58+
continue
59+
blurbs.load_next(filename)
60+
if not blurbs:
61+
continue
62+
metadata = blurbs[0][0]
63+
metadata['release date'] = 'XXXX-XX-XX'
64+
else:
65+
assert len(filenames) == 1
66+
blurbs.load(filenames[0])
67+
68+
header = f"What's New in Python {printable_version(version)}?"
69+
prnt()
70+
prnt(header)
71+
prnt('=' * len(header))
72+
prnt()
73+
74+
metadata, body = blurbs[0]
75+
release_date = metadata['release date']
76+
77+
prnt(f'*Release date: {release_date}*')
78+
prnt()
79+
80+
if 'no changes' in metadata:
81+
prnt(body)
82+
prnt()
83+
continue
84+
85+
last_section = None
86+
for metadata, body in blurbs:
87+
section = metadata['section']
88+
if last_section != section:
89+
last_section = section
90+
prnt(section)
91+
prnt('-' * len(section))
92+
prnt()
93+
if metadata.get('gh-issue'):
94+
issue_number = metadata['gh-issue']
95+
if int(issue_number):
96+
body = f'gh-{issue_number}: {body}'
97+
elif metadata.get('bpo'):
98+
issue_number = metadata['bpo']
99+
if int(issue_number):
100+
body = f'bpo-{issue_number}: {body}'
101+
102+
body = f'- {body}'
103+
text = textwrap_body(body, subsequent_indent=' ')
104+
prnt(text)
105+
prnt()
106+
prnt('**(For information about older versions, consult the HISTORY file.)**')
107+
108+
new_contents = '\n'.join(buff)
109+
110+
# Only write in `output` if the contents are different
111+
# This speeds up subsequent Sphinx builds
112+
try:
113+
previous_contents = Path(output).read_text(encoding='utf-8')
114+
except (FileNotFoundError, UnicodeError):
115+
previous_contents = None
116+
if new_contents != previous_contents:
117+
Path(output).write_text(new_contents, encoding='utf-8')
118+
else:
119+
print(output, 'is already up to date')

src/blurb/_versions.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import glob
2+
import sys
3+
4+
if sys.version_info[:2] >= (3, 11):
5+
from contextlib import chdir
6+
else:
7+
import os
8+
9+
class chdir:
10+
def __init__(self, path: str, /) -> None:
11+
self.path = path
12+
13+
def __enter__(self) -> None:
14+
self.previous_cwd = os.getcwd()
15+
os.chdir(self.path)
16+
17+
def __exit__(self, *args) -> None:
18+
os.chdir(self.previous_cwd)
19+
20+
21+
def glob_versions() -> list[str]:
22+
versions = []
23+
with chdir('Misc/NEWS.d'):
24+
for wildcard in ('2.*.rst', '3.*.rst', 'next'):
25+
versions += [x.partition('.rst')[0] for x in glob.glob(wildcard)]
26+
versions.sort(key=version_key, reverse=True)
27+
return versions
28+
29+
30+
def version_key(element: str, /) -> str:
31+
fields = list(element.split('.'))
32+
if len(fields) == 1:
33+
return element
34+
35+
# in sorted order,
36+
# 3.5.0a1 < 3.5.0b1 < 3.5.0rc1 < 3.5.0
37+
# so for sorting purposes we transform
38+
# "3.5." and "3.5.0" into "3.5.0zz0"
39+
last = fields.pop()
40+
for s in ('a', 'b', 'rc'):
41+
if s in last:
42+
last, stage, stage_version = last.partition(s)
43+
break
44+
else:
45+
stage = 'zz'
46+
stage_version = '0'
47+
48+
fields.append(last)
49+
while len(fields) < 3:
50+
fields.append('0')
51+
52+
fields.extend([stage, stage_version])
53+
fields = [s.rjust(6, '0') for s in fields]
54+
55+
return '.'.join(fields)
56+
57+
58+
def printable_version(version: str, /) -> str:
59+
if version == 'next':
60+
return version
61+
if 'a' in version:
62+
return version.replace('a', ' alpha ')
63+
if 'b' in version:
64+
return version.replace('b', ' beta ')
65+
if 'rc' in version:
66+
return version.replace('rc', ' release candidate ')
67+
return version + ' final'

0 commit comments

Comments
 (0)