Changeset - 771f50f19b4b
[Not reviewed]
default
0 6 1
Drew Perttula - 11 years ago 2014-06-09 07:10:39
drewp@bigasterisk.com
single-line effect code now evals by changing <uri> into a suitable python object
Ignore-this: cde829f021be54bc7cfd63bddde43aa9
7 files changed with 163 insertions and 32 deletions:
0 comments (0 inline, 0 general)
bin/effecteval
Show inline comments
 
@@ -53,7 +53,7 @@ class SongEffects(PrettyErrorHandler, cy
 
            (song, L9['effect'], effect, ctx),
 
            (effect, RDF.type, L9['Effect'], ctx),
 
            (effect, L9['code'],
 
             Literal('out = sub(%s, intensity=%s)' % (dropped.n3(), curve.n3())),
 
             Literal('out = %s * %s' % (dropped.n3(), curve.n3())),
 
             ctx),
 
            ]))
 
        
 
@@ -102,13 +102,15 @@ class Code(PrettyErrorHandler, cyclone.w
 
    def put(self):
 
        effect = URIRef(self.get_argument('uri'))
 
        code = Literal(self.get_argument('code'))
 
        with self.settings.graph.currentState(tripleFilter=(None, L9['effect'], effect)) as g:
 
        with self.settings.graph.currentState(
 
                tripleFilter=(None, L9['effect'], effect)) as g:
 
            song = g.subjects(L9['effect'], effect).next()
 
        self.settings.graph.patchObject(
 
            context=song,
 
            subject=effect,
 
            predicate=L9['code'],
 
            newObject=code)
 
        # right here we could tell if the code has a python error and return it
 
        self.send_error(202)
 
        
 
class EffectEval(PrettyErrorHandler, cyclone.web.RequestHandler):
light9/curvecalc/curve.py
Show inline comments
 
@@ -22,7 +22,8 @@ class Curve(object):
 
        self._muted = False
 

	
 
    def __repr__(self):
 
        return "<%s (%s points)>" % (self.__class__.__name__, len(self.points))
 
        return "<%s %s (%s points)>" % (self.__class__.__name__, self.uri,
 
                                        len(self.points))
 

	
 
    def muted():
 
        doc = "Whether to currently send levels (boolean, obviously)"
 
@@ -183,7 +184,7 @@ class CurveResource(object):
 
        self.curve = Curve(self.uri)
 
        self.curve.points.extend([(0, 0)])
 
        self.saveCurve()
 
        self.watchChanges()
 
        self.watchCurvePointChanges()
 
        
 
    def loadCurve(self):
 
        if hasattr(self, 'curve'):
 
@@ -192,7 +193,11 @@ class CurveResource(object):
 
        pointsFile = self.graph.value(self.uri, L9['pointsFile'])
 
        self.curve = Curve(self.uri,
 
                           pointsStorage='file' if pointsFile else 'graph')
 
        if hasattr(self.graph, 'addHandler'):
 
        self.graph.addHandler(self.pointsFromGraph)
 
        else:
 
            # given a currentState graph
 
            self.pointsFromGraph()
 
        
 
    def pointsFromGraph(self):
 
        pts = self.graph.value(self.uri, L9['points'])
light9/effecteval/effect.py
Show inline comments
 
from run_local import log
 
import re
 
import re, logging
 
from rdflib import URIRef
 
from light9.namespaces import L9
 
from light9.curvecalc.curve import Curve
 
from light9.namespaces import L9, RDF
 
from light9.curvecalc.curve import CurveResource
 
from light9 import Submaster
 
log = logging.getLogger('effect')
 

	
 
def uriFromCode(s):
 
    # i thought this was something a graph could do with its namespace manager
 
@@ -15,6 +15,46 @@ def uriFromCode(s):
 
        return URIRef(s[1:-1])
 
    raise NotImplementedError
 

	
 
# consider http://waxeye.org/ for a parser that can be used in py and js
 

	
 
class CodeLine(object):
 
    """code string is immutable"""
 
    def __init__(self, graph, code):
 
        self.graph, self.code = graph, code
 

	
 
        self.outName, self.expr, self.resources = self._asPython()
 

	
 
    def _asPython(self):
 
        """
 
        out = sub(<uri1>, intensity=<curveuri2>)
 
        becomes
 
          'out',
 
          'sub(_u1, intensity=curve(_u2, t))',
 
          {'_u1': URIRef('uri1'), '_u2': URIRef('uri2')}
 
        """
 
        lname, expr = [s.strip() for s in self.code.split('=', 1)]
 
        self.uriCounter = 0
 
        resources = {}
 

	
 
        def alreadyInCurveFunc(s, i):
 
            prefix = 'curve('
 
            return i >= len(prefix) and s[i-len(prefix):i] == prefix
 

	
 
        def repl(m):
 
            v = '_res%s' % self.uriCounter
 
            self.uriCounter += 1
 
            r = resources[v] = URIRef(m.group(1))
 
            if self._uriIsCurve(r):
 
                if not alreadyInCurveFunc(m.string, m.start()):
 
                    return 'curve(%s, t)' % v
 
            return v
 
        outExpr = re.sub(r'<(http\S*?)>', repl, expr)
 
        return lname, outExpr, resources
 

	
 
    def _uriIsCurve(self, uri):
 
        # this result could vary with graph changes (rare)
 
        return self.graph.contains((uri, RDF.type, L9['Curve']))
 
        
 
class EffectNode(object):
 
    def __init__(self, graph, uri):
 
        self.graph, self.uri = graph, uri
 
@@ -22,28 +62,51 @@ class EffectNode(object):
 
        self.graph.addHandler(self.prepare)
 

	
 
    def prepare(self):
 
        self.code = self.graph.value(self.uri, L9['code'])
 
        if self.code is None:
 
        log.info("prepare effect %s", self.uri)
 
        # maybe there can be multiple lines of code as multiple
 
        # objects here, and we sort them by dependencies
 
        codeStr = self.graph.value(self.uri, L9['code'])
 
        if codeStr is None:
 
            raise ValueError("effect %s has no code" % self.uri)
 
        m = re.match(r'^out = sub\((.*?), intensity=(.*?)\)', self.code)
 
        if not m:
 
            raise NotImplementedError
 
        subUri = uriFromCode(m.group(1))
 

	
 
        self.code = CodeLine(self.graph, codeStr)
 

	
 
        self.resourceMap = self.resourcesAsPython()
 
        
 
    def resourcesAsPython(self):
 
        """
 
        mapping of the local names for uris in the code to high-level
 
        objects (Submaster, Curve)
 
        """
 
        out = {}
 
        subs = Submaster.get_global_submasters(self.graph)
 
        self.sub = subs.get_sub_by_uri(subUri)
 
        
 
        intensityCurve = uriFromCode(m.group(2))
 
        self.curve = Curve(uri=intensityCurve)
 
        for localVar, uri in self.code.resources.items():
 
            for rdfClass in self.graph.objects(uri, RDF.type):
 
                if rdfClass == L9['Curve']:
 
                    cr = CurveResource(self.graph, uri)
 
                    cr.loadCurve()
 
                    out[localVar] = cr.curve
 
                elif rdfClass == L9['Submaster']:
 
                    out[localVar] = subs.get_sub_by_uri(uri)
 
                else:
 
                    out[localVar] = uri
 

	
 
        pts = self.graph.value(intensityCurve, L9['points'])
 
        if pts is None:
 
            log.info("curve %r has no points" % intensityCurve)
 
        else:
 
            self.curve.set_from_string(pts)
 
        return out
 
        
 
    def eval(self, songTime):
 
        ns = {'t': songTime}
 

	
 
        ns.update(dict(
 
            curve=lambda c, t: c.eval(t),
 
            ))
 
        # loop over lines in order, merging in outputs 
 
        # merge in named outputs from previous lines
 
        
 
        ns.update(self.resourceMap)
 
        return eval(self.code.expr, ns)
 

	
 
        
 
    def eval(self, songTime):
 
        # consider http://waxeye.org/ for a parser that can be used in py and js
 
        level = self.curve.eval(songTime)
 
        scaledSubs = self.sub * level
 
        return scaledSubs
 
class GraphAwareFunction(object):
 
    def __init__(self, graph):
 
        self.graph = graph
 

	
light9/effecteval/effectloop.py
Show inline comments
 
from __future__ import division
 
import time, json, logging, traceback
 
from twisted.internet import reactor
 
from twisted.internet.defer import inlineCallbacks, returnValue
 
@@ -19,6 +20,7 @@ class EffectLoop(object):
 
        self.currentEffects = []
 
        self.lastLogTime = 0
 
        self.lastLogMsg = ""
 
        self.lastErrorLog = 0
 
        self.graph.addHandler(self.setEffects)
 
        self.period = 1 / 30
 
        self.coastSecs = .3 # main reason to keep this low is to notice play/pause
 
@@ -73,7 +75,12 @@ class EffectLoop(object):
 
                    try:
 
                        outSubs.append(e.eval(songTime))
 
                    except Exception as exc:
 
                        now = time.time()
 
                        if now > self.lastErrorLog + 5:
 
                        log.error("effect %s: %s" % (e.uri, exc))
 
                            log.error("  expr: %s", e.code.expr)
 
                            log.error("  resources: %r", e.resourcesAsPython())
 
                            self.lastErrorLog = now
 
                out = Submaster.sub_maxes(*outSubs)
 

	
 
                self.logLevels(t1, out)
light9/effecteval/test_effect.py
Show inline comments
 
new file 100644
 
import unittest
 
import mock
 
import sys
 
sys.path.insert(0, 'bin') # for run_local
 

	
 
from effect import CodeLine
 
from rdflib import URIRef
 

	
 
def isCurve(self, uri):
 
    return 'curve' in uri
 

	
 
@mock.patch('light9.effecteval.effect.CodeLine._uriIsCurve', new=isCurve)
 
class TestAsPython(unittest.TestCase):
 
    def test_gets_lname(self):
 
        ec = CodeLine(graph=None, code='x = y+1')
 
        self.assertEqual('x', ec._asPython()[0])
 

	
 
    def test_gets_simple_code(self):
 
        ec = CodeLine(graph=None, code='x = y+1')
 
        self.assertEqual('y+1', ec._asPython()[1])
 
        self.assertEqual({}, ec._asPython()[2])
 
        
 
    def test_converts_uri_to_var(self):
 
        ec = CodeLine(graph=None, code='x = <http://example.com/>')
 
        _, expr, uris = ec._asPython()
 
        self.assertEqual('_res0', expr)
 
        self.assertEqual({'_res0': URIRef('http://example.com/')}, uris)
 

	
 
    def test_converts_multiple_uris(self):
 
        ec = CodeLine(graph=None, code='x = <http://example.com/> + <http://other>')
 
        _, expr, uris = ec._asPython()
 
        self.assertEqual('_res0 + _res1', expr)
 
        self.assertEqual({'_res0': URIRef('http://example.com/'),
 
                          '_res1': URIRef('http://other')}, uris)
 
        
 
    def test_doesnt_fall_for_brackets(self):
 
        ec = CodeLine(graph=None, code='x = 1<2>3< h')
 
        _, expr, uris = ec._asPython()
 
        self.assertEqual('1<2>3< h', expr)
 
        self.assertEqual({}, uris)
 
        
 
    def test_curve_uri_expands_to_curve_eval_func(self):
 
        ec = CodeLine(graph=None, code='x = <http://example/curve1>')
 
        _, expr, uris = ec._asPython()
 
        self.assertEqual('curve(_res0, t)', expr)
 
        self.assertEqual({'_res0': URIRef('http://example/curve1')}, uris)
 

	
 
    def test_curve_doesnt_double_wrap(self):
 
        ec = CodeLine(graph=None, code='x = curve(<http://example/curve1>, t+.01)')
 
        _, expr, uris = ec._asPython()
 
        self.assertEqual('curve(_res0, t+.01)', expr)
 
        self.assertEqual({'_res0': URIRef('http://example/curve1')}, uris)
makefile
Show inline comments
 
NOSEARGS="--no-path-adjustment light9.rdfdb.rdflibpatch light9.rdfdb.patch"
 
NOSEARGS="--no-path-adjustment light9.rdfdb.rdflibpatch light9.rdfdb.patch light9.effecteval.test_effect"
 

	
 
tests:
 
	eval env/bin/nosetests -x $(NOSEARGS)
 

	
 
tests_watch:
 
	eval env/bin/nosetests --with-watch $(NOSEARGS)
 
	eval env/bin/nosetests --with-watcher $(NOSEARGS)
 

	
 

	
 
# needed packages: python-gtk2 python-imaging
pydeps
Show inline comments
 
@@ -8,7 +8,8 @@ web.py==0.37
 
restkit==4.2.2
 
ipython==2.1.0
 
nose==1.3.3
 
nose-alert==0.9.0
 
nose-watcher==0.1.2
 
watchdog==0.7.1
 
ipdb==0.8
 
coloredlogs==0.5
 
genshi==0.7
 
@@ -18,4 +19,5 @@ txosc==0.2.0
 
service_identity==0.2
 
Pillow==2.4.0
 
faulthandler==2.3
 
treq==0.2.1
 
\ No newline at end of file
 
treq==0.2.1
 
mock==1.0.1
 
\ No newline at end of file
0 comments (0 inline, 0 general)