Skip to content

Commit

Permalink
add scripting engine (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcarbaugh authored Apr 22, 2023
1 parent 27a5ea2 commit 8ab5314
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 1 deletion.
50 changes: 50 additions & 0 deletions roku/scripting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import logging
import os
import re
import time
from collections import namedtuple

SCRIPT_RE = re.compile(r"(?P<command>\w+)(?:\:(?P<param>[\w\s]+))?(?:\@(?P<count>\d+))?(?:\*(?P<sleep>[\d\.]+))?") # noqa

Command = namedtuple('Command', ['command', 'param', 'count', 'sleep'])

logger = logging.getLogger('roku.scripting')


def load_script(path, params=None, raw=False):
if not os.path.exists(path):
raise ValueError(f'script at {path} not found')
with open(path) as infile:
content = infile.read()
if params:
content = content.format(**params)
if not raw:
content = content.strip().split('\n')
return content


def parse_script(script):
commands = []
for line in script:
if not line:
continue
m = SCRIPT_RE.match(line)
if m:
data = m.groupdict()
data['count'] = int(data['count'] or 1)
data['sleep'] = float(data['sleep']) if data['sleep'] else None
commands.append(Command(**data))
return commands


def run_script(roku, script, sleep=0.5):
for cmd in script:
logger.debug(cmd)
for i in range(cmd.count or 1):
func = getattr(roku, cmd.command)
if func:
if cmd.param:
func(cmd.param)
else:
func()
time.sleep(cmd.sleep or sleep)
36 changes: 36 additions & 0 deletions roku/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pytest

from roku import Application, Roku


class Fauxku(Roku):

def __init__(self, *args, **kwargs):
super(Fauxku, self).__init__(*args, **kwargs)
self._calls = []

def _call(self, method, path, *args, **kwargs):
self._calls.append((method, path, args, kwargs))
return ''

def calls(self):
return self._calls

def last_call(self):
return self._calls[-1]


@pytest.fixture
def roku():
return Fauxku('0.0.0.0')


@pytest.fixture
def apps(roku):
faux_apps = [
Application('11', '1.0.1', 'Fauxku Channel Store', roku),
Application('22', '2.0.2', 'Faux Netflix', roku),
Application('33', '3.0.3', 'Faux YouTube', roku),
Application('44HL', '4.0.4', 'Faux Hulu', roku),
]
return faux_apps
2 changes: 2 additions & 0 deletions roku/tests/scripts/testscript.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
literal:barbecue
literal:{avar}
1 change: 0 additions & 1 deletion roku/tests/test_roku.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import os
from urllib.parse import quote_plus

Expand Down
69 changes: 69 additions & 0 deletions roku/tests/test_scripting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import pytest

from roku import scripting


SCRIPT_PATH = 'roku/tests/scripts/testscript.txt'


def test_loading():
scripting.load_script(SCRIPT_PATH)


def test_loading_params():
params = {
'avar': 'here',
'notavar': 'missing',
}
content = scripting.load_script(SCRIPT_PATH, params=params, raw=True)
assert "literal:here" in content
assert "literal:missing" not in content


def test_loading_notfound():
with pytest.raises(ValueError):
scripting.load_script('thisisnotarealscript.txt')


def test_parse_command_only():
content = ('home',)
script = scripting.parse_script(content)
command = script[0]
assert command == scripting.Command('home', None, 1, None)


def test_parse_command_param():
content = ('literal:barbecue',)
script = scripting.parse_script(content)
command = script[0]
assert command == scripting.Command('literal', 'barbecue', 1, None)


def test_parse_command_count():
content = ('left@10',)
script = scripting.parse_script(content)
command = script[0]
assert command == scripting.Command('left', None, 10, None)


def test_parse_command_sleep():
content = ('left*2',)
script = scripting.parse_script(content)
command = script[0]
assert command == scripting.Command('left', None, 1, 2.0)


def test_parse_command_all():
content = ('literal:barbecue@3*5.1',)
script = scripting.parse_script(content)
command = script[0]
assert command == scripting.Command('literal', 'barbecue', 3, 5.1)


def test_run_script(roku):
content = ('home', 'literal:x')
script = scripting.parse_script(content)
scripting.run_script(roku, script)
calls = roku.calls()
assert 'keypress/Home' in calls[0][1]
assert 'keypress/Lit_x' in calls[1][1]

0 comments on commit 8ab5314

Please sign in to comment.