#!/usr/bin/env python
# Display today's UKCH food plans.
# Adam Sampson <ats1@kent.ac.uk>

import re, urllib, sys

def extract_javascript(s):
	"""Extract the contents of all <script> blocks in HTML."""

	return "\n".join(re.findall(r'(?msi)<script[^>]*>(.*?)</script>', s))

class JavascriptError(Exception): pass

class JavascriptEngine:
	"""An extremely limited Javascript interpreter."""

	def __init__(self, js):
		self.js = js
		self.max = len(js)
		self.pos = 0
		self.vars = {}

	def _match(self, s):
		"""Skip whitespace, then try to match a string at the current
		position."""

		while self.pos < self.max:
			if self.js[self.pos] not in " \t\r\n":
				break
			self.pos += 1

		l = len(s)
		if self.max - self.pos < l:
			return False
		if self.js[self.pos:self.pos + l] == s:
			self.pos += l
			return True
		return False

	def _token(self):
		"""Get the next token, skipping comments."""

		comments = {"<!--": "\n", "//": "\n", "/*": "*/"}
		while 1:
			had_comment = False
			for left, right in comments.items():
				if self._match(left):
					self._discard_to(right)
					had_comment = True
			if not had_comment:
				break
				
		items = ["var", "\"", "+", "==", "=", "if", "else", "new", "(", ")", ";", "&&", ">", "<", "{", ".", "}"]
		for item in items:
			if self._match(item):
				return item

		if self.pos == self.max:
			return None

		m = re.match(r"[a-zA-Z][a-zA-Z0-9]+", self.js[self.pos:])
		if m is None:
			s = self.js[self.pos]
		else:
			s = m.group(0)

		self.pos += len(s)
		return s

	def _untoken(self, s):
		"""Put back a token (which must be the one you just got)."""
		self.pos -= len(s)

	def _discard_to(self, s):
		"""Discard characters up to and including the given string."""

		p = self.js.find(s, self.pos)
		if p == -1:
			return False
		self.pos = p + len(s)
		return True

	def _read_expr(self):
		"""Read and evaluate an expression, returning (value,
		terminating token)."""

		op_pris = {"+": 1}
		def do_op(stack, op):
			if op == "+":
				a = stack.pop()
				stack.append(stack.pop() + a)
			else:
				raise JavascriptError("Unimplemented op in expr: " + op)

		vals = []
		ops = []
		while 1:
			tok = self._token()
			if tok == "var" or tok == "if":
				# Kludge (well, *more* kludge) since UKCH don't
				# bother terminating their var lines...
				self._untoken(tok)
				tok = ";"
				break
			elif tok is None or tok == ")" or tok == ";" or tok == ",":
				break
			elif tok == "\"":
				p = self.js.find("\"", self.pos)
				if p == -1:
					raise JavascriptError("Unterminated string in expr")
				vals.append(self.js[self.pos:p])
				self.pos = p + 1
			elif tok == "(":
				(val, term) = self._read_expr()
				if term != ")":
					raise JavascriptError("Badly terminated bracket expression")
				vals.append(val)
			elif tok in op_pris:
				while ops != [] and op_pris[tok] > op_pris[ops[-1]]:
					do_op(vals, ops.pop())
				ops.append(tok)
			else:
				raise JavascriptError("Unknown token in expr: " + tok)
		while ops != []:
			do_op(vals, ops.pop())
		if len(vals) != 1:
			raise JavascriptError("Bad value stack in expr")
		return (vals[0], tok)

	def run(self):
		"""Interpret code until we've seen both am and pm variables."""

		while 1:
			tok = self._token()
			if tok is None:
				return ("Sorry, I can't read the UKCH site at the moment.", "")
	
			if tok == "var":
				vn = self._token()
				if self._token() != "=":
					raise JavascriptError("var without =")
				(self.vars[vn], term) = self._read_expr()
				if term != ";":
					raise JavascriptError("Badly-terminated var expression")
			else:
				raise JavascriptError("Unknown token at toplevel: " + tok)

			if "am" in self.vars and "pm" in self.vars:
				return (self.vars["am"], self.vars["pm"])

def parse_rubbish_html(s):
	"""Turn the pseudo-HTML in the UKCH page into a list of (style, text)
	pairs."""

	bits = []
	mode = "p"
	pos = 0
	while 1:
		nextl = s.find("<", pos)
		nextr = s.find(">", nextl)
		if nextl == -1 or nextr == -1:
			bits.append((mode, s[pos:].strip()))
			break
		bits.append((mode, s[pos:nextl].strip()))

		tag = s[nextl + 1:nextr]
		i = tag.find(" ")
		if i != -1:
			tag = tag[:i]
		tag = tag.lower().strip()
		if tag == "":
			pass
		elif tag[0] == "/":
			mode = "p"
		elif tag != "a":
			mode = tag

		pos = nextr + 1

	return [(mode, text) for (mode, text) in bits if text != ""]

def word_wrap(prefix, s, width = 72):
	words = re.split(r"\s+", s)
	line = prefix
	for word in words:
		if len(line + word) + 1 > width:
			print line
			line = prefix
		line += word + " "
	if line != prefix:
		print line

def format_html(items):
	"""Display the result of parse_rubbish_html."""

	for (mode, text) in items:
		if mode == "h3":
			p = "*** "
		elif mode == "h4":
			p = "* "
		else:
			p = ""
		word_wrap(p, text)
		print

if __name__ == "__main__":
	f = urllib.urlopen("http://www.kent.ac.uk/hospitality/staff-student/catering/index.htm")
	data = f.read()
	f.close()

	js = extract_javascript(data)
	e = JavascriptEngine(js)
	try:
		(am, pm) = e.run()
	except JavascriptError, err:
		print "Problem running Javascript (please report this):", err
		sys.exit(0)

	format_html(parse_rubbish_html(am))
	format_html(parse_rubbish_html(pm))

