updates .vscode/, .github/, .editorconfig, sneks

This commit is contained in:
spookerton
2022-04-27 00:21:53 +01:00
parent 23c189eb0c
commit a828523451
12 changed files with 632 additions and 563 deletions
+22
View File
@@ -0,0 +1,22 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{dme,dmf,dmm,dm}]
end_of_line = crlf
indent_style = tab
[*.md]
trim_trailing_whitespace = false
[*.py]
indent_style = space
indent_size = 4
[*.yml]
indent_style = space
indent_size = 2
+7
View File
@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: "daily"
+37 -39
View File
@@ -1,43 +1,41 @@
name: Autochangelog
on:
pull_request_target:
types: closed
branches:
- master
on:
pull_request_target:
types: closed
branches:
- master
env:
BASENAME: "polaris"
BASENAME: "polaris"
jobs:
autochangelog:
name: Autochangelog
runs-on: ubuntu-16.04
if: github.event.pull_request.merged == true
steps:
- uses: /actions/checkout@v2
with:
ref: master
- name: Update repository to master
run: git pull "origin" master
- name: Ensure +x on CI directory
run: |
chmod -R +x ./tools/ci
- uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Generate Changelog
run: |
pip install pyyaml
python tools/GenerateChangelog/ss13_autochangelog.py \
html/changelogs \
${{ github.event.pull_request.number }} \
"${{ 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:
commit_message: Automatic changelog generation for ${{ github.events.pull_request.number }}
branch: ${{ github.events.pull_request.base }}
commit_user_name: Autochangelog Bot
autochangelog:
name: Autochangelog
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- uses: /actions/checkout@v2
with:
ref: master
- name: Update repository to master
run: git pull "origin" master
- name: Ensure +x on CI directory
run: |
chmod -R +x ./tools/ci
- name: Generate Changelog
run: |
pip install pyyaml
python tools/GenerateChangelog/ss13_autochangelog.py \
html/changelogs \
${{ github.event.pull_request.number }} \
"${{ 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:
commit_message: Automatic changelog generation for ${{ github.events.pull_request.number }}
branch: ${{ github.events.pull_request.base }}
commit_user_name: Autochangelog Bot
+7 -7
View File
@@ -10,7 +10,7 @@ env:
jobs:
file_tests:
name: Run Linters
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Ensure +x on CI directory
@@ -26,7 +26,7 @@ jobs:
dreamchecker:
name: DreamChecker
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -36,25 +36,25 @@ jobs:
path: ~/SpacemanDMM
key: ${{ runner.os }}-dreamchecker-${{ hashFiles('dependencies.sh')}}
restore-keys: ${{ runner.os }}-dreamchecker
- name: Install Dependencies
run: |
tools/ci/install_spaceman_dmm.sh dreamchecker
- name: Run Linter
id: linter
run: |
~/dreamchecker > ${GITHUB_WORKSPACE}/output-annotations.txt 2>&1
- name: Annotate Linter
uses: yogstation13/DreamAnnotate@v1
if: always()
with:
with:
outputFile: output-annotations.txt
unit_tests:
name: Integration Tests
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Ensure +x on CI directory
+1 -1
View File
@@ -8,4 +8,4 @@ jobs:
steps:
- uses: yogevbd/enforce-label-action@2.1.0
with:
BANNED_LABELS: "Input: Staff,Input: Author,Needs Announcement,Needs Changelog,Needs Documentation,Stale"
BANNED_LABELS: "Input: Staff,Input: Author,Needs Announcement,Needs Changelog,Needs Documentation,Stale"
+8
View File
@@ -0,0 +1,8 @@
{
"recommendations": [
"gbasood.byond-dm-language-support",
"platymuus.dm-langclient",
"EditorConfig.EditorConfig",
"eamodio.gitlens"
]
}
+43 -9
View File
@@ -1,11 +1,45 @@
{
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"editor.tabSize": 4,
"files.eol": "\r\n",
"gitblame.commitUrl": "https://github.com/polarisss13/polaris/commit/${hash}",
"gitlens.advanced.blame.customArguments": [
"--ignore-revs-file",
".git-blame-ignore-revs"
]
"files.eol": "\n",
"files.encoding": "utf8",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"files.associations": {
"*.{dme,dmf,dmm,dm}": "dm",
},
"[dm]": {
"files.eol": "\r\n",
"editor.detectIndentation": false,
"editor.insertSpaces": false
},
"[markdown]": {
"files.trimTrailingWhitespace": false
},
"[python]": {
"editor.detectIndentation": false,
"editor.insertSpaces": true,
"editor.tabSize": 4
},
"[yaml]": {
"editor.detectIndentation": false,
"editor.insertSpaces": true,
"editor.tabSize": 2
},
"workbench.editorAssociations": {
"*.dmi": "imagePreview.previewEditor"
},
"debug.onTaskErrors": "abort",
"dreammaker.objectTreePane": true,
"dreammaker.autoUpdate": true,
"dreammaker.tickOnCreate": true,
"gitlens.advanced.blame.customArguments": [
"--ignore-revs-file", ".git-blame-ignore-revs"
]
}
+67 -67
View File
@@ -26,32 +26,32 @@ all_changelog_entries = {}
validPrefixes = {
"fix": 'bugfix',
"fixes": 'bugfix',
"bugfix": 'bugfix',
"fixes": 'bugfix',
"bugfix": 'bugfix',
"wip": 'wip',
"tweak": 'tweak',
"tweaks": 'tweak',
"rsctweak": 'tweak',
"tweaks": 'tweak',
"rsctweak": 'tweak',
"soundadd": 'soundadd',
"sounddel": 'sounddel',
"add": 'rscadd',
"adds": 'rscadd',
"rscadd": 'rscadd',
"rscadd": 'rscadd',
"del": 'rscdel',
"dels": 'rscdel',
"delete": 'rscdel',
"deletes": 'rscdel',
"rscdeldel": 'rscdel',
"imageadd": 'imageadd',
"dels": 'rscdel',
"delete": 'rscdel',
"deletes": 'rscdel',
"rscdeldel": 'rscdel',
"imageadd": 'imageadd',
"imagedel": 'imagedel',
"maptweak": 'maptweak',
"remap": 'maptweak',
"remaps": 'maptweak',
"remap": 'maptweak',
"remaps": 'maptweak',
"typo": 'spellcheck',
"spellcheck": 'spellcheck',
"spellcheck": 'spellcheck',
"experimental": 'experiment',
"experiments": 'experiment',
"experiment": 'experiment'
"experiments": 'experiment',
"experiment": 'experiment'
}
incltag = False
@@ -62,60 +62,60 @@ new = 0
# Parse PR body for changelog entries
print('Reading changelogs...')
for line in args.pr_body.splitlines():
print(f"Checking line '{line}'")
if line[:1] == "🆑": # Find the start of the changelog
print("Found opening :cl: tag")
if incltag == True: # If we're already reading logs, skip
continue
incltag = True
# Fetch the author name
author = line[1:]
author.strip()
if not len(author):
author = args.pr_author
if author not in new_logs:
new_logs[author] = [] # Make array entry for the author
continue
# If we hit a /cl, we're no longer reading logs
elif line == "/🆑":
print("Found closing /:cl: tag")
incltag = False
print(f"Checking line '{line}'")
if line[:1] == "🆑": # Find the start of the changelog
print("Found opening :cl: tag")
if incltag == True: # If we're already reading logs, skip
continue
incltag = True
# If we aren't reading logs, we don't care about any other line contents
if not incltag:
continue
# Split line into tag (icon) and body (comment)
body = re.split("[ ,:-]", line, 1)
if len(body) != 2:
continue # If there's just one word, then it can't really be a changelog, now can it
if body[0] in validPrefixes:
tag = validPrefixes[body[0]]
body = body[1].strip(" ,-:\t\n")
else: # If the tag is invalid, just default to rscadd
tag = "rscadd"
body = line.strip(" ,-:\t\n")
new_logs[author].append(f" - {tag}: \"{body}\"")
new += 1
# Fetch the author name
author = line[1:]
author.strip()
if not len(author):
author = args.pr_author
if author not in new_logs:
new_logs[author] = [] # Make array entry for the author
continue
# If we hit a /cl, we're no longer reading logs
elif line == "/🆑":
print("Found closing /:cl: tag")
incltag = False
# If we aren't reading logs, we don't care about any other line contents
if not incltag:
continue
# Split line into tag (icon) and body (comment)
body = re.split("[ ,:-]", line, 1)
if len(body) != 2:
continue # If there's just one word, then it can't really be a changelog, now can it
if body[0] in validPrefixes:
tag = validPrefixes[body[0]]
body = body[1].strip(" ,-:\t\n")
else: # If the tag is invalid, just default to rscadd
tag = "rscadd"
body = line.strip(" ,-:\t\n")
new_logs[author].append(f" - {tag}: \"{body}\"")
new += 1
print(f"Writing {new} new changelog entries.") # f supposedly formats new into the var
for auth in new_logs:
# Sanitize authors without changes
if not len(new_logs[auth]):
continue
f = open(os.path.join(args.target_dir, f"{auth}{args.pr_numb}.yml"), 'w')
print(f"Writing changes to {f}")
f.write(f'author: {auth}\n')
f.write('delete-after: True\n')
f.write('changes:\n')
for log in new_logs[auth]:
f.write(f'{log}\n')
f.close()
# Sanitize authors without changes
if not len(new_logs[auth]):
continue
f = open(os.path.join(args.target_dir, f"{auth}{args.pr_numb}.yml"), 'w')
print(f"Writing changes to {f}")
f.write(f'author: {auth}\n')
f.write('delete-after: True\n')
f.write('changes:\n')
for log in new_logs[auth]:
f.write(f'{log}\n')
f.close()
+215 -215
View File
@@ -1,215 +1,215 @@
'''
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 [--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)
+120 -120
View File
@@ -1,120 +1,120 @@
'''
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.
'''
import argparse, re, sys
from collections import defaultdict
from os import path, walk
opt = argparse.ArgumentParser()
opt.add_argument('dir', help='The directory to scan for *.dm files with non-matching spans')
args = opt.parse_args()
if(not path.isdir(args.dir)):
print('Not a directory')
sys.exit(1)
# These tuples are expected to be ordered as:
# A unique human readable name (henceforth referred to as tuple name), a regex pattern matching an opening tag, a regex pattern matching a closing tag
tag_tuples = [ ('<span>', re.compile('<span(.*?)>', re.IGNORECASE), re.compile('</span>', re.IGNORECASE)),
('<font>', re.compile('<font(.*?)>', re.IGNORECASE), re.compile('</font>', re.IGNORECASE)),
('<center>', re.compile('<center>', re.IGNORECASE), re.compile('</center>', re.IGNORECASE)),
('<b>', re.compile('<b>', re.IGNORECASE), re.compile('</b>', re.IGNORECASE)),
('<i>', re.compile('<i>', re.IGNORECASE), re.compile('</i>', re.IGNORECASE))]
# The keys of this dictionary will be the file path of each parsed *.dm file
# The values of this dictionary is a another dictionary with the key/value pair: tag/list of unmatched lines
mismatches_by_file = { }
# Loops over all defined tag tuples and returns a dictionary with the key/value pair: tag/mismatch_count (positive means excess of opening tag, negative means excess of closing tags)
def get_tag_matches(line):
mismatch_count_by_tag = { }
for tag_tuple in tag_tuples:
mismatch_count = 0
mismatch_count += len(tag_tuple[1].findall(line))
mismatch_count -= len(tag_tuple[2].findall(line))
if mismatch_count != 0:
mismatch_count_by_tag[tag_tuple[0]] = mismatch_count
return mismatch_count_by_tag
# Support def that simply checks if a given dictionary in the format tag/list of unmatched lines has mismatch entries.
def has_mismatch(match_list):
for tag, list_of_mismatched_lines in match_list.iteritems():
if(len(list_of_mismatched_lines) > 0):
return 1
return 0
def arrange_mismatches(mismatches_by_tag, mismatch_line, mismatch_counts):
for tag, mismatch_count in mismatch_counts.iteritems():
stack_of_existing_mismatches = mismatches_by_tag[tag]
for i in range(0, abs(mismatch_count)):
if len(stack_of_existing_mismatches) == 0:
if(mismatch_count > 0):
stack_of_existing_mismatches.append(mismatch_line)
else:
stack_of_existing_mismatches.append(-mismatch_line)
else:
if stack_of_existing_mismatches[0] > 0:
if mismatch_count > 0:
stack_of_existing_mismatches.append(mismatch_line)
else:
stack_of_existing_mismatches.pop()
else:
if mismatch_count < 0:
stack_of_existing_mismatches.append(-mismatch_line)
else:
stack_of_existing_mismatches.pop()
# This section parses all *.dm files in the given directory, recursively.
for root, subdirs, files in walk(args.dir):
for filename in files:
if filename.endswith('.dm'):
file_path = path.join(root, filename)
with open(file_path, 'r') as file:
mismatches_by_file[file_path] = defaultdict(list)
for line_number, line in enumerate(file, 1):
# Then for each line in the file, conduct the tuple open/close matching.
mismatches_by_tag = get_tag_matches(line)
arrange_mismatches(mismatches_by_file[file_path], line_number, mismatches_by_tag)
# Pretty printing section.
# Loops over all matches and checks if there is a mismatch of tags.
# If so, then and only then is the corresponding file path printed along with the number of unmatched open/close tags.
total_mismatches = 0
for file, mismatches_by_tag in mismatches_by_file.iteritems():
if has_mismatch(mismatches_by_tag):
print(file)
for tag, mismatch_list in mismatches_by_tag.iteritems():
# A positive number means an excess of opening tag, a negative number means an excess of closing tags.
total_mismatches += len(mismatch_list)
if len(mismatch_list) > 0:
if mismatch_list[0] > 0:
print('\t{0} - Excess of {1} opening tag(s)'.format(tag, len(mismatch_list)))
elif mismatch_list[0] < 0:
print('\t{0} - Excess of {1} closing tag(s)'.format(tag, len(mismatch_list)))
for mismatch_line in sorted(set(mismatch_list)):
print('\t\tLine {0}'.format(abs(mismatch_line)))
# Simply prints the total number of mismatches found and if so returns 1 to, for example, fail CI builds.
if(total_mismatches == 0):
print('No mismatches found.')
else:
print('')
print('Total number of mismatches: {0}'.format(total_mismatches))
sys.exit(1)
'''
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.
'''
import argparse, re, sys
from collections import defaultdict
from os import path, walk
opt = argparse.ArgumentParser()
opt.add_argument('dir', help='The directory to scan for *.dm files with non-matching spans')
args = opt.parse_args()
if(not path.isdir(args.dir)):
print('Not a directory')
sys.exit(1)
# These tuples are expected to be ordered as:
# A unique human readable name (henceforth referred to as tuple name), a regex pattern matching an opening tag, a regex pattern matching a closing tag
tag_tuples = [ ('<span>', re.compile('<span(.*?)>', re.IGNORECASE), re.compile('</span>', re.IGNORECASE)),
('<font>', re.compile('<font(.*?)>', re.IGNORECASE), re.compile('</font>', re.IGNORECASE)),
('<center>', re.compile('<center>', re.IGNORECASE), re.compile('</center>', re.IGNORECASE)),
('<b>', re.compile('<b>', re.IGNORECASE), re.compile('</b>', re.IGNORECASE)),
('<i>', re.compile('<i>', re.IGNORECASE), re.compile('</i>', re.IGNORECASE))]
# The keys of this dictionary will be the file path of each parsed *.dm file
# The values of this dictionary is a another dictionary with the key/value pair: tag/list of unmatched lines
mismatches_by_file = { }
# Loops over all defined tag tuples and returns a dictionary with the key/value pair: tag/mismatch_count (positive means excess of opening tag, negative means excess of closing tags)
def get_tag_matches(line):
mismatch_count_by_tag = { }
for tag_tuple in tag_tuples:
mismatch_count = 0
mismatch_count += len(tag_tuple[1].findall(line))
mismatch_count -= len(tag_tuple[2].findall(line))
if mismatch_count != 0:
mismatch_count_by_tag[tag_tuple[0]] = mismatch_count
return mismatch_count_by_tag
# Support def that simply checks if a given dictionary in the format tag/list of unmatched lines has mismatch entries.
def has_mismatch(match_list):
for tag, list_of_mismatched_lines in match_list.items():
if(len(list_of_mismatched_lines) > 0):
return 1
return 0
def arrange_mismatches(mismatches_by_tag, mismatch_line, mismatch_counts):
for tag, mismatch_count in mismatch_counts.items():
stack_of_existing_mismatches = mismatches_by_tag[tag]
for i in range(0, abs(mismatch_count)):
if len(stack_of_existing_mismatches) == 0:
if(mismatch_count > 0):
stack_of_existing_mismatches.append(mismatch_line)
else:
stack_of_existing_mismatches.append(-mismatch_line)
else:
if stack_of_existing_mismatches[0] > 0:
if mismatch_count > 0:
stack_of_existing_mismatches.append(mismatch_line)
else:
stack_of_existing_mismatches.pop()
else:
if mismatch_count < 0:
stack_of_existing_mismatches.append(-mismatch_line)
else:
stack_of_existing_mismatches.pop()
# This section parses all *.dm files in the given directory, recursively.
for root, subdirs, files in walk(args.dir):
for filename in files:
if filename.endswith('.dm'):
file_path = path.join(root, filename)
with open(file_path, 'r') as file:
mismatches_by_file[file_path] = defaultdict(list)
for line_number, line in enumerate(file, 1):
# Then for each line in the file, conduct the tuple open/close matching.
mismatches_by_tag = get_tag_matches(line)
arrange_mismatches(mismatches_by_file[file_path], line_number, mismatches_by_tag)
# Pretty printing section.
# Loops over all matches and checks if there is a mismatch of tags.
# If so, then and only then is the corresponding file path printed along with the number of unmatched open/close tags.
total_mismatches = 0
for file, mismatches_by_tag in mismatches_by_file.items():
if has_mismatch(mismatches_by_tag):
print(file)
for tag, mismatch_list in mismatches_by_tag.items():
# A positive number means an excess of opening tag, a negative number means an excess of closing tags.
total_mismatches += len(mismatch_list)
if len(mismatch_list) > 0:
if mismatch_list[0] > 0:
print('\t{0} - Excess of {1} opening tag(s)'.format(tag, len(mismatch_list)))
elif mismatch_list[0] < 0:
print('\t{0} - Excess of {1} closing tag(s)'.format(tag, len(mismatch_list)))
for mismatch_line in sorted(set(mismatch_list)):
print('\t\tLine {0}'.format(abs(mismatch_line)))
# Simply prints the total number of mismatches found and if so returns 1 to, for example, fail CI builds.
if(total_mismatches == 0):
print('No mismatches found.')
else:
print('')
print('Total number of mismatches: {0}'.format(total_mismatches))
sys.exit(1)
+12 -12
View File
@@ -13,7 +13,7 @@ def _dmitool_call(*dmitool_args, **popen_args):
def _safe_parse(dict, key, deferred_value):
try:
dict[key] = deferred_value()
except Exception as e:
except Exception as e:
print "Could not parse property '%s': %s"%(key, e)
return e
return False
@@ -29,28 +29,28 @@ def help():
return str(stdout).strip()
def info(filepath):
""" Totally not a hack that parses the output from dmitool into a dictionary.
""" Totally not a hack that parses the output from dmitool into a dictionary.
May break at any moment.
"""
subproc = _dmitool_call("info", filepath, stdout=PIPE)
stdout, stderr = subproc.communicate()
result = {}
data = stdout.split(os.linesep)[1:]
#for s in data: print s
#parse header line
if len(data) > 0:
header = data.pop(0).split(",")
#don't need to parse states, it's redundant
_safe_parse(result, "images", lambda: int(header[0].split()[0].strip()))
_safe_parse(result, "size", lambda: header[2].split()[1].strip())
#parse state information
states = []
states = []
for item in data:
if not len(item): continue
stateinfo = {}
item = item.split(",", 3)
_safe_parse(stateinfo, "name", lambda: item[0].split()[1].strip(" \""))
@@ -58,9 +58,9 @@ def info(filepath):
_safe_parse(stateinfo, "frames", lambda: int(item[2].split()[0].strip()))
if len(item) > 3:
stateinfo["misc"] = item[3]
states.append(stateinfo)
result["states"] = states
return result
@@ -78,17 +78,17 @@ def import_state(target_path, input_path, icon_state, replace=False, delays=None
""" Inserts an input png given by the input_path into the target_path.
"""
args = ["import", target_path, icon_state, input_path]
if replace: args.append("nodup")
if rewind: args.append("rewind")
if ismovement: args.append("movement")
if delays: args.extend(("delays", ",".join(delays)))
if direction is not None: args.extend(("direction", direction))
if frame is not None: args.extend(("frame", frame))
if loop in ("inf", "infinity"):
args.append("loop")
elif loop:
args.extend(("loopn", loop))
return _dmitool_call(*args)
+93 -93
View File
@@ -1,93 +1,93 @@
#!/usr/bin/env python
import re, os, sys, fnmatch
# Regex pattern to extract the directory path in a #define FILE_DIR
filedir_pattern = re.compile(r'^#define\s*FILE_DIR\s*"(.*?)"')
# Regex pattern to extract any single quoted piece of text. This can also
# match single quoted strings inside of double quotes, which is part of a
# regular text string and should not be replaced. The replacement function
# however will any match that doesn't appear to be a filename so these
# extra matches should not be a problem.
rename_pattern = re.compile(r"'(.+?)'")
# Only filenames matching this pattern will have their resources renamed
source_pattern = re.compile(r"^.*?\.(dm|dmm)$")
# Open the .dme file and return a list of all FILE_DIR paths in it
def read_filedirs(filename):
result = []
dme_file = file(filename, "rt")
# Read each line from the file and check for regex pattern match
for row in dme_file:
match = filedir_pattern.match(row)
if match:
result.append(match.group(1))
dme_file.close()
return result
# Search through a list of directories, and build a dictionary which
# maps every file to its full pathname (relative to the .dme file)
# If the same filename appears in more than one directory, the earlier
# directory in the list takes preference.
def index_files(file_dirs):
result = {}
# Reverse the directory list so the earlier directories take precedence
# by replacing the previously indexed file of the same name
for directory in reversed(file_dirs):
for name in os.listdir(directory):
# Replace backslash path separators on Windows with forward slash
# Force "name" to lowercase when used as a key since BYOND resource
# names are case insensitive, even on Linux.
if name.find(".") == -1:
continue
result[name.lower()] = directory.replace('\\', '/') + '/' + name
return result
# Recursively search for every .dm/.dmm file in the .dme file directory. For
# each file, search it for any resource names in single quotes, and replace
# them with the full path previously found by index_files()
def rewrite_sources(resources):
# Create a closure for the regex replacement function to capture the
# resources dictionary which can't be passed directly to this function
def replace_func(name):
key = name.group(1).lower()
if key in resources:
replacement = resources[key]
else:
replacement = name.group(1)
return "'" + replacement + "'"
# Search recursively for all .dm and .dmm files
for (dirpath, dirs, files) in os.walk("."):
for name in files:
if source_pattern.match(name):
path = dirpath + '/' + name
source_file = file(path, "rt")
output_file = file(path + ".tmp", "wt")
# Read file one line at a time and perform replacement of all
# single quoted resource names with the fullpath to that resource
# file. Write the updated text back out to a temporary file.
for row in source_file:
row = rename_pattern.sub(replace_func, row)
output_file.write(row)
output_file.close()
source_file.close()
# Delete original source file and replace with the temporary
# output. On Windows, an atomic rename() operation is not
# possible like it is under POSIX.
os.remove(path)
os.rename(path + ".tmp", path)
dirs = read_filedirs("tgstation.dme");
resources = index_files(dirs)
rewrite_sources(resources)
#!/usr/bin/env python
import re, os, sys, fnmatch
# Regex pattern to extract the directory path in a #define FILE_DIR
filedir_pattern = re.compile(r'^#define\s*FILE_DIR\s*"(.*?)"')
# Regex pattern to extract any single quoted piece of text. This can also
# match single quoted strings inside of double quotes, which is part of a
# regular text string and should not be replaced. The replacement function
# however will any match that doesn't appear to be a filename so these
# extra matches should not be a problem.
rename_pattern = re.compile(r"'(.+?)'")
# Only filenames matching this pattern will have their resources renamed
source_pattern = re.compile(r"^.*?\.(dm|dmm)$")
# Open the .dme file and return a list of all FILE_DIR paths in it
def read_filedirs(filename):
result = []
dme_file = file(filename, "rt")
# Read each line from the file and check for regex pattern match
for row in dme_file:
match = filedir_pattern.match(row)
if match:
result.append(match.group(1))
dme_file.close()
return result
# Search through a list of directories, and build a dictionary which
# maps every file to its full pathname (relative to the .dme file)
# If the same filename appears in more than one directory, the earlier
# directory in the list takes preference.
def index_files(file_dirs):
result = {}
# Reverse the directory list so the earlier directories take precedence
# by replacing the previously indexed file of the same name
for directory in reversed(file_dirs):
for name in os.listdir(directory):
# Replace backslash path separators on Windows with forward slash
# Force "name" to lowercase when used as a key since BYOND resource
# names are case insensitive, even on Linux.
if name.find(".") == -1:
continue
result[name.lower()] = directory.replace('\\', '/') + '/' + name
return result
# Recursively search for every .dm/.dmm file in the .dme file directory. For
# each file, search it for any resource names in single quotes, and replace
# them with the full path previously found by index_files()
def rewrite_sources(resources):
# Create a closure for the regex replacement function to capture the
# resources dictionary which can't be passed directly to this function
def replace_func(name):
key = name.group(1).lower()
if key in resources:
replacement = resources[key]
else:
replacement = name.group(1)
return "'" + replacement + "'"
# Search recursively for all .dm and .dmm files
for (dirpath, dirs, files) in os.walk("."):
for name in files:
if source_pattern.match(name):
path = dirpath + '/' + name
source_file = file(path, "rt")
output_file = file(path + ".tmp", "wt")
# Read file one line at a time and perform replacement of all
# single quoted resource names with the fullpath to that resource
# file. Write the updated text back out to a temporary file.
for row in source_file:
row = rename_pattern.sub(replace_func, row)
output_file.write(row)
output_file.close()
source_file.close()
# Delete original source file and replace with the temporary
# output. On Windows, an atomic rename() operation is not
# possible like it is under POSIX.
os.remove(path)
os.rename(path + ".tmp", path)
dirs = read_filedirs("tgstation.dme");
resources = index_files(dirs)
rewrite_sources(resources)