Browse Source

Replace fancy AgeSDL Python meta hacks

Path of the Shell did not like my fancy metaprogramming tricks for
defining an AgeSDL Python class that contained characters that are
illegal in Python identifiers. So, now, we revert to just using a
standard class declaration.

That means that we need to strip out any illegal identifiers from the
age name first. A legal Python 2.x identifier is constrained to the
ASCII alphanumeric characters and the underscore with the stipulation
that the first character cannot be a number. To illustrate this to the
artist, we alert the age name property field if an illegal character is
found in the age name. We also alert on the underscore, which is now
used as a very very special replacement character. In the case of an
illegal character, an error message is shown in the UI with the correct
AgeSDL name.

Of course, I hope no one really uses those illegal characters and this
is just more fulmination on my part...
pull/128/head
Adam Johnson 6 years ago
parent
commit
6c4aedb17a
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 10
      korman/exporter/manager.py
  2. 7
      korman/exporter/python.py
  3. 35
      korman/korlib/__init__.py
  4. 12
      korman/operators/op_export.py
  5. 3
      korman/plasma_magic.py
  6. 17
      korman/ui/ui_world.py

10
korman/exporter/manager.py

@ -19,6 +19,7 @@ from PyHSPlasma import *
import weakref
from . import explosions
from .. import korlib
from ..plasma_magic import *
# These objects have to be in the plSceneNode pool in order to be loaded...
@ -243,21 +244,22 @@ class ExportManager:
output = self._exporter().output
# AgeSDL Hook Python
py_filename = "{}.py".format(age)
fixed_agename = korlib.replace_python2_identifier(age)
py_filename = "{}.py".format(fixed_agename)
age_py = get_text(py_filename, None)
if output.want_py_text(age_py):
py_code = age_py.as_string()
else:
py_code = very_very_special_python.format(age_name=age).lstrip()
py_code = very_very_special_python.format(age_name=fixed_agename).lstrip()
output.add_python_mod(py_filename, text_id=age_py, str_data=py_code)
# AgeSDL
sdl_filename = "{}.sdl".format(age)
sdl_filename = "{}.sdl".format(fixed_agename)
age_sdl = get_text(sdl_filename)
if age_sdl is not None:
sdl_code = None
else:
sdl_code = very_very_special_sdl.format(age_name=age).lstrip()
sdl_code = very_very_special_sdl.format(age_name=fixed_agename).lstrip()
output.add_sdl(sdl_filename, text_id=age_sdl, str_data=sdl_code)
def save_age(self):

7
korman/exporter/python.py

@ -80,7 +80,8 @@ class PythonPackageExporter:
def _ensure_age_sdl_hook(self, report):
age_props = bpy.context.scene.world.plasma_age
if age_props.age_sdl:
py_filename = "{}.py".format(age_props.age_name)
fixed_agename = korlib.replace_python2_identifier(age_props.age_name)
py_filename = "{}.py".format(fixed_agename)
age_py = self._modules.get(py_filename)
if age_py is not None:
del self._modules[py_filename]
@ -88,11 +89,11 @@ class PythonPackageExporter:
self._pfms[py_filename] = age_py
else:
report.warn("AgeSDL Python Script provided, but not requested for packing... Using default Python.", indent=1)
self._pfms[py_filename] = very_very_special_python.format(age_name=age_props.age_name)
self._pfms[py_filename] = very_very_special_python.format(age_name=fixed_agename)
else:
report.msg("Packing default AgeSDL Python", indent=1)
very_very_special_python.format(age_name=age_props.age_name)
self._pfms[py_filename] = very_very_special_python.format(age_name=age_props.age_name)
self._pfms[py_filename] = very_very_special_python.format(age_name=fixed_agename)
def _harvest_pfms(self, report):
objects = bpy.context.scene.objects

35
korman/korlib/__init__.py

@ -76,6 +76,13 @@ finally:
from .python import *
from .texture import TEX_DETAIL_ALPHA, TEX_DETAIL_ADD, TEX_DETAIL_MULTIPLY
_IDENTIFIER_RANGES = ((ord('0'), ord('9')), (ord('A'), ord('Z')), (ord('a'), ord('z')))
from keyword import kwlist as _kwlist
_KEYWORDS = set(_kwlist)
# Python 2.x keywords
_KEYWORDS.add("exec")
_KEYWORDS.add("print")
def _wave_chunks(stream):
while not stream.eof():
chunk_name = stream.read(4)
@ -101,3 +108,31 @@ finally:
header.read(stream)
return chunks[b"data"]["size"]
def is_legal_python2_identifier(identifier):
if not identifier:
return False
# FIXME: str.isascii in Python 3.7
if any(ord(i) > 0x7F for i in identifier):
return False
if is_python_keyword(identifier):
return False
return identifier.isidentifier()
is_python_keyword = _KEYWORDS.__contains__
def replace_python2_identifier(identifier):
"""Replaces illegal characters in a Python identifier with a replacement character"""
def process(identifier):
# No leading digits in identifiers, so skip the first range element (0...9)
yield next((identifier[0] for low, high in _IDENTIFIER_RANGES[1:]
if low <= ord(identifier[0]) <= high), '_')
for i in identifier[1:]:
yield next((i for low, high in _IDENTIFIER_RANGES if low <= ord(i) <= high), '_')
if identifier:
return "".join(process(identifier))
else:
return ""

12
korman/operators/op_export.py

@ -163,8 +163,12 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator):
if context.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
# Separate blender operator and actual export logic for my sanity
ageName = path.stem
if korlib.is_python_keyword(ageName):
self.report({"ERROR"}, "The Age name conflicts with the Python keyword '{}'".format(ageName))
return {"CANCELLED"}
# Separate blender operator and actual export logic for my sanity
with UiHelper(context) as _ui:
e = exporter.Exporter(self)
try:
@ -256,6 +260,12 @@ class PlasmaPythonExportOperator(ExportOperator, bpy.types.Operator):
return {"CANCELLED"}
path.touch()
# Age names cannot be python keywords
age_name = context.scene.world.plasma_age.age_name
if korlib.is_python_keyword(age_name):
self.report({"ERROR"}, "The Age name conflicts with the Python keyword '{}'".format(age_name))
return {"CANCELLED"}
# Bonus Fun: Implement Profile-mode here (later...)
e = exporter.PythonPackageExporter(filepath=self.filepath,
version=globals()[self.version])

3
korman/plasma_magic.py

@ -17,7 +17,8 @@ very_very_special_python = """
from Plasma import *
from PlasmaTypes import *
globals()["{age_name}"] = type("{age_name}", (ptResponder,), dict())
class {age_name}(ptResponder):
pass
"""
very_very_special_sdl = """

17
korman/ui/ui_world.py

@ -16,7 +16,7 @@
import bpy
from pathlib import Path
from ..korlib import ConsoleToggler
from .. import korlib
class AgeButtonsPanel:
@ -128,6 +128,9 @@ class PlasmaAgePanel(AgeButtonsPanel, bpy.types.Panel):
col.prop(active_page, "seq_suffix")
col.prop_menu_enum(active_page, "version")
# Age Names should really be legal Python 2.x identifiers for AgeSDLHooks
legal_identifier = korlib.is_legal_python2_identifier(age.age_name)
# Core settings
layout.separator()
split = layout.split()
@ -140,15 +143,23 @@ class PlasmaAgePanel(AgeButtonsPanel, bpy.types.Panel):
col = split.column()
col.label("Age Settings:")
col.prop(age, "seq_prefix", text="ID")
col.alert = not age.age_name.strip()
col.alert = not legal_identifier or '_' in age.age_name
col.prop(age, "age_name", text="")
# Display a hint if the identifier is illegal
if not legal_identifier:
if korlib.is_python_keyword(age.age_name):
layout.label(text="Ages should not be named the same as a Python keyword", icon="ERROR")
elif age.age_sdl:
fixed_identifier = korlib.replace_python2_identifier(age.age_name)
layout.label(text="Age's SDL will use the name '{}'".format(fixed_identifier), icon="ERROR")
layout.separator()
split = layout.split()
col = split.column()
col.label("Export Settings:")
col.enabled = ConsoleToggler.is_platform_supported()
col.enabled = korlib.ConsoleToggler.is_platform_supported()
col.prop(age, "verbose")
col.prop(age, "show_console")

Loading…
Cancel
Save