Skip to content

Instantly share code, notes, and snippets.

@tito
Last active May 15, 2018 05:04
Show Gist options
  • Save tito/4fb7f66861c5c511fe8e to your computer and use it in GitHub Desktop.
Save tito/4fb7f66861c5c511fe8e to your computer and use it in GitHub Desktop.
Kivy Router (tests)
# coding=utf-8
from kivy.app import App
from kivy.properties import StringProperty
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.logger import Logger
import re
Builder.load_string("""
<Router>:
ScreenManager:
id: screenmanager
""")
# Taken from werkzeug/routing.py
_rule_re = re.compile(r'''
(?P<static>[^<]*) # static rule data
<
(?:
(?P<converter>[a-zA-Z_][a-zA-Z0-9_]*) # converter name
(?:\((?P<args>.*?)\))? # converter arguments
\: # variable delimiter
)?
(?P<variable>[a-zA-Z_][a-zA-Z0-9_]*) # variable name
>
''', re.VERBOSE)
def parse_rule(rule):
pos = 0
end = len(rule)
do_match = _rule_re.match
used_names = set()
while pos < end:
m = do_match(rule, pos)
if m is None:
break
data = m.groupdict()
if data['static']:
yield None, None, data['static']
variable = data['variable']
converter = data['converter'] or 'default'
if variable in used_names:
raise ValueError('variable name %r used twice.' % variable)
used_names.add(variable)
yield converter, data['args'] or None, variable
pos = m.end()
if pos < end:
remaining = rule[pos:]
if '>' in remaining or '<' in remaining:
raise ValueError('malformed url rule: %r' % rule)
yield None, None, remaining
class BaseConverter(object):
regex = '[^/]/'
def to_python(self, value):
return value
def to_url(self, value):
return value
class PathConverter(BaseConverter):
regex = '[^/].*?'
def to_python(self, value):
if not value.startswith("/"):
return "/{}".format(value)
return value
class IntegerConverter(BaseConverter):
regex = r'\d+'
def to_python(self, value):
return int(value)
def to_url(self, value):
return '{}'.format(value)
class UnicodeConverter(BaseConverter):
regex = '[^/]{1,}'
CONVERTERS = {
"path": PathConverter,
"int": IntegerConverter,
"default": UnicodeConverter
}
def regex_rule(rule):
regex_parts = []
url_parts = list(parse_rule(rule))
print url_parts
for converter, args, variable in url_parts:
if converter is None:
regex_parts.append(re.escape(variable))
else:
conv = CONVERTERS.get(converter)()
regex_parts.append('(?P<%s>%s)' % (variable, conv.regex))
regex = r'^%s$' % (u''.join(regex_parts))
return (url_parts, re.compile(regex, re.UNICODE))
def route(rule):
url_parts, regex = regex_rule(rule)
def decorator(f):
if not hasattr(f, "router_rules"):
f.router_rules = [(rule, regex, url_parts)]
else:
f.router_rules.append((rule, regex, url_parts))
return f
return decorator
def route_options(**options):
def decorator(f):
f.router_options = options
return f
return decorator
class AppRouter(App):
route = StringProperty("")
def on_route(self, instance, route):
Logger.info("AppRouter: Route is '{}'".format(route))
self.root.route = self.route
class Router(RelativeLayout):
route = StringProperty("")
def __init__(self, **kwargs):
assert(route not in kwargs)
self.router_rules = {}
self.init_routes()
super(Router, self).__init__(**kwargs)
def init_routes(self):
for key in dir(self):
func = getattr(self, key)
if not callable(func):
continue
if not hasattr(func, "router_rules"):
continue
for rule, regex, url_parts in func.router_rules:
options = None
if hasattr(func, "router_options"):
options = func.router_options
self.add_route(rule, func, regex=regex, url_parts=url_parts, options=options)
def add_route(self, rule, func, regex=None, url_parts=None, options=None):
if regex is None:
url_parts, regex = regex_rule(rule)
Logger.debug("{}: Add route {}".format(
self.__class__.__name__, rule))
url_parts, regex = regex_rule(rule)
self.router_rules[regex] = (url_parts, func, options)
def on_route(self, instance, route):
print "ON ROUTE", self.__class__.__name__, route
for regex, info in self.router_rules.items():
result = regex.match(route)
if not result:
continue
url_parts, func, options = info
name = route
# convert url variables/value to kwargs
kwargs = self._result_to_variables(result, url_parts)
if options:
# get the default name of this route for the screen
name = options.get("name", route)
# should we pass the previous view?
if options.get("with_view"):
sm = self.ids.screenmanager
view = None
print "search screen", name
if sm.has_screen(name):
print "-> FOUND ONE", sm.get_screen(name)
view = sm.get_screen(name)._router_view
print "-> view is", view
kwargs["view"] = view
print "CALL", route, kwargs, func
view = func(**kwargs)
return self.switch_to_view(view, name=name)
Logger.warning("{}: Unable to find a view for {}".format(
self.__class__.__name__, route))
def switch_to_view(self, view, name=None):
sm = self.ids.screenmanager
if isinstance(view, Screen):
screen = view
self.link(screen, view)
else:
if hasattr(view, "_router_screen"):
screen = view._router_screen
else:
screen = Screen(name=name)
screen.add_widget(view)
self.link(screen, view)
if sm.has_screen(name):
previous_screen = sm.get_screen(name)
if previous_screen is view._router_screen:
return
sm.remove_widget(previous_screen)
self.unlink(screen=previous_screen)
sm.switch_to(screen)
def link(self, screen, view):
screen._router_view = view
view._router_screen = screen
def unlink(self, screen):
if screen._router_view:
screen._router_view._router_screen = None
screen._router_view = None
def _result_to_variables(self, result, url_parts):
kwargs = {}
index = 1
for converter, args, variable in url_parts:
if converter is None:
continue
conv = CONVERTERS.get(converter)()
value = result.group(index)
kwargs[variable] = conv.to_python(value)
index += 1
return kwargs
if __name__ == "__main__":
from kivy.uix.label import Label
Builder.load_string("""
<-MainRouter>:
BoxLayout:
orientation: "vertical"
BoxLayout:
orientation: "horizontal"
size_hint_y: None
height: "48dp"
padding: "2dp"
spacing: "2dp"
Button:
text: "Index"
on_release: app.route = "/"
Button:
text: "Settings"
on_release: app.route = "/settings"
Button:
text: "Credits"
on_release: app.route = "/credits"
ScreenManager:
id: screenmanager
<-SettingsRouter>:
BoxLayout:
orientation: "vertical"
BoxLayout:
orientation: "horizontal"
size_hint_y: None
height: "48dp"
padding: "2dp"
spacing: "2dp"
Button:
text: "Audio"
on_release: app.route = "/settings/audio"
Button:
text: "Video"
on_release: app.route = "/settings/video"
ScreenManager:
id: screenmanager
""")
class SettingsRouter(Router):
@route("/audio")
def audio(self):
return Label(text="audio settings")
@route("/video")
def video(self):
return Label(text="video settings")
class MainRouter(Router):
@route("/")
def index(self):
return Label(text="index")
@route("/settings")
@route("/settings/<path:subroute>")
@route_options(name="settings", with_view=True)
def settings(self, view, subroute="/audio"):
if not view:
view = SettingsRouter()
view.route = subroute
return view
@route("/credits")
def credits(self):
return Label(text="credits")
class TestAppRouter(AppRouter):
def build(self):
self.root = MainRouter()
self.route = "/settings/video"
TestAppRouter().run()
@yaimran21
Copy link

What requirement put in buildozer for garden router....My app run in IDE but don't run in .apk. What I can do? Please help me...I am in big trouble

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment