mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2026-08-27 15:17:21 +01:00
updates .vscode/, .github/, .editorconfig, sneks
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"gbasood.byond-dm-language-support",
|
||||
"platymuus.dm-langclient",
|
||||
"EditorConfig.EditorConfig",
|
||||
"eamodio.gitlens"
|
||||
]
|
||||
}
|
||||
Vendored
+43
-9
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user