feat: Better changelogs

This commit is contained in:
Selis
2023-07-17 18:54:57 +02:00
parent b1fc3532ac
commit 77e57e7209
19 changed files with 641 additions and 267 deletions

3
.gitattributes vendored
View File

@@ -11,6 +11,3 @@
# dmi icon merger hook
# needs additional setup, see tools/dmitool/merging.txt
*.dmi merge=merge-dmi
# force changelog merging to use union
html/changelog.html merge=union

View File

@@ -34,7 +34,6 @@ jobs:
"${{ github.event.pull_request.user.login }}" \
"${{ github.event.pull_request.body }}"
python tools/GenerateChangelog/ss13_genchangelog.py \
html/changelog.html \
html/changelogs
- uses: stefanzweifel/git-auto-commit-action@v4
with:

View File

@@ -27,7 +27,10 @@
debug_log = start_log("[log_path]-debug.log")
//VOREStation Edit End
changelog_hash = md5('html/changelog.html') //used for telling if the changelog has changed recently
// CHOMPedit Start - Better Changelogs
var/latest_changelog = file("/html/changelogs/archive/" + time2text(world.timeofday, "YYYY-MM") + ".yml")
changelog_hash = fexists(latest_changelog) ? md5(latest_changelog) : 0 //for telling if the changelog has changed recently
// CHOMPedit End
if(byond_version < RECOMMENDED_VERSION)
to_world_log("Your server's byond version does not meet the recommended requirements for this server. Please update BYOND")

View File

@@ -124,6 +124,7 @@
"no_image32.png" = 'html/images/no_image32.png',
)
/* CHOMPedit Start - Better Changelogs
/datum/asset/simple/changelog
assets = list(
"88x31.png" = 'html/88x31.png',
@@ -145,6 +146,7 @@
"changelog.js" = 'html/changelog.js',
"changelog.html" = 'html/changelog.html'
)
*/ // CHOMPedit end
// /datum/asset/group/goonchat
// children = list(

View File

@@ -433,11 +433,20 @@
/client/verb/changes()
set name = "Changelog"
set category = "OOC"
src << browse('html/changelog.html', "window=changes;size=675x650")
// CHOMPedit Start - Better Changelog
//src << browse('html/changelog.html', "window=changes;size=675x650")
//return
if(!GLOB.changelog_tgui)
GLOB.changelog_tgui = new /datum/changelog()
GLOB.changelog_tgui.tgui_interact(usr)
/*
if(prefs.lastchangelog != changelog_hash)
prefs.lastchangelog = changelog_hash
SScharacter_setup.queue_preferences_save(prefs)
winset(src, "rpane.changelog", "background-color=none;font-style=;")
*/ // CHOMPedit End
/mob/verb/observe()
set name = "Observe"

View File

@@ -0,0 +1,3 @@
1970-01-01:
ExampleUser:
- tweak: Tweaked something in my example PR!

View File

@@ -1,37 +1,60 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# wip (For works in progress)
# tweak
# soundadd
# sounddel
# rscadd (general adding of nice things)
# rscdel (general deleting of nice things)
# imageadd
# imagedel
# maptweak
# spellcheck (typo fixes)
# experiment
#################################
# Your name.
author: N3X15
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
changes:
- rscadd: "Added a changelog editing system that should cause fewer conflicts and more accurate timestamps."
- rscdel: "Killed innocent kittens."
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# - (fixes bugs)
# wip
# - (work in progress)
# qol
# - (quality of life)
# soundadd
# - (adds a sound)
# sounddel
# - (removes a sound)
# rscadd
# - (adds a feature)
# rscdel
# - (removes a feature)
# imageadd
# - (adds an image or sprite)
# imagedel
# - (removes an image or sprite)
# maptweak
# spellcheck
# - (fixes spelling or grammar)
# experiment
# - (experimental change)
# balance
# - (balance changes)
# code_imp
# - (misc internal code change)
# refactor
# - (refactors code)
# config
# - (makes a change to the config files)
# admin
# - (makes changes to administrator tools)
# server
# - (miscellaneous changes to server)
#################################
# Your name.
author: "
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
changes:
- rscadd: "Added a changelog editing system that should cause fewer conflicts and more accurate timestamps."
- rscdel: "Killed innocent kittens."

View File

@@ -0,0 +1 @@
GLOBAL_DATUM(changelog_tgui, /datum/changelog)

View File

@@ -0,0 +1,32 @@
/datum/changelog
var/static/list/changelog_items = list()
/datum/changelog/tgui_state()
return GLOB.tgui_always_state
/datum/changelog/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
ui = new(user, src, "Changelog")
ui.open()
/datum/changelog/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
. = ..()
if(.)
return
if(action == "get_month")
var/datum/asset/changelog_item/changelog_item = changelog_items[params["date"]]
if (!changelog_item)
changelog_item = new /datum/asset/changelog_item(params["date"])
changelog_items[params["date"]] = changelog_item
return ui.send_asset(changelog_item)
/datum/changelog/tgui_static_data()
var/list/data = list( "dates" = list() )
var/regex/ymlRegex = regex(@"\.yml", "g")
for(var/archive_file in sortList(flist("html/changelogs/archive/")))
var/archive_date = ymlRegex.Replace(archive_file, "")
data["dates"] = list(archive_date) + data["dates"]
return data

View File

@@ -0,0 +1,17 @@
/datum/asset/changelog_item
_abstract = /datum/asset/changelog_item
var/item_filename
/datum/asset/changelog_item/New(date)
item_filename = sanitize_filename("[date].yml")
register_asset(item_filename, file("html/changelogs/archive/" + item_filename))
/datum/asset/changelog_item/send(client)
if (!item_filename)
return
. = send_asset(client, item_filename)
/datum/asset/changelog_item/get_url_mappings()
if (!item_filename)
return
. = list("[item_filename]" = get_asset_url(item_filename))

View File

@@ -0,0 +1,315 @@
import { classes } from 'common/react';
import { useBackend } from '../backend';
import { Component, Fragment } from 'inferno';
import { Box, Button, Dropdown, Icon, Section, Stack, Table } from '../components';
import { Window } from '../layouts';
import { resolveAsset } from '../assets';
import dateformat from 'dateformat';
import yaml from 'js-yaml';
const icons = {
bugfix: { icon: 'bug', color: 'green' },
wip: { icon: 'hammer', color: 'orange' },
qol: { icon: 'hand-holding-heart', color: 'green' },
soundadd: { icon: 'tg-sound-plus', color: 'green' },
sounddel: { icon: 'tg-sound-minus', color: 'red' },
add: { icon: 'check-circle', color: 'green' },
expansion: { icon: 'check-circle', color: 'green' },
rscadd: { icon: 'check-circle', color: 'green' },
rscdel: { icon: 'times-circle', color: 'red' },
imageadd: { icon: 'tg-image-plus', color: 'green' },
imagedel: { icon: 'tg-image-minus', color: 'red' },
spellcheck: { icon: 'spell-check', color: 'green' },
experiment: { icon: 'radiation', color: 'yellow' },
balance: { icon: 'balance-scale-right', color: 'yellow' },
code_imp: { icon: 'code', color: 'green' },
refactor: { icon: 'tools', color: 'green' },
config: { icon: 'cogs', color: 'purple' },
admin: { icon: 'user-shield', color: 'purple' },
server: { icon: 'server', color: 'purple' },
tgs: { icon: 'toolbox', color: 'purple' },
tweak: { icon: 'wrench', color: 'green' },
unknown: { icon: 'info-circle', color: 'label' },
};
export class Changelog extends Component {
constructor() {
super();
this.state = {
data: 'Loading changelog data...',
selectedDate: '',
selectedIndex: 0,
};
this.dateChoices = [];
}
setData(data) {
this.setState({ data });
}
setSelectedDate(selectedDate) {
this.setState({ selectedDate });
}
setSelectedIndex(selectedIndex) {
this.setState({ selectedIndex });
}
getData = (date, attemptNumber = 1) => {
const { act } = useBackend(this.context);
const self = this;
const maxAttempts = 6;
if (attemptNumber > maxAttempts) {
return this.setData(
'Failed to load data after ' + maxAttempts + ' attempts'
);
}
act('get_month', { date });
fetch(resolveAsset(date + '.yml')).then(async (changelogData) => {
const result = await changelogData.text();
const errorRegex = /^Cannot find/;
if (errorRegex.test(result)) {
const timeout = 50 + attemptNumber * 50;
self.setData('Loading changelog data' + '.'.repeat(attemptNumber + 3));
setTimeout(() => {
self.getData(date, attemptNumber + 1);
}, timeout);
} else {
self.setData(yaml.load(result, { schema: yaml.CORE_SCHEMA }));
}
});
};
componentDidMount() {
const {
data: { dates = [] },
} = useBackend(this.context);
if (dates) {
dates.forEach((date) =>
this.dateChoices.push(dateformat(date, 'mmmm yyyy', true))
);
this.setSelectedDate(this.dateChoices[0]);
this.getData(dates[0]);
}
}
render() {
const { data, selectedDate, selectedIndex } = this.state;
const {
data: { dates },
} = useBackend(this.context);
const { dateChoices } = this;
const dateDropdown = dateChoices.length > 0 && (
<Stack mb={1}>
<Stack.Item>
<Button
className="Changelog__Button"
disabled={selectedIndex === 0}
icon={'chevron-left'}
onClick={() => {
const index = selectedIndex - 1;
this.setData('Loading changelog data...');
this.setSelectedIndex(index);
this.setSelectedDate(dateChoices[index]);
window.scrollTo(
0,
document.body.scrollHeight ||
document.documentElement.scrollHeight
);
return this.getData(dates[index]);
}}
/>
</Stack.Item>
<Stack.Item>
<Dropdown
displayText={selectedDate}
options={dateChoices}
onSelected={(value) => {
const index = dateChoices.indexOf(value);
this.setData('Loading changelog data...');
this.setSelectedIndex(index);
this.setSelectedDate(value);
window.scrollTo(
0,
document.body.scrollHeight ||
document.documentElement.scrollHeight
);
return this.getData(dates[index]);
}}
selected={selectedDate}
width={'150px'}
/>
</Stack.Item>
<Stack.Item>
<Button
className="Changelog__Button"
disabled={selectedIndex === dateChoices.length - 1}
icon={'chevron-right'}
onClick={() => {
const index = selectedIndex + 1;
this.setData('Loading changelog data...');
this.setSelectedIndex(index);
this.setSelectedDate(dateChoices[index]);
window.scrollTo(
0,
document.body.scrollHeight ||
document.documentElement.scrollHeight
);
return this.getData(dates[index]);
}}
/>
</Stack.Item>
</Stack>
);
const header = (
<Section>
<h1>CHOMPStation Changelist</h1>
<p>
{'The GitHub repository can be found '}
<a href="https://github.com/CHOMPStation2/CHOMPStation2">here</a>
{', recent GitHub contributors can be found '}
<a href="https://github.com/CHOMPStation2/CHOMPStation2/pulse/monthly">
here
</a>
.
</p>
<p>
{'Visit our wiki '}
<a href="https://wiki.vore-station.net/Main_Page">here</a>
{', check out our discord server '}
<a href="https://discord.gg/BaZnwYpAzc">here</a>.
</p>
{dateDropdown}
</Section>
);
const footer = (
<Section>
{dateDropdown}
<h3>CHOMPStation License</h3>
<p>
{'All code after '}
<a
href={
'https://github.com/CHOMPStation2/CHOMPStation2/commit/' +
'333c566b88108de218d882840e61928a9b759d8f'
}>
commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at
4:38 PM PST
</a>
{' is licensed under '}
<a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU AGPL v3</a>
{'. All code before that commit is licensed under '}
<a href="https://www.gnu.org/licenses/gpl-3.0.html">GNU GPL v3</a>
{', including tools unless their readme specifies otherwise. See '}
<a href="https://github.com/CHOMPStation2/CHOMPStation2/blob/master/LICENSE">
LICENSE
</a>
{' and '}
<a href="https://github.com/CHOMPStation2/CHOMPStation2/blob/master/LICENSE-GPL3.txt">
GPLv3.txt
</a>
{' for more details.'}
</p>
<p>
The TGS DMAPI API is licensed as a subproject under the MIT license.
{' See the footer of '}
<a
href={
'https://github.com/CHOMPStation2/CHOMPStation2/blob/master' +
'/code/__DEFINES/tgs.dm'
}>
code/__DEFINES/tgs.dm
</a>
{' and '}
<a
href={
'https://github.com/CHOMPStation2/CHOMPStation2/blob/master' +
'/code/modules/tgs/LICENSE'
}>
code/modules/tgs/LICENSE
</a>
{' for the MIT license.'}
</p>
<p>
{'All assets including icons and sound are under a '}
<a href="https://creativecommons.org/licenses/by-sa/3.0/">
Creative Commons 3.0 BY-SA license
</a>
{' unless otherwise indicated.'}
</p>
</Section>
);
const changes =
typeof data === 'object' &&
Object.keys(data).length > 0 &&
Object.entries(data)
.reverse()
.map(([date, authors]) => (
<Section key={date} title={dateformat(date, 'd mmmm yyyy', true)}>
<Box ml={3}>
{Object.entries(authors).map(([name, changes]) => (
<Fragment key={name}>
<h4>{name} changed:</h4>
<Box ml={3}>
<Table>
{changes.map((change) => {
const changeType = Object.keys(change)[0];
return (
<Table.Row key={changeType + change[changeType]}>
<Table.Cell
className={classes([
'Changelog__Cell',
'Changelog__Cell--Icon',
])}>
<Icon
color={
icons[changeType]
? icons[changeType].color
: icons['unknown'].color
}
name={
icons[changeType]
? icons[changeType].icon
: icons['unknown'].icon
}
/>
</Table.Cell>
<Table.Cell className="Changelog__Cell">
{change[changeType]}
</Table.Cell>
</Table.Row>
);
})}
</Table>
</Box>
</Fragment>
))}
</Box>
</Section>
));
return (
<Window title="Changelog" width={675} height={650}>
<Window.Content scrollable>
{header}
{changes}
{typeof data === 'string' && <p>{data}</p>}
{footer}
</Window.Content>
</Window>
);
}
}

View File

@@ -6,7 +6,7 @@
"@popperjs/core": "^2.11.5",
"@types/marked": "^4.0.8",
"common": "workspace:*",
"dateformat": "^5.0.3",
"dateformat": "^4.5.1",
"dompurify": "^2.3.8",
"highlight.js": "^11.5.1",
"inferno": "^7.4.11",
@@ -15,5 +15,9 @@
"marked": "^4.0.16",
"tgui-dev-server": "workspace:*",
"tgui-polyfill": "workspace:*"
},
"devDependencies": {
"@types/dateformat": "^5.0.0",
"@types/js-yaml": "^4.0.5"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -2112,6 +2112,13 @@ __metadata:
languageName: node
linkType: hard
"@types/dateformat@npm:^5.0.0":
version: 5.0.0
resolution: "@types/dateformat@npm:5.0.0"
checksum: fee3fff637675fd55b59fff6a0507a120ae212a0c7ba823c00a45473da617a7d466e6d7d40d2b4b88af9df3f4be3ed80a51e222ea346c7738de688aa2c6747f1
languageName: node
linkType: hard
"@types/eslint-scope@npm:^3.7.3":
version: 3.7.4
resolution: "@types/eslint-scope@npm:3.7.4"
@@ -2183,6 +2190,13 @@ __metadata:
languageName: node
linkType: hard
"@types/js-yaml@npm:^4.0.5":
version: 4.0.5
resolution: "@types/js-yaml@npm:4.0.5"
checksum: 7dcac8c50fec31643cc9d6444b5503239a861414cdfaa7ae9a38bc22597c4d850c4b8cec3d82d73b3fbca408348ce223b0408d598b32e094470dfffc6d486b4d
languageName: node
linkType: hard
"@types/jsdom@npm:^16.2.14, @types/jsdom@npm:^16.2.4":
version: 16.2.15
resolution: "@types/jsdom@npm:16.2.15"
@@ -3824,6 +3838,13 @@ __metadata:
languageName: node
linkType: hard
"dateformat@npm:^4.5.1":
version: 4.6.3
resolution: "dateformat@npm:4.6.3"
checksum: c3aa0617c0a5b30595122bc8d1bee6276a9221e4d392087b41cbbdf175d9662ae0e50d0d6dcdf45caeac5153c4b5b0844265f8cd2b2245451e3da19e39e3b65d
languageName: node
linkType: hard
"dateformat@npm:^5.0.3":
version: 5.0.3
resolution: "dateformat@npm:5.0.3"
@@ -9409,9 +9430,11 @@ __metadata:
resolution: "tgui_ch@workspace:packages/tgui_ch"
dependencies:
"@popperjs/core": ^2.11.5
"@types/dateformat": ^5.0.0
"@types/js-yaml": ^4.0.5
"@types/marked": ^4.0.8
common: "workspace:*"
dateformat: ^5.0.3
dateformat: ^4.5.1
dompurify: ^2.3.8
highlight.js: ^11.5.1
inferno: ^7.4.11

View File

@@ -1,4 +1,4 @@
@echo off
rem Cheridan asked for this. - N3X
call python ss13_genchangelog.py ../../html/changelog.html ../../html/changelogs
pause
@echo off
rem Cheridan asked for this. - N3X
call python ss13_genchangelog.py ../../html/changelogs
pause

View File

@@ -29,9 +29,7 @@ validPrefixes = {
"fixes": 'bugfix',
"bugfix": 'bugfix',
"wip": 'wip',
"tweak": 'tweak',
"tweaks": 'tweak',
"rsctweak": 'tweak',
"qol": 'qol',
"soundadd": 'soundadd',
"sounddel": 'sounddel',
"add": 'rscadd',
@@ -51,7 +49,13 @@ validPrefixes = {
"spellcheck": 'spellcheck',
"experimental": 'experiment',
"experiments": 'experiment',
"experiment": 'experiment'
"experiment": 'experiment',
"balance": 'balance',
"code_imp": 'code_imp',
"refactor": 'refactor',
"config": 'config',
"admin": 'admin',
"server": 'server',
}
incltag = False

View File

@@ -1,210 +1,149 @@
'''
Usage:
$ python ss13_genchangelog.py [--dry-run] html/changelog.html html/changelogs/
ss13_genchangelog.py - Generate changelog from YAML.
Copyright 2013 Rob "N3X15" Nelson <nexis@7chan.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
'''
from __future__ import print_function
import yaml, os, glob, sys, re, time, argparse
from datetime import datetime, date
from time import time
today = date.today()
dateformat = "%d %B %Y"
opt = argparse.ArgumentParser()
opt.add_argument('-d', '--dry-run', dest='dryRun', default=False, action='store_true', help='Only parse changelogs and, if needed, the targetFile. (A .dry_changelog.yml will be output for debugging purposes.)')
opt.add_argument('targetFile', help='The HTML changelog we wish to update.')
opt.add_argument('ymlDir', help='The directory of YAML changelogs we will use.')
args = opt.parse_args()
all_changelog_entries = {}
validPrefixes = [
'bugfix',
'wip',
'tweak',
'soundadd',
'sounddel',
'rscdel',
'rscadd',
'imageadd',
'imagedel',
'maptweak',
'spellcheck',
'experiment'
]
def dictToTuples(inp):
return [(k, v) for k, v in inp.items()]
changelog_cache = os.path.join(args.ymlDir, '.all_changelog.yml')
failed_cache_read = True
if os.path.isfile(changelog_cache):
try:
with open(changelog_cache) as f:
(_, all_changelog_entries) = yaml.load_all(f)
failed_cache_read = False
# Convert old timestamps to newer format.
new_entries = {}
for _date in all_changelog_entries.keys():
ty = type(_date).__name__
# print(ty)
if ty in ['str', 'unicode']:
temp_data = all_changelog_entries[_date]
_date = datetime.strptime(_date, dateformat).date()
new_entries[_date] = temp_data
else:
new_entries[_date] = all_changelog_entries[_date]
all_changelog_entries = new_entries
except Exception as e:
print("Failed to read cache:")
print(e, file=sys.stderr)
if args.dryRun:
changelog_cache = os.path.join(args.ymlDir, '.dry_changelog.yml')
if failed_cache_read and os.path.isfile(args.targetFile):
from bs4 import BeautifulSoup
from bs4.element import NavigableString
print(' Generating cache...')
with open(args.targetFile, 'r') as f:
soup = BeautifulSoup(f)
for e in soup.find_all('div', {'class':'commit'}):
entry = {}
date = datetime.strptime(e.h2.string.strip(), dateformat).date() # key
for authorT in e.find_all('h3', {'class':'author'}):
author = authorT.string
# Strip suffix
if author.endswith('updated:'):
author = author[:-8]
author = author.strip()
# Find <ul>
ulT = authorT.next_sibling
while(ulT.name != 'ul'):
ulT = ulT.next_sibling
changes = []
for changeT in ulT.children:
if changeT.name != 'li': continue
val = changeT.decode_contents(formatter="html")
newdat = {changeT['class'][0] + '': val + ''}
if newdat not in changes:
changes += [newdat]
if len(changes) > 0:
entry[author] = changes
if date in all_changelog_entries:
all_changelog_entries[date].update(entry)
else:
all_changelog_entries[date] = entry
del_after = []
errors = False
print('Reading changelogs...')
for fileName in glob.glob(os.path.join(args.ymlDir, "*.yml")):
name, ext = os.path.splitext(os.path.basename(fileName))
if name.startswith('.'): continue
if name == 'example': continue
fileName = os.path.abspath(fileName)
print(' Reading {}...'.format(fileName))
cl = {}
with open(fileName, 'r') as f:
cl = yaml.load(f)
f.close()
if today not in all_changelog_entries:
all_changelog_entries[today] = {}
author_entries = all_changelog_entries[today].get(cl['author'], [])
if len(cl['changes']):
new = 0
for change in cl['changes']:
if change not in author_entries:
(change_type, _) = dictToTuples(change)[0]
if change_type not in validPrefixes:
errors = True
print(' {0}: Invalid prefix {1}'.format(fileName, change_type), file=sys.stderr)
author_entries += [change]
new += 1
all_changelog_entries[today][cl['author']] = author_entries
if new > 0:
print(' Added {0} new changelog entries.'.format(new))
if cl.get('delete-after', False):
if os.path.isfile(fileName):
if args.dryRun:
print(' Would delete {0} (delete-after set)...'.format(fileName))
else:
del_after += [fileName]
if args.dryRun: continue
cl['changes'] = []
with open(fileName, 'w') as f:
yaml.dump(cl, f, default_flow_style=False)
targetDir = os.path.dirname(args.targetFile)
with open(args.targetFile.replace('.htm', '.dry.htm') if args.dryRun else args.targetFile, 'w') as changelog:
with open(os.path.join(targetDir, 'templates', 'header.html'), 'r') as h:
for line in h:
changelog.write(line)
for _date in reversed(sorted(all_changelog_entries.keys())):
entry_htm = '\n'
entry_htm += '\t\t\t<h2 class="date">{date}</h2>\n'.format(date=_date.strftime(dateformat))
write_entry = False
for author in sorted(all_changelog_entries[_date].keys()):
if len(all_changelog_entries[_date]) == 0: continue
author_htm = '\t\t\t<h3 class="author">{author} updated:</h3>\n'.format(author=author)
author_htm += '\t\t\t<ul class="changes bgimages16">\n'
changes_added = []
for (css_class, change) in (dictToTuples(e)[0] for e in all_changelog_entries[_date][author]):
if change in changes_added: continue
write_entry = True
changes_added += [change]
author_htm += '\t\t\t\t<li class="{css_class}">{change}</li>\n'.format(css_class=css_class, change=change.strip())
author_htm += '\t\t\t</ul>\n'
if len(changes_added) > 0:
entry_htm += author_htm
if write_entry:
changelog.write(entry_htm)
with open(os.path.join(targetDir, 'templates', 'footer.html'), 'r') as h:
for line in h:
changelog.write(line)
with open(changelog_cache, 'w') as f:
cache_head = 'DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.'
yaml.dump_all([cache_head, all_changelog_entries], f, default_flow_style=False)
if len(del_after):
print('Cleaning up...')
for fileName in del_after:
if os.path.isfile(fileName):
print(' Deleting {0} (delete-after set)...'.format(fileName))
os.remove(fileName)
if errors:
sys.exit(1)
'''
Usage:
$ python ss13_genchangelog.py html/changelogs/
ss13_genchangelog.py - Generate changelog from YAML.
Copyright 2013 Rob "N3X15" Nelson <nexis@7chan.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
'''
from __future__ import print_function
import yaml, os, glob, sys, re, time, argparse
from datetime import datetime, date
from time import time
today = date.today()
fileDateFormat = "%Y-%m"
opt = argparse.ArgumentParser()
opt.add_argument('ymlDir', help='The directory of YAML changelogs we will use.')
args = opt.parse_args()
archiveDir = os.path.join(args.ymlDir, 'archive')
all_changelog_entries = {}
# Do not change the order, add to the bottom of the array if necessary
validPrefixes = [
'bugfix',
'wip',
'qol',
'soundadd',
'sounddel',
'rscadd',
'rscdel',
'imageadd',
'imagedel',
'maptweak',
'spellcheck',
'experiment',
'balance',
'code_imp',
'refactor',
'config',
'admin',
'server'
]
def dictToTuples(inp):
return [(k, v) for k, v in inp.items()]
old_changelog_cache = os.path.join(args.ymlDir, '.all_changelog.yml')
failed_cache_read = True
if os.path.isfile(old_changelog_cache):
try:
print('Reading old changelog cache...')
data = {}
with open(old_changelog_cache,encoding='utf-8') as f:
(_, all_changelog_entries) = yaml.load_all(f, Loader=yaml.SafeLoader)
# Categorize changes by year and month
for _date in all_changelog_entries.keys():
ty = type(_date).__name__
formattedDate = _date.strftime(fileDateFormat)
if not formattedDate in data:
data[formattedDate] = {}
data[formattedDate][_date] = all_changelog_entries[_date]
# Write files with changes by year and month
for month in data.keys():
print("Writing " + month + ".yml...")
if not os.path.exists(archiveDir):
os.makedirs(archiveDir)
currentFile = os.path.join(archiveDir, month + '.yml')
with open(currentFile, 'w', encoding='utf-8') as f:
yaml.dump(data[month], f, default_flow_style=False)
# Remove the old changelog cache, as we won't use it anymore
print("Removing old changelog cache...")
os.remove(old_changelog_cache)
old_changelog_html = os.path.join(args.ymlDir, '..', 'changelog.html')
if os.path.isfile(old_changelog_html):
print("Removing old changelog html...")
os.remove(old_changelog_html)
except Exception as e:
print("Failed to read old changelog cache:")
print(e, file=sys.stderr)
errors = False
print('Reading changelogs...')
for fileName in glob.glob(os.path.join(args.ymlDir, "*.yml")):
name, ext = os.path.splitext(os.path.basename(fileName))
if name.startswith('.'): continue
if name == 'example': continue
fileName = os.path.abspath(fileName)
formattedDate = today.strftime(fileDateFormat)
monthFile = os.path.join(archiveDir, formattedDate + '.yml')
print(' Reading {}...'.format(fileName))
cl = {}
with open(fileName, 'r') as f:
cl = yaml.load(f, Loader=yaml.SafeLoader)
currentEntries = {}
if os.path.exists(monthFile):
with open(monthFile,'r',encoding='utf-8') as f:
currentEntries = yaml.load(f, Loader=yaml.SafeLoader)
if today not in currentEntries:
currentEntries[today] = {}
author_entries = currentEntries[today].get(cl['author'], [])
if len(cl['changes']):
new = 0
for change in cl['changes']:
if change not in author_entries:
(change_type, _) = dictToTuples(change)[0]
if change_type not in validPrefixes:
errors = True
print(' {0}: Invalid prefix {1}'.format(fileName, change_type), file=sys.stderr)
author_entries += [change]
new += 1
currentEntries[today][cl['author']] = author_entries
if new > 0:
print(' Added {0} new changelog entries.'.format(new))
if cl.get('delete-after', False):
if os.path.isfile(fileName):
print(' Deleting {0} (delete-after set)...'.format(fileName))
os.remove(fileName)
with open(monthFile, 'w', encoding='utf-8') as f:
yaml.dump(currentEntries, f, default_flow_style=False)
if errors:
sys.exit(1)

View File

@@ -47,7 +47,7 @@ if [ $retVal -ne 0 ]; then
fi
#Checking for a change to html/changelogs/example.yml
md5sum -c - <<< "88490b460c26947f5ec1ab1bb9fa9f17 *html/changelogs/example.yml"
md5sum -c - <<< "0c56937110d88f750a32d9075ddaab8b *html/changelogs/example.yml"
retVal=$?
if [ $retVal -ne 0 ]; then
echo -e "${RED}Do not modify the example.yml changelog file.${NC}"
@@ -71,4 +71,4 @@ if [ $retVal -ne 0 ]; then
fi
# Quit with our status code
exit $FAILED
exit $FAILED

View File

@@ -4513,11 +4513,11 @@
#include "code\ZAS\Zone.dm"
#include "interface\interface.dm"
#include "interface\skin.dmf"
#include "maps\runtime_station.dm"
#include "maps\atoll\atoll_decals.dm"
#include "maps\atoll\atoll_objs.dm"
#include "maps\atoll\atoll_turfs.dm"
#include "maps\gateway_archive_vr\blackmarketpackers.dm"
#include "maps\southern_cross\southern_cross.dm"
#include "maps\southern_cross\items\encryptionkey_sc.dm"
#include "maps\southern_cross\items\headset_sc.dm"
#include "maps\southern_cross\items\clothing\sc_accessory.dm"
@@ -4538,6 +4538,7 @@
#include "maps\~map_system\maps.dm"
#include "modular_chomp\code\coalesce_ch.dm"
#include "modular_chomp\code\global.dm"
#include "modular_chomp\code\_global_vars\tgui.dm"
#include "modular_chomp\code\_HELPERS\icons\flatten.dm"
#include "modular_chomp\code\_HELPERS\type2type\color.dm"
#include "modular_chomp\code\_onclick\hud\alert.dm"
@@ -4546,6 +4547,7 @@
#include "modular_chomp\code\datums\autolathe\engineering_ch.dm"
#include "modular_chomp\code\datums\autolathe\general_ch.dm"
#include "modular_chomp\code\datums\browser\color_matrix_picker.dm"
#include "modular_chomp\code\datums\changelog\changelog.dm"
#include "modular_chomp\code\datums\components\gargoyle.dm"
#include "modular_chomp\code\datums\interfaces\appearance.dm"
#include "modular_chomp\code\datums\outfits\jobs\noncrew.dm"
@@ -4582,6 +4584,7 @@
#include "modular_chomp\code\matrices\color_matrix.dm"
#include "modular_chomp\code\modules\admin\functions\modify_traits.dm"
#include "modular_chomp\code\modules\artifice\deadringer.dm"
#include "modular_chomp\code\modules\asset_cache\asset_list.dm"
#include "modular_chomp\code\modules\client\preferences.dm"
#include "modular_chomp\code\modules\client\preferences_spawnpoints.dm"
#include "modular_chomp\code\modules\client\preference_setup\general\03_body.dm"