#!/usr/local/bin/python2.7 python
#-
# Copyright (c) 2010-2015 Varnish Software AS
# All rights reserved.
#
# Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

"""
Read the vmod.vcc file (inputvcc) and produce:
	vmod_if.h -- Prototypes for the implementation
	vmod_if.c -- Magic glue & datastructures to make things a VMOD.
	vmod_${name}.rst -- Extracted documentation
"""

# This script should work with both Python 2 and Python 3.
from __future__ import print_function

import sys
import re
import optparse
import unittest
import random
from os import fdopen, rename, unlink
from os.path import dirname, exists, join, realpath
from pprint import pprint, pformat
from tempfile import mkstemp

ctypes = {
	'ACL':		"VCL_ACL",
	'BACKEND':	"VCL_BACKEND",
	'BLOB':		"VCL_BLOB",
	'BOOL':		"VCL_BOOL",
	'BYTES':	"VCL_BYTES",
	'DURATION':	"VCL_DURATION",
	'ENUM':		"VCL_ENUM",
	'HEADER':	"VCL_HEADER",
	'HTTP':		"VCL_HTTP",
	'INT':		"VCL_INT",
	'IP':		"VCL_IP",
	'PRIV_CALL':	"struct vmod_priv *",
	'PRIV_VCL':	"struct vmod_priv *",
	'PRIV_TASK':	"struct vmod_priv *",
	'PRIV_TOP':	"struct vmod_priv *",
	'PROBE':	"VCL_PROBE",
	'REAL':		"VCL_REAL",
	'STRING':	"VCL_STRING",
	'STRING_LIST':	"const char *, ...",
	'TIME':		"VCL_TIME",
	'VOID':		"VCL_VOID",
}

#######################################################################

def write_file_warning(fo, a, b, c):
	fo.write(a + "\n")
	fo.write(b + " NB:  This file is machine generated, DO NOT EDIT!\n")
	fo.write(b + "\n")
	fo.write(b + " Edit vmod.vcc and run make instead\n")
	fo.write(c + "\n\n")

def write_c_file_warning(fo):
	write_file_warning(fo, "/*", " *", " */")

def write_rst_file_warning(fo):
	write_file_warning(fo, "..", "..", "..")

#######################################################################

def lwrap(s, w=72):
	"""
	Wrap a c-prototype like string into a number of lines
	"""
	l = []
	p = ""
	while len(s) > w:
		y = s[:w].rfind(',')
		if y == -1:
			y = s[:w].rfind('(')
		if y == -1:
			break
		l.append(p + s[:y + 1])
		s = s[y + 1:].lstrip()
		p = "    "
	if len(s) > 0:
		l.append(p + s)
	return l

def quote(s):
	t = ""
	for i in s:
		if i == '"':
			t += '\\"'
		else:
			t += i
	return t

#######################################################################

def is_c_name(s):
	return None != re.match("^[a-zA-Z][a-zA-Z0-9_]*$", s)


class ParseError(Exception):
	"An error reading the input file."
	pass

class FormatError(Exception):
	"""
		Raised if the content of the (otherwise well-formed) input file
		is invalid.
	"""
	def __init__(self, msg, details):
		self.msg = msg
		self.details = details
		Exception.__init__(self)


#######################################################################

class Token(object):
	def __init__(self, ln, ch, tokstr):
		self.ln = ln
		self.ch = ch
		self.str = tokstr

	def __repr__(self):
		return "<@%d \"%s\">" % (self.ln, self.str)

#######################################################################

class Vmod(object):
	def __init__(self, nam, dnam, sec):
		if not is_c_name(nam):
			raise ParseError("Module name '%s' is illegal" % nam)
		self.nam = nam
		self.dnam = dnam
		self.sec = sec
		self.event = None
		self.funcs = list()
		self.objs = list()
		self.doc_str = []
		self.doc_order = []

	def set_event(self, nam):
		if self.event != None:
			raise ParseError("Module %s already has $Event" %
			    self.nam)
		if not is_c_name(nam):
			raise ParseError("$Event name '%s' is illegal" % nam)
		self.event = nam

	def add_func(self, fn):
		self.funcs.append(fn)
		self.doc_order.append(fn)

	def add_obj(self, o):
		self.objs.append(o)
		self.doc_order.append(o)

	def c_proto(self, fo):
		for o in self.objs:
			fo.write("/* Object %s */\n" % o.nam)
			o.fixup(self.nam)
			o.c_proto(fo)
			fo.write("\n")
		if len(self.funcs) > 0:
			fo.write("/* Functions */\n")
		for f in self.funcs:
			for i in lwrap(f.c_proto()):
				fo.write(i + "\n")
		if self.event != None:
			fo.write("\n")
			fo.write("#ifdef VCL_MET_MAX\n")
			fo.write("vmod_event_f " + self.event + ";\n")
			fo.write("#endif\n")

	def c_typedefs_(self):
		l = list()
		for o in self.objs:
			for t in o.c_typedefs(self.nam):
				l.append(t)
			l.append("")
		if len(self.funcs) > 0:
			l.append("/* Functions */")
		for f in self.funcs:
			l.append(f.c_typedef(self.nam))
		l.append("")
		return l

	def c_typedefs(self, fo):
		for i in self.c_typedefs_():
			for j in lwrap(i):
				fo.write(j + "\n")

	def c_vmod(self, fo):

		cs = self.c_struct()
		fo.write(cs + ';\n')

		vfn = 'Vmod_%s_Func' % self.nam

		fo.write("/*lint -esym(754, %s::*) */\n" % vfn)
		fo.write("\nstatic const struct %s Vmod_Func = " % vfn)
		fo.write(self.c_initializer())
		fo.write("\n")

		fo.write("\nstatic const char Vmod_Proto[] =\n")
		for t in self.c_typedefs_():
			for i in lwrap(t, w=64):
				fo.write('\t"' + i + '\\n"\n')
		fo.write('\t"\\n"\n')
		for i in (cs + ";").split("\n"):
			fo.write('\n\t"' + i + '\\n"')
		fo.write('\n\t"static struct ' + vfn + " " + vfn + ';";\n\n')

		fo.write(self.c_strspec())

		fo.write("\n")

		nm = "Vmod_" + self.nam + "_Data"
		fo.write("/*lint -esym(759, %s) */\n" % nm)
		fo.write("const struct vmod_data " + nm + " = {\n")
		fo.write("\t.vrt_major = VRT_MAJOR_VERSION,\n");
		fo.write("\t.vrt_minor = VRT_MINOR_VERSION,\n");
		fo.write("\t.name = \"%s\",\n" % self.nam)
		fo.write("\t.func = &Vmod_Func,\n")
		fo.write("\t.func_len = sizeof(Vmod_Func),\n")
		fo.write("\t.proto = Vmod_Proto,\n")
		fo.write("\t.spec = Vmod_Spec,\n")
		fo.write("\t.abi = VMOD_ABI_Version,\n")

		# NB: Sort of hackish:
		# Fill file_id with random stuff, so we can tell if
		# VCC and VRT_Vmod_Init() dlopens the same file
		#
		fo.write("\t.file_id = \"")
		for i in range(32):
			fo.write("%c" % random.randint(0x40,0x5a))
		fo.write("\",\n")
		fo.write("};\n")

	def c_initializer(self):
		s = '{\n'
		for o in self.objs:
			s += o.c_initializer()

		s += "\n\t/* Functions */\n"
		for f in self.funcs:
			s += f.c_initializer()

		s += "\n\t/* Init/Fini */\n"
		if self.event != None:
			s += "\t" + self.event + ",\n"
		s += "};"

		return s

	def c_struct(self):
		s = 'struct Vmod_' + self.nam + '_Func {\n'
		for o in self.objs:
			s += o.c_struct(self.nam)

		s += "\n\t/* Functions */\n"
		for f in self.funcs:
			s += f.c_struct(self.nam)

		s += "\n\t/* Init/Fini */\n"
		if self.event != None:
			s += "\tvmod_event_f\t*_event;\n"
		s += '}'
		return s

	def c_strspec(self):
		s = "/*lint -save -e786 -e840 */\n"
		s += "static const char * const Vmod_Spec[] = {\n"

		for o in self.objs:
			s += o.c_strspec(self.nam) + ",\n\n"

		if len(self.funcs) > 0:
			s += "\t/* Functions */\n"
		for f in self.funcs:
			s += f.c_strspec(self.nam) + ',\n\n'

		if self.event != None:
			s += "\t/* Init/Fini */\n"
			s += '\t"$EVENT\\0Vmod_' + self.nam + '_Func._event",\n'

		s += "\t0\n"
		s += "};\n"
		s += "/*lint -restore */\n"
		return s

	def doc(self, l):
		self.doc_str.append(l)

	def doc_dump(self, fo, suf):
		fo.write(".. role:: ref(emphasis)\n\n")
		i = "vmod_" + self.nam
		fo.write(".. _" + i + "(" + self.sec + "):\n\n")
		fo.write("=" * len(i) + "\n")
		fo.write(i + "\n")
		fo.write("=" * len(i) + "\n")
		fo.write("\n")
		i = self.dnam
		fo.write("-" * len(i) + "\n")
		fo.write(i + "\n")
		fo.write("-" * len(i) + "\n")
		fo.write("\n")
		fo.write(":Manual section: %s\n" % self.sec)
		fo.write("\n")
		fo.write("SYNOPSIS\n")
		fo.write("========\n")
		fo.write("\n")
		fo.write("import %s [from \"path\"] ;\n" % self.nam)
		fo.write("\n")
		for i in self.doc_str:
			fo.write(i + "\n")
		fo.write("CONTENTS\n")
		fo.write("========\n")
		fo.write("\n")
		l = []
		for i in self.funcs:
			l.append(i.doc_idx(suf))
		for i in self.objs:
			l += i.doc_idx(suf)
		l.sort()
		for i in l:
			fo.write("* " + i[1] + "\n")
		fo.write("\n")
		for i in self.doc_order:
			i.doc_dump(fo)

#######################################################################

class Func(object):
	def __init__(self, nam, retval, al):
		#if not is_c_name(nam):
		#	raise Exception("Func name '%s' is illegal" % nam)
		if retval not in ctypes:
			raise TypeError(
			    "Return type '%s' not a valid type", retval)
		self.nam = nam
		self.cnam = nam.replace(".", "_")
		self.al = al
		self.retval = retval
		self.pfx = None
		self.doc_str = []

	def __repr__(self):
		return "<FUNC %s %s>" % (self.retval, self.nam)

	def set_pfx(self, s):
		self.pfx = s

	def c_proto(self, fini=False):
		s = ctypes[self.retval] + " vmod_" + self.cnam + "("
		p = ""
		if not fini:
			s += "VRT_CTX"
			p = ", "
		if self.pfx != None:
			s += p + self.pfx
			p = ", "
		for a in self.al:
			s += p + ctypes[a.typ]
			p = ", "
		s += ");"
		return s

	def c_typedef(self, modname, fini=False):
		s = "typedef "
		s += ctypes[self.retval]
		s += " td_" + modname + "_" + self.cnam + "("
		p = ""
		if not fini:
			s += "VRT_CTX"
			p = ", "
		if self.pfx != None:
			s += p + self.pfx
			p = ", "
		for a in self.al:
			s += p + ctypes[a.typ]
			p = ", "
		s += ");"
		return s

	def c_struct(self, modname):
		s = '\ttd_' + modname + "_" + self.cnam
		if len(s.expandtabs()) >= 40:
			s += "\n\t\t\t\t\t"
		else:
			while len(s.expandtabs()) < 40:
				s += "\t"
		s += "*" + self.cnam + ";\n"
		return s

	def c_initializer(self):
		return "\tvmod_" + self.cnam + ",\n"

	def c_strspec(self, modnam, pfx="\t"):
		s = pfx + '"' + modnam + "." + self.nam + '\\0"\n'
		s += pfx + '"'
		s += "Vmod_" + modnam + "_Func." + self.cnam + '\\0"\n'
		s += pfx + '    "' + self.retval + '\\0"\n'
		for a in self.al:
			s += pfx + '\t"' + a.c_strspec() + '"\n'
		s += pfx + '"\\0"'
		return s

	def doc(self, l):
		self.doc_str.append(l)

	def doc_proto(self):
		s = self.retval + " " + self.nam + "("
		d = ""
		for i in self.al:
			s += d + i.typ
			d = ", "
		s += ")"
		return s

	def doc_idx(self, suf):
		if suf == "":
			return (self.nam, ":ref:`func_" + self.nam + "`")
		else:
			return (self.nam, self.doc_proto())

	def doc_dump(self, fo):
		s = self.doc_proto()
		fo.write(".. _func_" + self.nam + ":\n\n")
		fo.write(s + "\n")
		fo.write("-" * len(s) + "\n")
		fo.write("\n")
		fo.write("Prototype\n")
		s = "\t" + self.retval + " " + self.nam + "("
		d = ""
		for i in self.al:
			s += d + i.typ
			if i.nam != None:
				s += " " + i.nam
			d = ", "
		fo.write(s + ")\n")
		for i in self.doc_str:
			fo.write(i + "\n")


#######################################################################

class Obj(object):
	def __init__(self, nam):
		self.nam = nam
		self.init = None
		self.fini = None
		self.methods = list()
		self.doc_str = []
		self.st = None

	def fixup(self, modnam):
		assert self.nam != None
		self.st = "struct vmod_" + modnam + "_" + self.nam
		self.init.set_pfx(self.st + " **, const char *")
		self.fini.set_pfx(self.st + " **")
		for m in self.methods:
			m.set_pfx(self.st + " *")

	def set_init(self, f):
		self.init = f
		self.fini = Func(f.nam, "VOID", [])
		self.init.cnam += "__init"
		self.fini.cnam += "__fini"

	def add_method(self, m):
		self.methods.append(m)

	def c_typedefs(self, modnam):
		l = list()
		l.append("/* Object " + self.nam + " */")
		l.append(self.st + ";")
		l.append(self.init.c_typedef(modnam) + "")
		l.append(self.fini.c_typedef(modnam, fini=True) + "")
		for m in self.methods:
			l.append(m.c_typedef(modnam) + "")
		return l

	def c_proto(self, fo):
		fo.write(self.st + ";\n")
		l = []
		l += lwrap(self.init.c_proto())
		l += lwrap(self.fini.c_proto(fini=True))
		for m in self.methods:
			l += lwrap(m.c_proto())
		for i in l:
			fo.write(i + "\n")

	def c_struct(self, modnam):
		s = "\t/* Object " + self.nam + " */\n"
		s += self.init.c_struct(modnam)
		s += self.fini.c_struct(modnam)
		for m in self.methods:
			s += m.c_struct(modnam)
		return s

	def c_initializer(self):
		s = "\t/* Object " + self.nam + " */\n"
		s += self.init.c_initializer()
		s += self.fini.c_initializer()
		for m in self.methods:
			s += m.c_initializer()
		return s

	def c_strspec(self, modnam):
		s = "\t/* Object " + self.nam + " */\n"
		s += '\t"$OBJ\\0"\n'
		s += self.init.c_strspec(modnam, pfx="\t\t") + '\n'
		s += '\t\t"' + self.st + '\\0"\n'
		s += self.fini.c_strspec(modnam, pfx="\t\t") + '\n'
		for m in self.methods:
			s += m.c_strspec(modnam, pfx="\t\t") + '\n'
		s += '\t"\\0"'
		return s

	def doc(self, l):
		self.doc_str.append(l)

	def doc_idx(self, suf):
		l = []
		if suf == "":
			l.append((self.nam, ":ref:`obj_" + self.nam + "`"))
		else:
			l.append((self.nam, "Object " + self.nam))
		for i in self.methods:
			l.append(i.doc_idx(suf))
		return l

	def doc_dump(self, fo):
		fo.write(".. _obj_" + self.nam + ":\n\n")
		s = "Object " + self.nam
		fo.write(s + "\n")
		fo.write("=" * len(s) + "\n")
		fo.write("\n")

		for i in self.doc_str:
			fo.write(i + "\n")

		for i in self.methods:
			i.doc_dump(fo)

#######################################################################

class Arg(object):
	def __init__(self, typ, nam=None, det=None):
		self.nam = nam
		self.typ = typ
		self.det = det
		self.val = None

	def __repr__(self):
		return "<ARG %s %s %s>" % (self.nam, self.typ, str(self.det))

	def c_strspec(self):
		if self.det == None:
			s = self.typ + "\\0"
		else:
			s = self.det
		if self.nam != None:
			s += '"\n\t\t    "\\1' + self.nam + '\\0'
		if self.val != None:
			# The space before the value is important to
			# terminate the \2 escape sequence
			s += '"\n\t\t\t"\\2 ' + quote(self.val) + "\\0"
		return s

#######################################################################
#
#
def parse_enum2(tl):
	t = tl.get_token()
	if t.str != "{":
		raise ParseError("expected \"{\"")
	s = "ENUM\\0"
	t = None
	while True:
		if t == None:
			t = tl.get_token()
		if t.str == "}":
			break
		s += t.str + "\\0"
		t = tl.get_token()
		if t.str == ",":
			t = None
		elif t.str == "}":
			break
		else:
			raise ParseError(
			    "Expected \"}\" or \",\" not \"%s\"" % t.str)
	s += "\\0"
	return Arg("ENUM", det=s)

def parse_arg(tl, al):
	t = tl.get_token()
	assert t != None

	if t.str == ")":
		return t

	if t.str == "ENUM":
		al.append(parse_enum2(tl))
	elif t.str in ctypes:
		al.append(Arg(t.str))
	else:
		raise Exception("ARG? %s", t.str)

	t = tl.get_token()
	if t.str == "," or t.str == ")":
		return t

	if not is_c_name(t.str):
		raise ParseError(
		    'Expected ")", "," or argument name, not "%s"' % t.str)

	al[-1].nam = t.str
	t = tl.get_token()

	if t.str == "," or t.str == ")":
		return t

	if t.str != "=":
		raise ParseError(
		    'Expected ")", "," or "=", not "%s"' % t.str)

	t = tl.get_token()
	al[-1].val = t.str

	t = tl.get_token()
	return t

#######################################################################
#
#

def parse_module(tl):
	nm = tl.get_token().str
	sec = tl.get_token().str
	s = ""
	while len(tl.tl) > 0:
		s += " " + tl.get_token().str
	dnm = s[1:]
	return Vmod(nm, dnm, sec)

#######################################################################
#
#

def parse_func(tl, rt_type=None, pobj=None):
	al = list()
	if rt_type == None:
		t = tl.get_token()
		rt_type = t.str
	if rt_type not in ctypes:
		raise TypeError(
		    "Return type '%s' not a valid type" % rt_type)

	t = tl.get_token()
	fname = t.str
	if pobj != None and fname[0] == "." and is_c_name(fname[1:]):
		fname = pobj + fname
	elif pobj != None and fname[0] != ".":
		raise ParseError("Method name '%s' must start with ." % fname)
	elif pobj != None and not is_c_name(fname[1:]):
		raise ParseError("Method name '%s' is illegal" % fname[1:])
	elif not is_c_name(fname):
		raise ParseError("Function name '%s' is illegal" % fname)
	elif pobj != None and fname[0] != ".":
		raise ParseError("Method name '%s' must start with ." % fname)

	t = tl.get_token()
	if t.str != "(":
		raise ParseError("Expected \"(\" got \"%s\"" % t.str)

	while True:
		t = parse_arg(tl, al)
		if t.str == ")":
			break
		if t.str != ",":
			raise ParseError("End Of Input looking for ')' or ','")

	f = Func(fname, rt_type, al)

	return f

#######################################################################
#
#

def parse_obj(tl):
	f = parse_func(tl, "VOID")
	o = Obj(f.nam)
	o.set_init(f)
	return o


#######################################################################
# A section of the inputvcc, starting at a keyword

class FileSection(object):
	def __init__(self):
		self.l = []
		self.tl = []

	def add_line(self, ln, l):
		self.l.append((ln, l))

	def get_token(self):
		while True:
			if len(self.tl) > 0:
				# print("T\t", self.tl[0])
				return self.tl.pop(0)
			if len(self.l) == 0:
				break
			self.more_tokens()
		return None

	def more_tokens(self):
		ln, l = self.l.pop(0)
		if l == "":
			return
		l = re.sub("[ \t]*#.*$", "", l)
		l = re.sub("[ \t]*\n", "", l)

		if re.match("['\"]", l):
			m = re.match("(['\"]).*?(\\1)", l)
			if not m:
				raise FormatError("Unbalanced quote",
				    "Unbalanced quote on line %d" % ln)
			self.tl.append(Token(ln, 0, l[:m.end()]))
			self.l.insert(0, (ln, l[m.end():]))
			return

		m = re.search("['\"]", l)
		if m:
			rest = l[m.start():]
			self.l.insert(0, (ln, rest))
			l = l[:m.start()]

		l = re.sub("([(){},=])", r' \1 ', l)
		if l == "":
			return
		for j in l.split():
			self.tl.append(Token(ln, 0, j))

	def parse(self, vx):
		t = self.get_token()
		if t == None:
			return
		t0 = t.str
		if t.str == "$Module":
			o = parse_module(self)
			vx.append(o)
		elif t.str == "$Event":
			x = self.get_token()
			vx[0].set_event(x.str)
			o = None
		elif t.str == "$Function":
			if len(vx) == 2:
				vx.pop(-1)
			o = parse_func(self)
			vx[0].add_func(o)
		elif t.str == "$Object":
			if len(vx) == 2:
				vx.pop(-1)
			o = parse_obj(self)
			vx[0].add_obj(o)
			vx.append(o)
		elif t.str == "$Method":
			if len(vx) != 2:
				raise FormatError("$Method outside $Object", "")
			o = parse_func(self, pobj=vx[1].nam)
			vx[1].add_method(o)
		else:
			if opts.strict:
				raise FormatError("Unknown keyword: %s" %
				    t.str, "")
			else:
				print("WARNING: Unknown keyword: %s:" %
				    t.str, file=sys.stderr)
				o = None
				while len(self.tl) > 0:
				    self.get_token()

		assert len(self.tl) == 0
		if o is None and len(self.l) > 0:
			m = "%s description is not included in .rst" % t0
			details = pformat(self.l)
			if opts.strict:
				raise FormatError(m, details)
			else:
				print("WARNING: %s:" % m, file=sys.stderr)
				print(details, file=sys.stderr)
		else:
			for ln, i in self.l:
				o.doc(i)

#######################################################################
# Polish the copyright message
#
def polish(l):
	if len(l[0]) == 0:
		l.pop(0)
		return True
	c = l[0][0]
	for i in l:
		if len(i) == 0:
			continue
		if i[0] != c:
			c = None
			break
	if c != None:
		for i in range(len(l)):
			l[i] = l[i][1:]
		return True
	return False


class SimpleTestCase(unittest.TestCase):
	def test_included_vccs(self):
		from tempfile import mktemp
		from glob import glob
		tmpfile = mktemp()
		bdir = dirname(realpath(__file__))
		for inputfile in glob(join(bdir, "../libvmod_*/vmod.vcc")):
			runmain(inputfile, ".", tmpfile)
			unlink(tmpfile + ".c")
			unlink(tmpfile + ".h")


#######################################################################
def runmain(inputvcc, rstdir, outputprefix):
	# Read the file in
	lines = []
	with open(inputvcc, "r") as fp:
		for i in fp:
			lines.append(i.rstrip())
	ln = 0

	#######################################################################
	# First collect the copyright:  All initial lines starting with '#'

	copy_right = []
	while len(lines[0]) > 0 and lines[0][0] == "#":
		ln += 1
		copy_right.append(lines.pop(0))

	if len(copy_right) > 0:
		if copy_right[0] == "#-":
			copy_right = []
		else:
			while polish(copy_right):
				continue

	if False:
		for i in copy_right:
			print("(C)\t", i)

	#######################################################################
	# Break into sections

	sl = []
	sc = FileSection()
	sl.append(sc)
	while len(lines) > 0:
		ln += 1
		l = lines.pop(0)
		j = l.split()
		if len(j) > 0 and re.match("^\$", j[0]):
			sc = FileSection()
			sl.append(sc)
		sc.add_line(ln, l)

	#######################################################################
	# Parse each section

	try:
		vx = []
		for i in sl:
			i.parse(vx)
			assert len(i.tl) == 0
	except ParseError as e:
		print("ERROR: Parse error reading \"%s\":" % inputvcc)
		pprint(str(e))
		exit(-1)
	except FormatError as e:
		print("ERROR: Format error reading \"%s\": %s" %
		    (inputvcc, pformat(e.msg)))
		print(e.details)
		exit(-2)

	#######################################################################
	# Parsing done, now process
	#
	fd_cfile, fn_cfile = mkstemp(dir='.')
	cfile = fdopen(fd_cfile, "w")

	fd_headerfile, fn_headerfile = mkstemp(dir='.')
	headerfile = fdopen(fd_headerfile, "w")

	write_c_file_warning(cfile)
	write_c_file_warning(headerfile)

	headerfile.write('struct vmod_priv;\n\n')

	headerfile.write('extern const struct vmod_data Vmod_%s_Data;\n\n' % vx[0].nam)

	vx[0].c_proto(headerfile)

	cfile.write('#include "config.h"\n')
	cfile.write('#include "vcl.h"\n')
	cfile.write('#include "vrt.h"\n')
	cfile.write('#include "%s.h"\n' % outputprefix)
	cfile.write('#include "vmod_abi.h"\n')
	cfile.write('\n')

	vx[0].c_typedefs(cfile)
	vx[0].c_vmod(cfile)

	cfile.close()
	headerfile.close()

	for suf in ("", ".man"):
		fd, fn_fp = mkstemp(dir='.')
		fp = fdopen(fd, "w")
		write_rst_file_warning(fp)

		vx[0].doc_dump(fp, suf)

		if len(copy_right) > 0:
			fp.write("\n")
			fp.write("COPYRIGHT\n")
			fp.write("=========\n")
			fp.write("\n::\n\n")
			for i in copy_right:
				fp.write("  %s\n" % i)
			fp.write("\n")
		fp.close()
		rename(fn_fp, join(rstdir, "vmod_%s%s.rst" % (vx[0].nam, suf)))

	# Our makefile targets the .c file so make sure to put that in place last.
	rename(fn_headerfile, outputprefix + ".h")
	rename(fn_cfile, outputprefix + ".c")


if __name__ == "__main__":
	usagetext = "Usage: %prog [options] <vmod.vcc>"
	oparser = optparse.OptionParser(usage=usagetext)

	oparser.add_option('-N', '--strict', action='store_true', default=False,
	    help="Be strict when parsing the input file")
	oparser.add_option('-o', '--output', metavar="prefix", default='vcc_if',
	    help='Output file prefix (default: "vcc_if")')
	oparser.add_option('-w', '--rstdir', metavar="directory", default='.',
	    help='Where to save the generated RST files (default: ".")')
	oparser.add_option('', '--runtests', action='store_true', default=False,
	    dest="runtests", help=optparse.SUPPRESS_HELP)
	(opts, args) = oparser.parse_args()

	if opts.runtests:
		# Pop off --runtests, pass remaining to unittest.
		del sys.argv[1]
		unittest.main()
		exit()

	i_vcc = None
	if len(args) == 1 and exists(args[0]):
		i_vcc = args[0]
	elif exists("vmod.vcc"):
		if not i_vcc:
			i_vcc = "vmod.vcc"
	else:
		print("ERROR: No vmod.vcc file supplied or found.",
		    file=sys.stderr)
		oparser.print_help()
		exit(-1)

	runmain(i_vcc, opts.rstdir, opts.output)
