"""Module with utlilty functions - especially for handling code snippets"""
import inspect
import re
import socket
from typing import Any
import numpy as np
[docs]def is_socket_used(addr=("127.0.0.1", 57110)):
a_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
result_of_check = a_socket.bind(addr)
except OSError as error:
return True
else:
return False
finally:
a_socket.close()
[docs]def parse_pyvars(code: str, frame_nr: int = 2):
"""Looks through call stack and finds values of variables.
Parameters
----------
code : str
SuperCollider command to be parsed
frame_nr : int, optional
on which frame to start, by default 2 (grandparent frame)
Returns
-------
dict
{variable_name: variable_value}
Raises
------
NameError
If the variable value could not be found.
"""
matches = re.findall(r"\s*\^[A-Za-z_]\w*\s*", code)
pyvars = {match.split("^")[1].strip(): None for match in matches}
missing_vars = list(pyvars.keys())
stack = inspect.stack()
frame = None
try:
while missing_vars and frame_nr < len(stack):
frame = stack[frame_nr][0]
for pyvar in pyvars:
if pyvar not in missing_vars:
continue
# check for variable in local variables
if pyvar in frame.f_locals:
pyvars[pyvar] = frame.f_locals[pyvar]
missing_vars.remove(pyvar)
# check for variable in global variables
elif pyvar in frame.f_globals:
pyvars[pyvar] = frame.f_globals[pyvar]
missing_vars.remove(pyvar)
frame_nr += 1
finally:
del frame
del stack
if missing_vars:
raise NameError("name(s) {} not defined".format(missing_vars))
return pyvars
[docs]def replace_vars(code: str, pyvars: dict) -> str:
"""Replaces python variables with SuperCollider literals in code.
This replaces the pyvars preceded with ^ in the code with a SC literal.
The conversion is done with convert_to_sc.
Parameters
----------
code : str
SuperCollider Code with python injections.
pyvars : dict
Dict with variable names and values.
Returns
-------
str
Code with injected variables.
"""
for pyvar, value in pyvars.items():
pyvar = "^" + pyvar
value = convert_to_sc(value)
code = code.replace(pyvar, value)
return code
[docs]def convert_to_sc(obj: Any) -> str:
"""Converts python objects to SuperCollider code literals.
This supports currently:
* numpy.ndarray -> SC Array representation
* complex type -> SC Complex
* strings -> if starting with sc3: it will be used as SC code
if it starts with a \\ (single escaped backward slash) it will be used as symbol
else it will be inserted as string
For unsupported types the __repr__ will be used.
Parameters
----------
obj : Any
object that should be converted to a SuperCollider code literal.
Returns
-------
str
SuperCollider Code literal
"""
if isinstance(obj, np.ndarray):
return obj.tolist().__repr__()
if isinstance(obj, complex):
return "Complex({0}, {1})".format(obj.real, obj.imag)
if isinstance(obj, str):
if obj.startswith("sc3:"): # start sequence for sc3-code
return "{}".format(obj[4:])
if obj.startswith(r"\\") and not obj.startswith(r"\\\\"):
return "'{}'".format(obj[1:]) # 'x' will be interpreted as symbol
if obj.startswith(r"\\\\"):
obj = obj[1:]
return '"{}"'.format(obj) # "x" will be interpreted as string
# further type conversion can be added in the future
return obj.__repr__()