#!/usr/pkg/bin/python3.12

# yodl2texinfo-post.py
# 
# (c) 1998,1999 Jan Nieuwenhuizen

program_name = 'yodl2texinfo-post'
version = '@TOPLEVEL_VERSION@'
simple_nodes = 0
backup = 0

## urg::
## this hack (File: texinfo,  Node: Pointer Validation)
##
##      @set nodename Node 1
##      @node @value{nodename}, Node 2, Top, Top
## 
## together with --commands-in-node-names, still doesn't work:
##
##    out/topinfo.texinfo:906: Node `@value{nodename}' previously defined at line 48.
##
urg_value = 1
not_in_node_set = '[,:\.?!\\\'`{}]'
not_in_node = '.*[,:\.?!\\\'`{}].*'

import os
import sys

import getopt
from string import *
import regex
import regsub
import time

def program_id ():
	return program_name + ' version ' + version;

def identify ():
	sys.stdout.write (program_id () + '\n')

def help ():
	sys.stdout.write ("Usage: %s [OPTION]... [FILE]...\n"
		"Resolve yodl tags, autogenerate nodes and menus.\n\n"
		+ "Options:\n"
		+ "  -h, --help		   print this help\n"
		+ "  -k, --keep	           keep backup of Yodl output\n"
		+ "  -s, --simple-nodes	   don't put next,prev,up references in nodes\n"
		% (program_name)
		)
	sys.exit (0)


identify ()
(options, files) = getopt.getopt (
	sys.argv[1:], 'hks', ['help','keep','simple-nodes'])
for opt in options:
	o = opt[0]
	a = opt[1]
	if o == '--help' or o == '-h':
		help ()
	elif o == '--keep' or o == '-k':
		backup = 1
	elif o == '--simple-nodes' or o == '-s':
		simple_nodes = 1
	else:
		print o
		raise getopt.error


def gulp_file (f):
	try:
		i = open (f)
		i.seek (0, 2)
		n = i.tell ()
		i.seek (0,0)
	except:
		sys.stderr.write ('can\'t open file %s\n ' % f)
		return ''
	s = i.read (n)
	if len (s) <= 0:
		sys.stderr.write ('gulped empty file: %s\n'% f)
	return s


class Node:
	def __init__ (this, pos, name):
		this.pos = pos
		this.name = name
		this.description = ''
		this.prev = ''
		this.next = ''
		this.top = ''

class Menu (Node):
	def __init__ (this, pos, name):
		Node.__init__ (this, pos, name)
		this.node_list = []

TAB_POS = 34

class Pre_texinfo_file (Menu):
	def __init__ (this, filename):
		Menu.__init__ (this, 0, 'Top')
		this.filename = filename
		this.node_list = []
		this.s = ''
		this.offset = 0
		this.last = Node (0, '')
		this.prefix = ''
		this.default = Node (0,'')
		this.default.top = '(dir)'
		this.node_dict = {}
		this.xref_list = []

	def gulp (this):
		this.s = gulp_file (this.filename)

	def new_menu (this, pos, name):
		m = Menu (pos, name)
		if this.default.name:
			m.name = this.default.name
		if this.default.description:
			m.description = this.default.description
		m.name = this.prefix + m.name
		return m

	def new_node (this, pos, name):
		n = Node (pos, name)
		if this.default.name:
			n.name = this.default.name
		if this.default.description:
			n.description = this.default.description
		n.name = this.prefix + n.name
		while this.node_dict.has_key (n.name):
			n.name = n.name + 'i'
		this.node_dict[n.name] = 0
		n.top = this.default.top
		n.prev = this.last.name
		this.last.next = n.name
		this.last = n
		return n

	def eat_tag (this):
		i = regex.search ('@TAGSTART@', this.s)
		j = 0
		if i < 0:
			return 0
		j = regex.search ('@TAGEND@', this.s[i:])
		if j < 0:
			raise 'huh?'
		j = i + j + len ('@TAGEND@')
		tag = this.s[i + len ('@TAGSTART@'):j - len ('@TAGEND@')]
		k = regex.search (' ', tag[1:]) + 1
		tag_name = tag[1:k]
		tag_string = tag[k:len (tag) - 1]
		while tag_string[:1] == ' ':
			tag_string = tag_string[1:]
		#
		# urg, commas in titles...
		# but even more urg, makeinfo (3.12) chokes on 
		# various other characters in node names too
		# 
		if urg_value:
			tag_string = regsub.gsub (not_in_node_set, '-', tag_string)
			tag_string = regsub.gsub ('--', '-', tag_string)
			# brr
			tag_string = regsub.gsub ('--', '-', tag_string)
			tag_string = regsub.gsub ('@code', '', tag_string)
			tag_string = regsub.gsub ('@emph', '', tag_string)
			tag_string = regsub.gsub ('@strong', '', tag_string)
		if tag_name == 'menu':
			this.default.top = 'Top'
			n = this.new_node (i, tag_string)
			m = this.new_menu (i, tag_string)
			this.default.top = tag_string
			this.default.name = ''
			this.default.description = ''
			m.node_list.append (n)
			this.node_list.append (m)
		elif tag_name == 'node':
			n = this.new_node (i, tag_string)
			this.default.name = ''
			this.default.description = ''
			if not len (this.node_list):
				# m = Menu (i, 'Toplevel')
				m = this.new_menu (i, 'Toplevel')
				this.node_list.append (m)
			this.node_list[len (this.node_list)-1].node_list.append (n)
		elif tag_name == 'node_description':
			this.default.description = tag_string
		elif tag_name == 'node_name':
			this.default.name = tag_string
		elif tag_name == 'node_prefix':
			this.prefix = tag_string
		elif tag_name == 'ref':
			## urg, prefix is broken
			ref = '@ref{' + this.prefix + tag_string + '}'
			this.s = this.s[:i] + ref + this.s[j:]
			this.xref_list.append (this.prefix + tag_string)
			## urg
			return 1
		else:
			sys.stderr.write ("unknown tag: `" + tag + "', skipping\n")
		this.s = this.s[:i] + this.s[j:]
		return 1

	def plug (this):
		if backup:
			os.rename (this.filename, this.filename + '~')
		fd = open (this.filename, 'w')
		fd.write (this.s)
		fd.close ()

	def create_node (this, n):
		node = '@node '
		set = ''
		if not urg_value and  regex.match (not_in_node, n.name) != -1:
			set = set + "@set nodename " + n.name + "\n"
			node = node + "@value{nodename}"
		else:
			node = node + n.name
		if not simple_nodes:
			if  not urg_value and regex.match (not_in_node, n.next) != -1:
				set = set + "@set nextname " + n.next + "\n"
				node = node + ", @value{nextname}"
			else:
				node = node + ", " + n.next
			if  not urg_value and regex.match (not_in_node, n.prev) != -1:
				set = set + "@set prevname " + n.prev + "\n"
				node = node + ", @value{prevname}"
			else:
				node = node + ", " + n.prev
			if  not urg_value and regex.match (not_in_node, n.top) != -1:
				set = set + "@set topname " + n.top + "\n"
				node = node + ", @value{topname}"
			else:
				node = node + ", " + n.top
		node = set + node + "\n"
		if n.name == 'Top':
			node = node + "@top\n"
		this.s = this.s[:n.pos + this.offset] + node + this.s[n.pos + this.offset:]
		this.offset = this.offset + len (node)
		
	def create_menu (this, m):
		if len (m.node_list) < 2:
			return
		menu = "@menu\n"
		for n in m.node_list[1:]:
			d = n.description
			if not d:
				d = n.name
			if not urg_value and regex.match (not_in_node, n.name) != -1:
				menu = menu + "@set nodename " + n.name + "\n"
				menu = menu + "* @value{nodename}::"
				menu = menu + ' ' * (TAB_POS - len (n.name)) + d + "\n"
			else:
				menu = menu + ljust ('* ' + n.name + '::', TAB_POS) + d + "\n"
		menu = menu + "@end menu\n"
		this.s = this.s[:m.pos + this.offset] + menu + this.s[m.pos + this.offset:]
		m.node_list[0].pos = m.node_list[0].pos - len (menu)
		this.offset = this.offset + len (menu)

	def create_nodes (this, m):
		for n in m.node_list:
			this.create_node (n)
		
	def parse (this):
		i = 1
		while i:
			i = this.eat_tag ()

	def nodes_and_menus (this):
		this.default.top = '(dir)'
		n = this.new_node (0, 'Top')
		m = Menu (0, 'Top')
		m.node_list.append (n)
		this.node_list.insert (0, m)
		n.pos = this.offset
		this.create_menu (this)
		n.pos = - this.offset
		this.create_nodes (m)
		for m in this.node_list[1:]:
			this.create_menu (m)
			this.create_nodes (m)

	def xrefs (this):
		this.prefix = ''
		this.default.top = 'Top'
		m = Menu (len (this.s), 'Missing nodes')
		this.default.top = 'Missing nodes'
		first_b = 1
		for i in this.xref_list:
			if not this.node_dict.has_key (i):
				if first_b:
					this.default.top = 'Top'
					n = this.new_node (len (this.s), 'Missing nodes')
					m.node_list.append (n)
					this.default.top = 'Missing nodes'
					first_b = 0
				sys.stderr.write (this.filename + ": warning: xref to nonexistent node: `" + i + "'\n")
				n = this.new_node (len (this.s), i)
				m.node_list.append (n)
		if len (m.node_list) > 1:
			this.node_list.append (m)
		this.default.top = 'Top'

	def post (this):
		this.gulp ()
		this.parse ()
		this.xrefs ()
		this.nodes_and_menus ()
		this.s = regsub.gsub ('^\n\n', '\n', this.s)
		this.s = '\n' + this.s 
		infotitle = ''
		basename = os.path.basename (os.path.splitext (this.filename)[0])
		infoname = basename + ".info"
		if len (this.node_list) > 1:
			infotitle = this.node_list[1].name
		else:
			infotitle = basename
#		this.s = '@settitle %s\n' % this.node_list[1].name + this.s 
#		this.s = '@setfilename %s\n' % infoname + this.s 
#		this.s = '\\input texinfo @c -*-texinfo-*-\n' + this.s 
		this.s = r"""\input texinfo @c -*-texinfo-*-
@setfilename %s
@settitle %s
""" % (infoname, infotitle) + this.s 
		this.s = this.s + "\n@bye"
		this.plug ()


for f in files:
	t = Pre_texinfo_file (f)
	t.post ()

