#!/usr/bin/python2
# -*- coding: utf-8 -*-
version = "0.1.3"

import os
import fileinput
import getopt
import sys
import re

# =============================================================================== 

class Dialect:
	shortcuts = {}
	synonyms = {}
	required = {}
	short_tags = ()

class HtmlDialect(Dialect):
	shortcuts = {
		'cc:ie': {
			'opening_tag': '<!--[if IE]>',
			'closing_tag': '<![endif]-->'},
		'cc:ie6': {
			'opening_tag': '<!--[if lte IE 6]>',
			'closing_tag': '<![endif]-->'},
		'cc:ie7': {
			'opening_tag': '<!--[if lte IE 7]>',
			'closing_tag': '<![endif]-->'},
		'cc:noie': {
			'opening_tag': '<!--[if !IE]><!-->',
			'closing_tag': '<!--<![endif]-->'},
		'html:4t': {
			'expand': True,
			'opening_tag':
				'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' +
				'<html lang="en">\n' +
				'<head>\n' +
				'    ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
				'    ' + '<title></title>\n' + 
				'</head>\n' +
				'<body>',
			'closing_tag':
				'</body>\n' +
				'</html>'},
		'html:4s': {
			'expand': True,
			'opening_tag':
				'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' +
				'<html lang="en">\n' +
				'<head>\n' +
				'    ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
				'    ' + '<title></title>\n' + 
				'</head>\n' +
				'<body>',
			'closing_tag':
				'</body>\n' +
				'</html>'},
		'html:xt': {
			'expand': True,
			'opening_tag':
				'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' +
				'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
				'<head>\n' +
				'    ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
				'    ' + '<title></title>\n' + 
				'</head>\n' +
				'<body>',
			'closing_tag':
				'</body>\n' +
				'</html>'},
		'html:xs': {
			'expand': True,
			'opening_tag':
				'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
				'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
				'<head>\n' +
				'    ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
				'    ' + '<title></title>\n' + 
				'</head>\n' +
				'<body>',
			'closing_tag':
				'</body>\n' +
				'</html>'},
		'html:xxs': {
			'expand': True,
			'opening_tag':
				'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' +
				'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
				'<head>\n' +
				'    ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
				'    ' + '<title></title>\n' + 
				'</head>\n' +
				'<body>',
			'closing_tag':
				'</body>\n' +
				'</html>'},
		'html:5': {
			'expand': True,
			'opening_tag':
				'<!DOCTYPE html>\n' +
				'<html lang="en">\n' +
				'<head>\n' +
				'    ' + '<meta charset="UTF-8" />\n' +
				'    ' + '<title></title>\n' + 
				'</head>\n' +
				'<body>',
			'closing_tag':
				'</body>\n' +
				'</html>'},
		'input:button': {
			'name': 'input',
			'attributes': { 'class': 'button', 'type': 'button', 'name': '', 'value': '' }
			},
		'input:password': {
			'name': 'input',
			'attributes': { 'class': 'text password', 'type': 'password', 'name': '', 'value': '' }
			},
		'input:radio': {
			'name': 'input',
			'attributes': { 'class': 'radio', 'type': 'radio', 'name': '', 'value': '' }
			},
		'input:checkbox': {
			'name': 'input',
			'attributes': { 'class': 'checkbox', 'type': 'checkbox', 'name': '', 'value': '' }
			},
		'input:file': {
			'name': 'input',
			'attributes': { 'class': 'file', 'type': 'file', 'name': '', 'value': '' }
			},
		'input:text': {
			'name': 'input',
			'attributes': { 'class': 'text', 'type': 'text', 'name': '', 'value': '' }
			},
		'input:submit': {
			'name': 'input',
			'attributes': { 'class': 'submit', 'type': 'submit', 'value': '' }
			},
		'input:hidden': {
			'name': 'input',
			'attributes': { 'type': 'hidden', 'name': '', 'value': '' }
			},
		'script:src': {
			'name': 'script',
			'attributes': { 'src': '' }
			},
		'script:jquery': {
			'name': 'script',
			'attributes': { 'src': 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js' }
			},
		'script:jsapi': {
			'name': 'script',
			'attributes': { 'src': 'http://www.google.com/jsapi' }
			},
		'script:jsapix': {
			'name': 'script',
			'text': '\n    google.load("jquery", "1.3.2");\n    google.setOnLoadCallback(function() {\n        \n    });\n'
			},
		'link:css': {
			'name': 'link',
			'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'all' },
			},
		'link:print': {
			'name': 'link',
			'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'print' },
			},
		'link:favicon': {
			'name': 'link',
			'attributes': { 'rel': 'shortcut icon', 'type': 'image/x-icon', 'href': '' },
			},
		'link:touch': {
			'name': 'link',
			'attributes': { 'rel': 'apple-touch-icon', 'href': '' },
			},
		'link:rss': {
			'name': 'link',
			'attributes': { 'rel': 'alternate', 'type': 'application/rss+xml', 'title': 'RSS', 'href': '' },
			},
		'link:atom': {
			'name': 'link',
			'attributes': { 'rel': 'alternate', 'type': 'application/atom+xml', 'title': 'Atom', 'href': '' },
			},
		'meta:ie7': {
			'name': 'meta',
			'attributes': { 'http-equiv': 'X-UA-Compatible', 'content': 'IE=7' },
			},
		'meta:ie8': {
			'name': 'meta',
			'attributes': { 'http-equiv': 'X-UA-Compatible', 'content': 'IE=8' },
			},
		'form:get': {
			'name': 'form',
			'attributes': { 'method': 'get' },
			},
		'form:g': {
			'name': 'form',
			'attributes': { 'method': 'get' },
			},
		'form:post': {
			'name': 'form',
			'attributes': { 'method': 'post' },
			},
		'form:p': {
			'name': 'form',
			'attributes': { 'method': 'post' },
			},
		}
	synonyms = {
		'checkbox': 'input:checkbox',
		'check': 'input:checkbox',
		'input:c': 'input:checkbox',
		'button': 'input:button',
		'input:b': 'input:button',
		'input:h': 'input:hidden',
		'hidden': 'input:hidden',
		'submit': 'input:submit',
		'input:s': 'input:submit',
		'radio': 'input:radio',
		'input:r': 'input:radio',
		'text': 'input:text',
		'passwd': 'input:password',
		'password': 'input:password',
		'pw': 'input:password',
		'input:t': 'input:text',
		'linkcss': 'link:css',
		'scriptsrc': 'script:src',
		'jquery': 'script:jquery',
		'jsapi': 'script:jsapi',
		'html5': 'html:5',
		'html4': 'html:4s',
		'html4s': 'html:4s',
		'html4t': 'html:4t',
		'xhtml': 'html:xxs',
		'xhtmlt': 'html:xt',
		'xhtmls': 'html:xs',
		'xhtml11': 'html:xxs',
		'opt': 'option',
		'st': 'strong',
		'css': 'style',
		'csss': 'link:css',
		'css:src': 'link:css',
		'csssrc': 'link:css',
		'js': 'script',
		'jss': 'script:src',
		'js:src': 'script:src',
		'jssrc': 'script:src',
		}
	short_tags = (
		'area', 'base', 'basefont', 'br', 'embed', 'hr', \
		'input', 'img', 'link', 'param', 'meta')
	required = {
		'a':      {'href':''},
		'base':   {'href':''},
		'abbr':   {'title': ''},
		'acronym':{'title': ''},
		'bdo':    {'dir': ''},
		'link':   {'rel': 'stylesheet', 'href': ''},
		'style':  {'type': 'text/css'},
		'script': {'type': 'text/javascript'},
		'img':    {'src':'', 'alt':''},
		'iframe': {'src': '', 'frameborder': '0'},
		'embed':  {'src': '', 'type': ''},
		'object': {'data': '', 'type': ''},
		'param':  {'name': '', 'value': ''},
		'form':   {'action': '', 'method': 'post'},
		'table':  {'cellspacing': '0'},
		'input':  {'type': '', 'name': '', 'value': ''},
		'base':   {'href': ''},
		'area':   {'shape': '', 'coords': '', 'href': '', 'alt': ''},
		'select': {'name': ''},
		'option': {'value': ''},
		'textarea':{'name': ''},
		'meta':   {'content': ''},
	}

class Parser:
	"""The parser.
	"""

	# Constructor
	# --------------------------------------------------------------------------- 

	def __init__(self, options=None, str='', dialect=HtmlDialect()):
		"""Constructor.
		"""

		self.tokens = []
		self.str = str
		self.options = options
		self.dialect = dialect
		self.root = Element(parser=self)
		self.caret = []
		self.caret.append(self.root)
		self._last = []

	# Methods 
	# --------------------------------------------------------------------------- 

	def load_string(self, str):
		"""Loads a string to parse.
		"""

		self.str = str
		self._tokenize()
		self._parse()

	def render(self):
		"""Renders.
		Called by [[Router]].
		"""

		# Get the initial render of the root node
		output = self.root.render()

		# Indent by whatever the input is indented with
		indent = re.findall("^[\r\n]*(\s*)", self.str)[0]
		output = indent + output.replace("\n", "\n" + indent)

		# Strip newline if not needed
		if self.options.has("no-last-newline") \
			or self.prefix or self.suffix:
			output = re.sub(r'\n\s*$', '', output)

		# TextMate mode
		if self.options.has("textmate"):
			output = self._textmatify(output)

		return output

	# Protected methods 
	# --------------------------------------------------------------------------- 

	def _textmatify(self, output):
		"""Returns a version of the output with TextMate placeholders in it.
		"""

		matches = re.findall(r'(></)|("")|(\n\s+)\n|(.|\s)', output)
		output = ''
		n = 1
		for i in matches:
			if i[0]:
				output += '>$%i</' % n
				n += 1
			elif i[1]:
				output += '"$%i"' % n
				n += 1
			elif i[2]:
				output += i[2] + '$%i\n' % n
				n += 1
			elif i[3]:
				output += i[3]
		output += "$0"
		return output

	def _tokenize(self):
		"""Tokenizes.
		Initializes [[self.tokens]].
		"""

		str = self.str.strip()

		# Find prefix/suffix
		while True:
			match = re.match(r"^(\s*<[^>]+>\s*)", str)
			if match is None: break
			if self.prefix is None: self.prefix = ''
			self.prefix += match.group(0)
			str = str[len(match.group(0)):]

		while True:
			match = re.findall(r"(\s*<[^>]+>[\s\n\r]*)$", str)
			if not match: break
			if self.suffix is None: self.suffix = ''
			self.suffix = match[0] + self.suffix
			str = str[:-len(match[0])]

		# Split by the element separators
		for token in re.split('(<|>|\+(?!\\s*\+|$))', str):
			if token.strip() != '':
				self.tokens.append(Token(token, parser=self))

	def _parse(self):
		"""Takes the tokens and does its thing.
		Populates [[self.root]].
		"""

		# Carry it over to the root node.
		if self.prefix or self.suffix:
			self.root.prefix = self.prefix
			self.root.suffix = self.suffix
			self.root.depth += 1

		for token in self.tokens:
			if token.type == Token.ELEMENT:
				# Reset the "last elements added" list. We will
				# repopulate this with the new elements added now.
				self._last[:] = []

				# Create [[Element]]s from a [[Token]].
				# They will be created as many as the multiplier specifies,
				# multiplied by how many carets we have
				count = 0
				for caret in self.caret:
					local_count = 0
					for i in range(token.multiplier):
						count += 1
						local_count += 1
						new = Element(token, caret,
								count = count,
								local_count = local_count,
								parser = self)
						self._last.append(new)
						caret.append(new)

			# For >
			elif token.type == Token.CHILD:
				# The last children added.
				self.caret[:] = self._last

			# For <
			elif token.type == Token.PARENT:
				# If we're the root node, don't do anything
				parent = self.caret[0].parent
				if parent is not None:
					self.caret[:] = [parent]
		return

	# Properties
	# --------------------------------------------------------------------------- 

	# Property: dialect
	# The dialect of XML
	dialect = None

	# Property: str
	# The string
	str = ''

	# Property: tokens
	# The list of tokens
	tokens = []

	# Property: options
	# Reference to the [[Options]] instance
	options = None

	# Property: root
	# The root [[Element]] node.
	root = None 

	# Property: caret
	# The current insertion point.
	caret = None

	# Property: _last
	# List of the last appended stuff
	_last = None

	# Property: indent
	# Yeah
	indent = ''

	# Property: prefix
	# (String) The trailing tag in the beginning.
	#
	# Description:
	# For instance, in `<div>ul>li</div>`, the `prefix` is `<div>`.
	prefix = ''

	# Property: suffix
	# (string) The trailing tag at the end.
	suffix = ''
	pass

# =============================================================================== 

class Element:
	"""An element.
	"""

	def __init__(self, token=None, parent=None, count=None, local_count=None, \
				parser=None, opening_tag=None, closing_tag=None, \
				attributes=None, name=None, text=None):
		"""Constructor.

		This is called by ???.

		Description:
		All parameters are optional.

		token       - (Token) The token (required)
		parent      - (Element) Parent element; `None` if root
		count       - (Int) The number to substitute for `&` (e.g., in `li.item-$`)
		local_count - (Int) The number to substitute for `$` (e.g., in `li.item-&`)
		parser      - (Parser) The parser

		attributes  - ...
		name        - ...
		text        - ...
		"""

		self.children = []
		self.attributes = {}
		self.parser = parser

		if token is not None:
			# Assumption is that token is of type [[Token]] and is
			# a [[Token.ELEMENT]].
			self.name        = token.name
			self.attributes  = token.attributes.copy()
			self.text        = token.text
			self.populate    = token.populate
			self.expand      = token.expand
			self.opening_tag = token.opening_tag
			self.closing_tag = token.closing_tag

		# `count` can be given. This will substitude & in classname and ID
		if count is not None:
			for key in self.attributes:
				attrib = self.attributes[key]
				attrib = attrib.replace('&', ("%i" % count))
				if local_count is not None:
					attrib = attrib.replace('$', ("%i" % local_count))
				self.attributes[key] = attrib

		# Copy over from parameters
		if attributes: self.attributes = attribues
		if name:       self.name       = name
		if text:       self.text       = text

		self._fill_attributes()

		self.parent = parent
		if parent is not None:
			self.depth = parent.depth + 1

		if self.populate: self._populate()

	def render(self):
		"""Renders the element, along with it's subelements, into HTML code.

		[Grouped under "Rendering methods"]
		"""

		output = ""
		try:    spaces_count = int(self.parser.options.options['indent-spaces'])
		except: spaces_count = 4
		spaces = ' ' * spaces_count
		indent = self.depth * spaces
		
		prefix, suffix = ('', '')
		if self.prefix: prefix = self.prefix + "\n"
		if self.suffix: suffix = self.suffix

		# Make the guide from the ID (/#header), or the class if there's no ID (/.item)
		# This is for the start-guide, end-guide and post-tag-guides
		guide_str = ''
		if 'id' in self.attributes:
			guide_str += "#%s" % self.attributes['id']
		elif 'class' in self.attributes:
			guide_str += ".%s" % self.attributes['class'].replace(' ', '.')

		# Build the post-tag guide (e.g., </div><!-- /#header -->),
		# the start guide, and the end guide.
		guide = ''
		start_guide = ''
		end_guide = ''
		if ((self.name == 'div') and \
			(('id' in self.attributes) or ('class' in self.attributes))):

			if (self.parser.options.has('post-tag-guides')):
				guide = "<!-- /%s -->" % guide_str

			if (self.parser.options.has('start-guide-format')):
				format = self.parser.options.get('start-guide-format')
				try: start_guide = format % guide_str
				except: start_guide = (format + " " + guide_str).strip()
				start_guide = "%s<!-- %s -->\n" % (indent, start_guide)

			if (self.parser.options.has('end-guide-format')):
				format = self.parser.options.get('end-guide-format')
				try: end_guide = format % guide_str
				except: end_guide = (format + " " + guide_str).strip()
				end_guide = "\n%s<!-- %s -->" % (indent, end_guide)

		# Short, self-closing tags (<br />)
		short_tags = self.parser.dialect.short_tags

		# When it should be expanded..
		# (That is, <div>\n...\n</div> or similar -- wherein something must go
		# inside the opening/closing tags)
		if  len(self.children) > 0 \
			or self.expand \
			or prefix or suffix \
			or (self.parser.options.has('expand-divs') and self.name == 'div'):

			for child in self.children:
				output += child.render()

			# For expand divs: if there are no children (that is, `output`
			# is still blank despite above), fill it with a blank line.
			if (output == ''): output = indent + spaces + "\n"

			# If we're a root node and we have a prefix or suffix...
			# (Only the root node can have a prefix or suffix.)
			if prefix or suffix:
				output = "%s%s%s%s%s\n" % \
					(indent, prefix, output, suffix, guide)

			# Uh..
			elif self.name != '' or \
				self.opening_tag is not None or \
				self.closing_tag is not None:
				output = start_guide + \
						indent + self.get_opening_tag() + "\n" + \
						output + \
						indent + self.get_closing_tag() + \
						guide + end_guide + "\n"
			

		# Short, self-closing tags (<br />)
		elif self.name in short_tags: 
			output = "%s<%s />\n" % (indent, self.get_default_tag())

		# Tags with text, possibly
		elif self.name != '' or \
			self.opening_tag is not None or \
			self.closing_tag is not None:
			output = "%s%s%s%s%s%s%s%s" % \
				(start_guide, indent, self.get_opening_tag(), \
				self.text, \
				self.get_closing_tag(), \
				guide, end_guide, "\n")

		# Else, it's an empty-named element (like the root). Pass.
		else: pass


		return output

	def get_default_tag(self):
		"""Returns the opening tag (without brackets).

		Usage:
			element.get_default_tag()

		[Grouped under "Rendering methods"]
		"""

		output = '%s' % (self.name)
		for key, value in self.attributes.iteritems():
			output += ' %s="%s"' % (key, value)
		return output

	def get_opening_tag(self):
		if self.opening_tag is None:
			return "<%s>" % self.get_default_tag()
		else:
			return self.opening_tag

	def get_closing_tag(self):
		if self.closing_tag is None:
			return "</%s>" % self.name
		else:
			return self.closing_tag

	def append(self, object):
		"""Registers an element as a child of this element.

		Usage:
			element.append(child)

		Description:
		Adds a given element `child` to the children list of this element. It
		will be rendered when [[render()]] is called on the element.

		See also:
		- [[get_last_child()]]

		[Grouped under "Traversion methods"]
		"""

		self.children.append(object)

	def get_last_child(self):
		"""Returns the last child element which was [[append()]]ed to this element.

		Usage:
			element.get_last_child()

		Description:
		This is the same as using `element.children[-1]`.

		[Grouped under "Traversion methods"]
		"""

		return self.children[-1]

	def _populate(self):
		"""Expands with default items.

		This is called when the [[populate]] flag is turned on.
		"""

		if self.name == 'ul':
			elements = [Element(name='li', parent=self, parser=self.parser)]

		elif self.name == 'dl':
			elements = [
				Element(name='dt', parent=self, parser=self.parser),
				Element(name='dd', parent=self, parser=self.parser)]

		elif self.name == 'table':
			tr = Element(name='tr', parent=self, parser=self.parser)
			td = Element(name='td', parent=tr, parser=self.parser)
			tr.children.append(td)
			elements = [tr]

		else:
			elements = []

		for el in elements:
			self.children.append(el)

	def _fill_attributes(self):
		"""Fills default attributes for certain elements.

		Description:
		This is called by the constructor.

		[Protected, grouped under "Protected methods"]
		"""

		# Make sure <a>'s have a href, <img>'s have an src, etc.
		required = self.parser.dialect.required

		for element, attribs in required.iteritems():
			if self.name == element:
				for attrib in attribs:
					if attrib not in self.attributes:
						self.attributes[attrib] = attribs[attrib]

	# ---------------------------------------------------------------------------

	# Property: last_child
	# [Read-only]
	last_child = property(get_last_child)

	# ---------------------------------------------------------------------------

	# Property: parent
	# (Element) The parent element.
	parent = None

	# Property: name
	# (String) The name of the element (e.g., `div`)
	name = ''

	# Property: attributes
	# (Dict) The dictionary of attributes (e.g., `{'src': 'image.jpg'}`)
	attributes = None

	# Property: children
	# (List of Elements) The children
	children = None

	# Property: opening_tag
	# (String or None) The opening tag. Optional; will use `name` and
	# `attributes` if this is not given.
	opening_tag = None

	# Property: closing_tag
	# (String or None) The closing tag
	closing_tag = None

	text = ''
	depth = -1
	expand = False
	populate = False
	parser = None

	# Property: prefix
	# Only the root note can have this.
	prefix = None
	suffix = None

# =============================================================================== 

class Token:
	def __init__(self, str, parser=None):
		"""Token.

		Description:
		str   - The string to parse

		In the string `div > ul`, there are 3 tokens. (`div`, `>`, and `ul`)

		For `>`, it will be a `Token` with `type` set to `Token.CHILD`
		"""

		self.str = str.strip()
		self.attributes = {}
		self.parser = parser

		# Set the type.
		if self.str == '<':
			self.type = Token.PARENT
		elif self.str == '>':
			self.type = Token.CHILD
		elif self.str == '+':
			self.type = Token.SIBLING
		else:
			self.type = Token.ELEMENT
			self._init_element()
		
	def _init_element(self):
		"""Initializes. Only called if the token is an element token.
		[Private]
		"""

		# Get the tag name. Default to DIV if none given.
		name = re.findall('^([\w\-:]*)', self.str)[0]
		name = name.lower().replace('-', ':')

		# Find synonyms through this thesaurus
		synonyms = self.parser.dialect.synonyms
		if name in synonyms.keys():
			name = synonyms[name]

		if ':' in name:
			try:    spaces_count = int(self.parser.options.get('indent-spaces'))
			except: spaces_count = 4
			indent = ' ' * spaces_count

			shortcuts = self.parser.dialect.shortcuts
			if name in shortcuts.keys():
				for key, value in shortcuts[name].iteritems():
					setattr(self, key, value)
				if 'html' in name:
					return
			else:
				self.name = name

		elif (name == ''): self.name = 'div'
		else: self.name = name

		# Look for attributes
		attribs = []
		for attrib in re.findall('\[([^\]]*)\]', self.str):
			attribs.append(attrib)
			self.str = self.str.replace("[" + attrib + "]", "")
		if len(attribs) > 0:
			for attrib in attribs:
				try:    key, value = attrib.split('=', 1)
				except: key, value = attrib, ''
				self.attributes[key] = value

		# Try looking for text
		text = None
		for text in re.findall('\{([^\}]*)\}', self.str):
			self.str = self.str.replace("{" + text + "}", "")
		if text is not None:
			self.text = text

		# Get the class names
		classes = []
		for classname in re.findall('\.([\$a-zA-Z0-9_\-\&]+)', self.str):
			classes.append(classname)
		if len(classes) > 0:
			try:    self.attributes['class']
			except: self.attributes['class'] = ''
			self.attributes['class'] += ' ' + ' '.join(classes)
			self.attributes['class'] = self.attributes['class'].strip()

		# Get the ID
		id = None
		for id in re.findall('#([\$a-zA-Z0-9_\-\&]+)', self.str): pass
		if id is not None:
			self.attributes['id'] = id

		# See if there's a multiplier (e.g., "li*3")
		multiplier = None
		for multiplier in re.findall('\*\s*([0-9]+)', self.str): pass
		if multiplier is not None:
			self.multiplier = int(multiplier)

		# Populate flag (e.g., ul+)
		flags = None
		for flags in re.findall('[\+\!]+$', self.str): pass
		if flags is not None:
			if '+' in flags: self.populate = True
			if '!' in flags: self.expand = True

	def __str__(self):
		return self.str 

	str = ''
	parser = None

	# For elements
	# See the properties of `Element` for description on these.
	name = ''
	attributes = None
	multiplier = 1
	expand = False
	populate = False
	text = ''
	opening_tag = None
	closing_tag = None

	# Type
	type = 0
	ELEMENT = 2 
	CHILD = 4
	PARENT = 8
	SIBLING = 16

# =============================================================================== 

class Router:
	"""The router.
	"""

	# Constructor 
	# --------------------------------------------------------------------------- 

	def __init__(self):
		pass

	# Methods 
	# --------------------------------------------------------------------------- 

	def start(self, options=None, str=None, ret=None):
		if (options):
			self.options = Options(router=self, options=options, argv=None)
		else:
			self.options = Options(router=self, argv=sys.argv[1:], options=None)

		if (self.options.has('help')):
			return self.help()

		elif (self.options.has('version')):
			return self.version()

		else:
			return self.parse(str=str, ret=ret)
	
	def help(self):
		print "Usage: %s [OPTIONS]" % sys.argv[0]
		print "Expands input into HTML."
		print ""
		for short, long, info in self.options.cmdline_keys:
			if "Deprecated" in info: continue 
			if not short == '': short = '-%s,' % short
			if not long  == '': long  = '--%s' % long.replace("=", "=XXX")

			print "%6s %-25s %s" % (short, long, info)
		print ""
		print "\n".join(self.help_content)

	def version(self):
		print "Uhm, yeah."

	def parse(self, str=None, ret=None):
		self.parser = Parser(self.options)

		try:
			# Read the files
			# for line in fileinput.input(): lines.append(line.rstrip(os.linesep))
			if str is not None:
				lines = str
			else:
				lines = [sys.stdin.read()]
				lines = " ".join(lines)

		except KeyboardInterrupt:
			pass

		except:
			sys.stderr.write("Reading failed.\n")
			return
			
		try:
			self.parser.load_string(lines)
			output = self.parser.render()
			if ret: return output
			sys.stdout.write(output)

		except:
			sys.stderr.write("Parse error. Check your input.\n")
			print sys.exc_info()[0]
			print sys.exc_info()[1]

	def exit(self):
		sys.exit()

	help_content = [
		"Please refer to the manual for more information.",
	]

# =============================================================================== 

class Options:
	def __init__(self, router, argv, options=None):
		# Init self
		self.router = router

		# `options` can be given as a dict of stuff to preload
		if options:
			for k, v in options.iteritems():
				self.options[k] = v
			return

		# Prepare for getopt()
		short_keys, long_keys = "", []
		for short, long, info in self.cmdline_keys: # 'v', 'version'
			short_keys += short
			long_keys.append(long)

		try:
			getoptions, arguments = getopt.getopt(argv, short_keys, long_keys)

		except getopt.GetoptError:
			err = sys.exc_info()[1]
			sys.stderr.write("Options error: %s\n" % err)
			sys.stderr.write("Try --help for a list of arguments.\n")
			return router.exit()

		# Sort them out into options
		options = {}
		i = 0
		for option in getoptions:
			key, value = option # '--version', ''
			if (value == ''): value = True

			# If the key is long, write it
			if key[0:2] == '--':
				clean_key = key[2:]
				options[clean_key] = value

			# If the key is short, look for the long version of it
			elif key[0:1] == '-':
				for short, long, info in self.cmdline_keys:
					if short == key[1:]:
						print long
						options[long] = True

		# Done
		for k, v in options.iteritems():
			self.options[k] = v

	def __getattr__(self, attr):
		return self.get(attr)

	def get(self, attr):
		try:    return self.options[attr]
		except: return None

	def has(self, attr):
		try:    return self.options.has_key(attr)
		except: return False

	options = {
		'indent-spaces': 4
	}
	cmdline_keys = [
		('h', 'help', 'Shows help'),
		('v', 'version', 'Shows the version'),
		('', 'no-guides', 'Deprecated'),
		('', 'post-tag-guides', 'Adds comments at the end of DIV tags'),
		('', 'textmate', 'Adds snippet info (textmate mode)'),
		('', 'indent-spaces=', 'Indent spaces'),
		('', 'expand-divs', 'Automatically expand divs'),
		('', 'no-last-newline', 'Skip the trailing newline'),
		('', 'start-guide-format=', 'To be documented'),
		('', 'end-guide-format=', 'To be documented'),
	]
	
	# Property: router
	# Router
	router = 1

# =============================================================================== 

if __name__ == "__main__":
	z = Router()
	z.start()
