mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-05-31 12:14:15 -04:00
174 lines
6.3 KiB
Python
174 lines
6.3 KiB
Python
#!/usr/bin/env python
|
|
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
|
|
|
from datetime import date
|
|
|
|
|
|
def parse(raw, parse_dates=True):
|
|
entries = []
|
|
current_entry = None
|
|
current_section = 'new features'
|
|
|
|
def normal(linenum, line, stripped_line):
|
|
nonlocal current_entry, current_section
|
|
if not stripped_line:
|
|
return normal
|
|
if stripped_line.startswith('{' '{' '{'):
|
|
parts = line.split()[1:]
|
|
if len(parts) != 2:
|
|
raise ValueError(f'The entry start line is malformed: {line}')
|
|
if current_entry is not None:
|
|
raise ValueError(f'Start of entry while previous entry is still active at line: {linenum}')
|
|
version, draw = parts
|
|
if parse_dates:
|
|
d = date(*map(int, draw.split('-')))
|
|
else:
|
|
d = draw
|
|
current_entry = {'version': version, 'date': d, 'new features': [], 'bug fixes': [], 'improved recipes': [], 'new recipes': []}
|
|
current_section = 'new features'
|
|
return in_entry
|
|
raise ValueError(f'Invalid content at line {linenum}: {line}')
|
|
|
|
def in_entry(linenum, line, stripped_line):
|
|
nonlocal current_section, current_entry
|
|
if stripped_line == '}' '}' '}':
|
|
if current_entry is None:
|
|
raise ValueError(f'Entry terminator without active entry at line: {linenum}')
|
|
entries.append(current_entry)
|
|
current_entry = None
|
|
return normal
|
|
if line.startswith(':: '):
|
|
current_section = line[3:].strip()
|
|
if current_section not in ('new features', 'bug fixes', 'new recipes', 'improved recipes'):
|
|
raise ValueError(f'Unknown section: {current_section}')
|
|
return in_entry
|
|
if line.startswith('-'):
|
|
return start_item(linenum, line, stripped_line)
|
|
if not stripped_line:
|
|
return in_entry
|
|
raise ValueError(f'Invalid content at line {linenum}: {line}')
|
|
|
|
def start_item(linenum, line, stripped_line):
|
|
line = line[1:].lstrip()
|
|
items = current_entry[current_section]
|
|
if current_section == 'improved recipes':
|
|
items.append(line.rstrip())
|
|
return in_entry
|
|
if current_section == 'new recipes':
|
|
idx = line.rfind('by ')
|
|
if idx == -1:
|
|
items.append({'title': line.strip()})
|
|
else:
|
|
items.append({'title': line[:idx].strip(), 'author': line[idx + 3:].strip()})
|
|
return in_entry
|
|
item = {}
|
|
if line.startswith('['):
|
|
idx = line.find(']')
|
|
if idx == -1:
|
|
raise ValueError(f'No closing ] found in line: {linenum}')
|
|
for x in line[1:idx].split():
|
|
if x == 'major':
|
|
item['type'] = x
|
|
continue
|
|
num = int(x)
|
|
item.setdefault('tickets', []).append(num)
|
|
item['title'] = line[idx+1:].strip()
|
|
else:
|
|
item['title'] = line.strip()
|
|
items.append(item)
|
|
return in_item
|
|
|
|
def finalize_item(item):
|
|
if 'description' in item and not item['description']:
|
|
del item['description']
|
|
if 'description' in item:
|
|
item['description'] = item['description'].strip()
|
|
return item
|
|
|
|
def in_item(linenum, line, stripped_line):
|
|
item = current_entry[current_section][-1]
|
|
if line.startswith('::'):
|
|
finalize_item(item)
|
|
return in_entry(linenum, line, stripped_line)
|
|
if line.startswith('-'):
|
|
finalize_item(item)
|
|
return start_item(linenum, line, stripped_line)
|
|
if line.startswith('}' '}' '}'):
|
|
return in_entry(linenum, line, stripped_line)
|
|
if not stripped_line:
|
|
if 'description' not in item:
|
|
item['description'] = ''
|
|
return in_item
|
|
if 'description' in item:
|
|
item['description'] += stripped_line + ' '
|
|
else:
|
|
item['title'] += ' ' + stripped_line
|
|
return in_item
|
|
|
|
state = normal
|
|
for i, line in enumerate(raw.splitlines()):
|
|
if line.startswith('#'):
|
|
continue
|
|
stripped_line = line.strip()
|
|
state = state(i + 1, line, stripped_line)
|
|
return entries
|
|
|
|
|
|
def migrate():
|
|
from yaml import safe_load
|
|
|
|
def output_item(item, lines):
|
|
meta = []
|
|
if item.get('type') == 'major':
|
|
meta.append(item['type'])
|
|
for x in item.get('tickets', ()):
|
|
meta.append(str(x))
|
|
title = item['title']
|
|
if meta:
|
|
meta = ' '.join(meta)
|
|
title = f'[{meta}] {title}'
|
|
lines.append(f'- {title}')
|
|
d = item.get('description')
|
|
if d:
|
|
lines.append(''), lines.append(d)
|
|
lines.append('')
|
|
|
|
for name in ('Changelog.yaml', 'Changelog.old.yaml'):
|
|
entries = safe_load(open(name).read())
|
|
lines = []
|
|
for entry in entries:
|
|
lines.append('')
|
|
lines.append('{' '{' '{'+f' {entry["version"]} {entry["date"]}')
|
|
for w in ('new features', 'bug fixes'):
|
|
nf = entry.get(w)
|
|
if nf:
|
|
lines.append(f':: {w}'), lines.append('')
|
|
for x in nf:
|
|
output_item(x, lines)
|
|
lines.append('')
|
|
nr = entry.get('new recipes')
|
|
if nr:
|
|
lines.append(':: new recipes'), lines.append('')
|
|
for r in nr:
|
|
aut = r.get('author') or r.get('authors')
|
|
title = r['title']
|
|
if title:
|
|
if aut:
|
|
lines.append(f'- {title} by {aut}')
|
|
else:
|
|
lines.append(f'- {title}')
|
|
lines.append('')
|
|
ir = entry.get('improved recipes')
|
|
if ir:
|
|
lines.append(':: improved recipes'), lines.append('')
|
|
for r in ir:
|
|
lines.append(f'- {r}')
|
|
lines.append('')
|
|
with open(name.replace('yaml', 'txt'), 'w') as f:
|
|
f.write('\n'.join(lines))
|
|
lines.append(''), lines.append('}' '}' '}'), lines.append('')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
migrate()
|