mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-11-03 19:17:02 -05: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()
 |