"""
This file demonstrates using decorators not to modify a function
but rather to send the function into a class which will use it later.

This mirrors how <https://flask.palletsprojects.com/> works.
The Flask handles listening for and parsing HTTP requests; it decides
how to respond to those requests by checking the path and method 
against various decorator calls and calling the decorated function
that matches.
"""

class Responder:
    """An example of the idea used by flask apps:
    decorator functions are left unmodified, and are also stored
    to be called under specific situations by the app."""
    def __init__(self):
        self._exact = {}
        self._starts = {}
        self._contains = {}
    def startswith(self, prefix):
        def decorator(f):
            self._starts[prefix.lower()] = f
            return f
        return decorator
    def contains(self, substring):
        def decorator(f):
            self._contains[substring.lower()] = f
            return f
        return decorator
    def exact(self, string):
        def decorator(f):
            self._contains[string.lower()] = f
            return f
        return decorator
    def chat(self, message):
        m = message.strip().lower()
        for s,f in self._exact.items():
            if m == s: return f(message)
        for s,f in self._starts.items():
            if m.startswith(s): return f(message)
        for s,f in self._contains.items():
            if s in m: return f(message)
        return 'Hmm...'


res = Responder()

@res.startswith('why')
def f(s):
    return 'Why not?'

@res.contains('?')
def f(s):
    return 'I give up. '+s.strip()

@res.exact('bye')
def f(s):
    return 'Going so soon?'

@res.contains('!')
def f(s):
    raise StopIteration("I don't deal well with excitement. Goodbye.")

print('Beginning conversation:', end='\n>>> ')
import sys
for line in sys.stdin:
    try:
        print(res.chat(line), end='\n>>> ')
    except StopIteration as ex:
        print(ex)
        break