#!/usr/local/bin/python3.11

# Add html markup for syntax coloring of YANG modules, or documents
# which contain YANG modules.
#
# Works on the syntax level and does thus not include any intelligent
# links or anything.

import sys
import optparse
from xml.sax.saxutils import escape

import pyang
import pyang.syntax


usage = """%prog [-d] [-c] [<filename>]

Add html markup for syntax coloring of YANG modules, or documents
which contain YANG modules.

Reads a YANG module from <filename> or if no <filename> is given,
reads from stdin.  The YANG module is formatted as html.

If the option -d is given, the input contains an html document.  Everything
in a 'pre' element with the 'yang' class is converted.

  <pre class='yang'>
  unformatted yang module
  </pre>
"""

optlist = [
    # use capitalized versions of std options help and version
    optparse.make_option("-h", "--help",
                         action="help",
                         help="Show this help message and exit"),
    optparse.make_option("-v", "--version",
                         action="version",
                         help="Show version number and exit"),
    optparse.make_option("-o", "--output",
                         dest="outfile",
                         help="Write the output to OUTFILE instead " \
                               "of stdout."),
    optparse.make_option("-d",
                         dest="scan_document",
                         action="store_true",
                         help="Add markup to an existing html document"),
    optparse.make_option("-c",
                         dest="emit_css",
                         action="store_true"),
    ]

optparser = optparse.OptionParser(usage, add_help_option = False)
optparser.version = '%prog ' + pyang.__version__
optparser.add_options(optlist)

(o, args) = optparser.parse_args()

if len(args) > 1:
    sys.stderr.write("too many files to convert")
    sys.exit(1)


if len(args) == 0:
    # read the entire doc into buf
    buf = ""
    for line in sys.stdin:
        buf = buf + line
else:
    filename = args[0]
    try:
        fd = file(filename)
        buf = fd.read()
    except IOError as ex:
        sys.stderr.write("error %s: %s\n" % (filename, str(ex)))
        sys.exit(1)

if o.outfile == None:
    fd = sys.stdout
else:
    fd = open(o.outfile, "w+")

def emit_css():
    fd.write("""
<style type="text/css" media="all">
pre.yang {
  border: thin solid black;
  background-color: #eeeeee;
  color: black;
  margin: 10px 10px 10px 10px;
  padding: 10px 10px 10px 10px;
  line-height: 1.2em;
}
span.kw {
  color: blue;
}
span.cmt {
  color: red;
}
span.str {
  color: green;
}
</style>
""")

i = 0
q = None
keyword_next = True
begin_yang = '<pre class="yang">'

if o.scan_document:
    no_yang = True
else:
    no_yang = False
    if o.emit_css:
        o.emit_css = False
        emit_css()
    fd.write(begin_yang)

while i < len(buf):
    if no_yang == True and buf[i:].startswith(begin_yang):
        no_yang = False
        fd.write(buf[:i])
        if o.emit_css:
            o.emit_css = False
            emit_css()
        fd.write(begin_yang)
        buf = buf[i+len(begin_yang):]
        i = 0
        continue
    elif no_yang == True:
        i = i + 1
        continue
    elif no_yang == False and buf[i:].startswith('</pre>'):
        no_yang = True
        continue
    if q == '"' and buf[i] == '\\':
        i = i + 2;
        continue
    if q != None and buf[i] == q:
        fd.write("<span class='str'>")
        fd.write(q)
        fd.write(escape(buf[:i+1]))
        fd.write("</span>")
        buf = buf[i+1:]
        q = None
        i = 0
    elif q != None:
        i = i + 1
        continue
    if buf[i] == '/' and buf[i+1] == '/':
        end = buf.find("\n", i)
        if end == -1:
            end = len(buf)
        fd.write(buf[:i])
        fd.write("<span class='cmt'>")
        fd.write(escape(buf[i:end]))
        fd.write("</span>\n")
        buf = buf[end+1:]
        i = 0
    elif buf[i] == '/' and buf[i+1] == '*':
        end = buf.find("*/", i)
        if end == -1:
            end = len(buf)
        fd.write(buf[:i])
        fd.write("<span class='cmt'>")
        fd.write(escape(buf[i:end]))
        fd.write("*/</span>")
        buf = buf[end+2:]
        i = 0
    elif buf[i] == '"':
        fd.write(buf[:i])
        q = '"'
        buf = buf[i+1:]
        i = 0
    elif buf[i] == "'":
        fd.write(buf[:i])
        q = "'"
        buf = buf[i+1:]
        i = 0
    elif buf[i].isspace():
        i = i + 1
    elif keyword_next == True:
        m = pyang.syntax.re_keyword.match(buf, i)
        if m != None:
            kw = m.group()
            if kw in pyang.syntax.yin_map:
                fd.write(buf[:i])
                fd.write("<span class='kw'>" + kw + "</span>")
                buf = buf[i+len(kw):]
                i = 0
                keyword_next = False
                continue
        i = i + 1
    elif buf[i] in ['{', '}', ';']:
        keyword_next = True
        i = i + 1
    else:
        i = i + 1

fd.write(buf)

if not o.scan_document:
    fd.write('</pre>\n')

