Create attractive VASL scenarios, with loads of useful information embedded to assist with game play.
https://vasl-templates.org
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
4.9 KiB
152 lines
4.9 KiB
""" Miscellaneous utilities. """
|
|
|
|
import os
|
|
import io
|
|
import tempfile
|
|
import pathlib
|
|
from collections import defaultdict
|
|
|
|
from flask import request, Response, send_file
|
|
from PIL import Image
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
class MsgStore:
|
|
"""Store different types of messages."""
|
|
|
|
def __init__( self ):
|
|
self._msgs = None
|
|
self.reset()
|
|
|
|
def reset( self ):
|
|
"""Reset the MsgStore."""
|
|
self._msgs = defaultdict( list )
|
|
|
|
def info( self, msg, *args, **kwargs ):
|
|
"""Add an informational message."""
|
|
self._add_msg( "info", msg, *args, **kwargs )
|
|
|
|
def warning( self, msg, *args, **kwargs ):
|
|
"""Add a warning message."""
|
|
self._add_msg( "warning", msg, *args, **kwargs )
|
|
|
|
def error( self, msg, *args, **kwargs ):
|
|
"""Add an error message."""
|
|
self._add_msg( "error", msg, *args, **kwargs )
|
|
|
|
def get_msgs( self, msg_type ):
|
|
"""Get stored messages."""
|
|
return self._msgs[ msg_type ]
|
|
|
|
def _add_msg( self, msg_type, msg, *args, **kwargs ):
|
|
"""Add a message to the store."""
|
|
logger = kwargs.pop( "logger", None )
|
|
msg = msg.format( *args, **kwargs )
|
|
self._msgs[ msg_type ].append( msg )
|
|
if logger:
|
|
func = getattr( logger, "warn" if msg_type == "warning" else msg_type )
|
|
func( msg )
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
class TempFile:
|
|
"""Manage a temp file that can be closed while it's still being used."""
|
|
|
|
def __init__( self, mode="wb", extn=None ):
|
|
self.mode = mode
|
|
self.extn = extn
|
|
self.temp_file = None
|
|
self.name = None
|
|
|
|
def __enter__( self ):
|
|
"""Allocate a temp file."""
|
|
self.temp_file = tempfile.NamedTemporaryFile( mode=self.mode, suffix=self.extn, delete=False )
|
|
self.name = self.temp_file.name
|
|
return self
|
|
|
|
def __exit__( self, exc_type, exc_val, exc_tb ):
|
|
"""Clean up the temp file."""
|
|
self.close()
|
|
os.unlink( self.temp_file.name )
|
|
|
|
def write( self, data ):
|
|
"""Write data to the temp file."""
|
|
self.temp_file.write( data )
|
|
|
|
def close( self ):
|
|
"""Close the temp file."""
|
|
self.temp_file.close()
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
def resize_image_response( resp, default_width=None, default_height=None, default_scaling=None ):
|
|
"""Resize an image that will be returned as a Flask response."""
|
|
|
|
assert isinstance( resp, Response )
|
|
|
|
def get_image():
|
|
"""Get the the image from the Flask response that was passed in."""
|
|
resp.direct_passthrough = False
|
|
buf = io.BytesIO()
|
|
buf.write( resp.get_data() )
|
|
buf.seek( 0 )
|
|
return Image.open( buf )
|
|
|
|
# check if the caller specified a width and/or height
|
|
width = request.args.get( "width", default_width )
|
|
height = request.args.get( "height", default_height )
|
|
if width and height:
|
|
# width and height were specified, just use them as-is
|
|
img = get_image()
|
|
width = int( width )
|
|
height = int( height )
|
|
elif width and not height:
|
|
# width only, calculate the height
|
|
img = get_image()
|
|
aspect_ratio = float(img.size[0]) / float(img.size[1])
|
|
height = int(width) / aspect_ratio
|
|
elif not width and height:
|
|
# height only, calculate the width
|
|
img = get_image()
|
|
aspect_ratio = float(img.size[0]) / float(img.size[1])
|
|
width = int(height) * aspect_ratio
|
|
elif not width and not height:
|
|
# check if the caller specified a scaling factor
|
|
scaling = request.args.get( "scaling", default_scaling )
|
|
if scaling and scaling != 100:
|
|
img = get_image()
|
|
width = img.size[0] * float(scaling)/100
|
|
height = img.size[1] * float(scaling)/100
|
|
|
|
# check if we need to resize the image
|
|
if width or height:
|
|
assert width and height
|
|
# yup - make it so
|
|
img = img.resize( (int(width),int(height)), Image.ANTIALIAS )
|
|
buf = io.BytesIO()
|
|
img.save( buf, format="PNG" )
|
|
buf.seek( 0 )
|
|
return send_file( buf, mimetype="image/png" )
|
|
else:
|
|
# nope - return the image as-is
|
|
return resp
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
def change_extn( fname, extn ):
|
|
"""Change a filename's extension."""
|
|
return pathlib.Path( fname ).with_suffix( extn )
|
|
|
|
def is_image_file( fname ):
|
|
"""Check if a file is an image."""
|
|
if fname.startswith( "." ):
|
|
extn = fname
|
|
else:
|
|
extn = os.path.splitext( fname )[0]
|
|
return extn.lower() in (".png",".jpg",".jpeg",".gif")
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
class SimpleError( Exception ):
|
|
"""Represents a simple error that doesn't require a stack trace (e.g. bad configuration)."""
|
|
pass
|
|
|