"""Public Suffix List support for Python.
"""

import os.path

class PublicSuffixList(object):
	def __init__(self, input_file=None):
		"""Reads and parses public suffix list.
		
		input_file is an iterable object that returns lines of a
		public suffix list file (standard Python file-like objects
		work). If input_file is None, a file named
		"publicsuffix.txt" in the same directory as this Python
		module is used.
		
		The file format is described at http://publicsuffix.org/list/
		"""

		if input_file is None:
			input_path = os.path.join(os.path.dirname(__file__), 'publicsuffix.txt')
			input_file = file(input_path)

		root = self._build_structure(input_file)
		self.root = self._simplify(root)

	def _find_node(self, parent, parts):
		if not parts:
			return parent

		if len(parent) == 1:
			parent.append({})

		assert len(parent) == 2
		negate, children = parent

		child = parts.pop()
		try:
			child = child.encode('ascii')
		except:
			pass

		child_node = children.get(child, None)

		if not child_node:
			children[child] = child_node = [0]

		return self._find_node(child_node, parts)

	def _add_rule(self, root, rule):
		if rule.startswith('!'):
			negate = 1
			rule = rule[1:]
		else:
			negate = 0

		parts = rule.split('.')
		self._find_node(root, parts)[0] = negate

	def _simplify(self, node):
		if len(node) == 1:
			return node[0]

		return (node[0], dict((k, self._simplify(v)) for (k, v) in node[1].iteritems()))

	def _build_structure(self, fp):
		root = [0]

		for line in fp:
			line = line.decode('utf-8').strip()
			if line.startswith('//') or not line:
				continue

			self._add_rule(root, line.split()[0].lstrip('.'))

		return root

	def _lookup_node(self, matches, depth, parent, parts):
		if parent in (0, 1):
			negate = parent
			children = None
		else:
			negate, children = parent

		matches[-depth] = matches[-depth] or negate

		if depth < len(parts) and children:
			for name in (parts[-depth], '*'):
				child = children.get(name, None)
				if child is not None:
					self._lookup_node(matches, depth+1, child, parts)

	def get_public_suffix(self, domain):
		"""get_public_suffix("www.example.com") -> "example.com"

		Calling this function with a DNS name will return the
		public suffix for that name.
		"""

		parts = domain.lower().lstrip('.').split('.')
		hits = [None] * len(parts)

		self._lookup_node(hits, 1, self.root, parts)

		for i, what in enumerate(hits):
			if what is not None:
				return '.'.join(parts[i:])

"""Public Suffix List support for Python.
"""

import os.path

class PublicSuffixList(object):
	def __init__(self, input_file=None):
		"""Reads and parses public suffix list.
		
		input_file is an iterable object that returns lines of a
		public suffix list file (standard Python file-like objects
		work). If input_file is None, a file named
		"publicsuffix.txt" in the same directory as this Python
		module is used.
		
		The file format is described at http://publicsuffix.org/list/
		"""

		if input_file is None:
			input_path = os.path.join(os.path.dirname(__file__), 'publicsuffix.txt')
			input_file = file(input_path)

		root = self._build_structure(input_file)
		self.root = self._simplify(root)

	def _find_node(self, parent, parts):
		if not parts:
			return parent

		if len(parent) == 1:
			parent.append({})

		assert len(parent) == 2
		negate, children = parent

		child = parts.pop()
		try:
			child = child.encode('ascii')
		except:
			pass

		child_node = children.get(child, None)

		if not child_node:
			children[child] = child_node = [0]

		return self._find_node(child_node, parts)

	def _add_rule(self, root, rule):
		if rule.startswith('!'):
			negate = 1
			rule = rule[1:]
		else:
			negate = 0

		parts = rule.split('.')
		self._find_node(root, parts)[0] = negate

	def _simplify(self, node):
		if len(node) == 1:
			return node[0]

		return (node[0], dict((k, self._simplify(v)) for (k, v) in node[1].iteritems()))

	def _build_structure(self, fp):
		root = [0]

		for line in fp:
			line = line.decode('utf-8').strip()
			if line.startswith('//') or not line:
				continue

			self._add_rule(root, line.split()[0].lstrip('.'))

		return root

	def _lookup_node(self, matches, depth, parent, parts):
		if parent in (0, 1):
			negate = parent
			children = None
		else:
			negate, children = parent

		matches[-depth] = negate

		if depth < len(parts) and children:
			for name in ('*', parts[-depth]):
				child = children.get(name, None)
				if child is not None:
					self._lookup_node(matches, depth+1, child, parts)

	def get_public_suffix(self, domain):
		"""get_public_suffix("www.example.com") -> "example.com"

		Calling this function with a DNS name will return the
		public suffix for that name.
		"""

		parts = domain.lower().lstrip('.').split('.')
		hits = [None] * len(parts)

		self._lookup_node(hits, 1, self.root, parts)

		for i, what in enumerate(hits):
			if what is not None and what == 0:
				return '.'.join(parts[i:])

Fix for exception to the wildcard rule.