Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/blurb/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ def prompt(prompt: str, /) -> str:
return input(f'[{prompt}> ')


def require_ok(prompt: str, /) -> str:
prompt = f"[{prompt}> "
while True:
s = input(prompt).strip()
if s == 'ok':
return s


def subcommand(fn: CommandFunc):
global subcommands
subcommands[fn.__name__] = fn
Expand Down Expand Up @@ -138,7 +146,6 @@ def _blurb_help() -> None:


def main() -> None:
global original_dir

args = sys.argv[1:]

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

import blurb._merge
blurb._merge.original_dir = os.getcwd()
try:
original_dir = os.getcwd()
chdir_to_repo_root()

# map keyword arguments to options
Expand Down
119 changes: 119 additions & 0 deletions src/blurb/_merge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import os
import sys
from pathlib import Path

from blurb._cli import require_ok, subcommand
from blurb._versions import glob_versions, printable_version
from blurb.blurb import Blurbs, glob_blurbs, textwrap_body

original_dir: str = os.getcwd()


@subcommand
def merge(output: str | None = None, *, forced: bool = False) -> None:
"""Merge all blurbs together into a single Misc/NEWS file.

Optional output argument specifies where to write to.
Default is <cpython-root>/Misc/NEWS.

If overwriting, blurb merge will prompt you to make sure it's okay.
To force it to overwrite, use -f.
"""
if output:
output = os.path.join(original_dir, output)
else:
output = 'Misc/NEWS'

versions = glob_versions()
if not versions:
sys.exit("You literally don't have ANY blurbs to merge together!")

if os.path.exists(output) and not forced:
print(f'You already have a {output!r} file.')
require_ok('Type ok to overwrite')

write_news(output, versions=versions)


def write_news(output: str, *, versions: list[str]) -> None:
buff = []

def prnt(msg: str = '', /):
buff.append(msg)

prnt("""
+++++++++++
Python News
+++++++++++

""".strip())

for version in versions:
filenames = glob_blurbs(version)

blurbs = Blurbs()
if version == 'next':
for filename in filenames:
if os.path.basename(filename) == 'README.rst':
continue
blurbs.load_next(filename)
if not blurbs:
continue
metadata = blurbs[0][0]
metadata['release date'] = 'XXXX-XX-XX'
else:
assert len(filenames) == 1
blurbs.load(filenames[0])

header = f"What's New in Python {printable_version(version)}?"
prnt()
prnt(header)
prnt('=' * len(header))
prnt()

metadata, body = blurbs[0]
release_date = metadata['release date']

prnt(f'*Release date: {release_date}*')
prnt()

if 'no changes' in metadata:
prnt(body)
prnt()
continue

last_section = None
for metadata, body in blurbs:
section = metadata['section']
if last_section != section:
last_section = section
prnt(section)
prnt('-' * len(section))
prnt()
if metadata.get('gh-issue'):
issue_number = metadata['gh-issue']
if int(issue_number):
body = f'gh-{issue_number}: {body}'
elif metadata.get('bpo'):
issue_number = metadata['bpo']
if int(issue_number):
body = f'bpo-{issue_number}: {body}'

body = f'- {body}'
text = textwrap_body(body, subsequent_indent=' ')
prnt(text)
prnt()
prnt('**(For information about older versions, consult the HISTORY file.)**')

new_contents = '\n'.join(buff)

# Only write in `output` if the contents are different
# This speeds up subsequent Sphinx builds
try:
previous_contents = Path(output).read_text(encoding='utf-8')
except (FileNotFoundError, UnicodeError):
previous_contents = None
if new_contents != previous_contents:
Path(output).write_text(new_contents, encoding='utf-8')
else:
print(output, 'is already up to date')
67 changes: 67 additions & 0 deletions src/blurb/_versions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import glob
import sys

if sys.version_info[:2] >= (3, 11):
from contextlib import chdir
else:
import os

class chdir:
def __init__(self, path: str, /) -> None:
self.path = path

def __enter__(self) -> None:
self.previous_cwd = os.getcwd()
os.chdir(self.path)

def __exit__(self, *args) -> None:
os.chdir(self.previous_cwd)


def glob_versions() -> list[str]:
versions = []
with chdir('Misc/NEWS.d'):
for wildcard in ('2.*.rst', '3.*.rst', 'next'):
versions += [x.partition('.rst')[0] for x in glob.glob(wildcard)]
versions.sort(key=version_key, reverse=True)
return versions


def version_key(element: str, /) -> str:
fields = list(element.split('.'))
if len(fields) == 1:
return element

# in sorted order,
# 3.5.0a1 < 3.5.0b1 < 3.5.0rc1 < 3.5.0
# so for sorting purposes we transform
# "3.5." and "3.5.0" into "3.5.0zz0"
last = fields.pop()
for s in ('a', 'b', 'rc'):
if s in last:
last, stage, stage_version = last.partition(s)
break
else:
stage = 'zz'
stage_version = '0'

fields.append(last)
while len(fields) < 3:
fields.append('0')

fields.extend([stage, stage_version])
fields = [s.rjust(6, '0') for s in fields]

return '.'.join(fields)


def printable_version(version: str, /) -> str:
if version == 'next':
return version
if 'a' in version:
return version.replace('a', ' alpha ')
if 'b' in version:
return version.replace('b', ' beta ')
if 'rc' in version:
return version.replace('rc', ' release candidate ')
return version + ' final'
Loading