You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

118 lines
4.5 KiB

# yaclog-ksp: yet another changelog tool
# Copyright (c) 2021. Andrew Cassidy
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <>.
import pathlib
import re
import click
import yaclog
from yaclog_ksp.cfgnode import ConfigNode
@click.option('--path', envvar='YACLOG_PATH', default='', show_default=True,
type=click.Path(dir_okay=False, writable=True, readable=True),
help='Location of the changelog file.')
@click.option('-o', '--output', 'outpath',
type=click.Path(writable=True, dir_okay=False),
help="Output file to write to. Uses 'GameData/{name}/Versioning/{name}ChangeLog.cfg' by default.")
@click.option('-n', '--name', help="The name of the mod. Derived from the current directory by default.")
def main(path, outpath, name):
""" Converts markdown changelogs to KSP changelog configs."""
if not name:
# try to guess name from current directory
pathname = pathlib.Path.cwd().name.removeprefix('KSP-')
modslug = str.join('', [s.title() for s in re.split(r'[ _-]+', pathname)])
segments = re.findall(r'[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))', modslug)
name = segments[0] + [' ' + s for s in segments[1:]]
modslug = str.join('', [s.title() for s in re.split(r'[ _-]+', name)])
if not outpath:
# default is in GameData/{name}/Versioning/{name}ChangeLog.cfg
outpath = pathlib.Path('GameData', modslug, 'Versioning', modslug + 'ChangeLog.cfg')
log =
node = ConfigNode()
# find metadata table rows
for key, value in re.findall(r'^\|(?P<key>[^\n-]*?)\|(?P<value>[^\n-]*?)\|$', log.preamble, flags=re.MULTILINE):
key = key.strip()
value = value.strip()
if key.strip(':-'):
node.add_value(key, value)
# if modname not in metadata, then add it here
if not node.has_value('modName'):
node.add_value('modName', name)
# iterate through all versions
for version in log.versions:
v_node = node.add_new_node('VERSION')
# add date
v_node.add_value('versionDate', str(
# check for KSP version tag and add it
for tag in version.tags:
if match := re.match(r'KSP (?P<versionKSP>.*)', tag):
v_node.add_value('versionKSP', match['versionKSP'])
# add entries
for section, entries in version.sections.items():
for entry in entries:
bullets = re.findall(r'^[\t ]*[-+*] (.*?)$', entry, flags=re.MULTILINE)
if len(bullets) < 1:
# not a bullet point, but a paragraph. all one string
change = entry.replace('\n', ' ')
subchanges = []
# bullet point, may have sub points
change = bullets[0]
subchanges = bullets[1:]
if section or len(subchanges) > 0:
e_node = v_node.add_new_node('CHANGE')
if section:
# KerbalChangelog only actually cares about the first character,
# so dont bother correcting "Fixed"->"Fix", etc
e_node.add_value('type', section.title())
e_node.add_value('change', change)
for sc in subchanges:
e_node.add_value('subchange', sc)
v_node.add_value('change', change)
with open(outpath, 'w') as fp:
fp.write('// Changelog file generated by yaclog-ksp (\n')
print(f'wrote output to {outpath}')
if __name__ == '__main__':