#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
"""

    Syntax Highlighter using Vim

    Converted from Text::VimColor perl module by
    Copyright 2002-2004, Geoff Richards.

    Copyright (c) 2004 by Oliver Graf <ograf@bitart.de>

    Version History:
      1.0 - first working version
      1.1 - ANSI output, test code
      1.2 - subclass css

"""

### Imports ###################################################################

import re

### Config ####################################################################

VIM_COMMAND = '/usr/bin/vim';
VIM_OPTIONS = '-RXZ -i NONE -u NONE -N'

SYNTAX = {
	'': 'Text',
	'Normal': 'Text',
	'Comment':    'Comment',
	'Constant':   'Constant',
	'Identifier': 'Identifier',
	'Statement':  'Statement',
	'PreProc':    'PreProc',
	'Type':       'Type',
	'Special':    'Special',
	'Underlined': 'Underlined',
	'Error':      'Error',
	'Todo':       'Todo',
	}

MOIN_SYNTAX = {
	'':           'Char',
	'Normal':     'Char',
	'Comment':    'Comment',
	'Constant':   'ConsWord',
	'Identifier': 'ID',
	'Statement':  'ResWord',
	'PreProc':    'Preprc',
	'Type':       'ResWord2',
	'Special':    'Special',
	'Underlined': 'SPChar',
	'Error':      'Error',
	'Todo':       'Special',
	}

### Constants #################################################################

MODE_RAW=0
MODE_HTML=1
MODE_ANSI=2
#MODE_XML=3

# just an example
ANSI_SEQ = {'':           ('', ''),
			'Normal':     ('', ''),
			'Text':       ('', ''),
			'Comment':    ('\x1b[34m', '\x1b[0m'),
			'Constant':   ('\x1b[31m', '\x1b[0m'),
			'Identifier': ('\x1b[36m', '\x1b[0m'),
			'Statement':  ('\x1b[33m', '\x1b[0m'),
			'PreProc':    ('\x1b[35m', '\x1b[0m'),
			'Type':       ('', ''),
			'Special':    ('', ''),
			'Underlined': ('', ''),
			'Error':      ('', ''),
			'Todo':       ('', ''),
			}

ANSI_RESET = '\x1b[0m'

VIM_MARK_RC='''" mark.vim - turn Vim syntax highlighting into an ad-hoc markup language that
" can be parsed by the Text::VimColor Perl module.
"
" Maintainer: Geoff Richards <qef@laxan.com>
" Based loosely on 2html.vim, by Bram Moolenaar <Bram@vim.org>,
"   modified by David Ne\\v{c}as (Yeti) <yeti@physics.muni.cz>.

set report=1000000

" For some reason (I\'m sure it used to work) we now need to get Vim
" to make another attempt to detect the filetype if it wasn\'t set
" explicitly.
if !strlen(&filetype)
   filetype detect
endif
syn on

" Set up the output buffer.
new
set modifiable
set paste

" Expand tabs.: Without this they come out as \'^I\'.
set isprint+=9

wincmd p

" Loop over all lines in the original text
let s:end = line("$")
let s:lnum = 1
while s:lnum <= s:end

  " Get the current line
  let s:line = getline(s:lnum)
  let s:len = strlen(s:line)
  let s:new = ""

  " Loop over each character in the line
  let s:col = 1
  while s:col <= s:len
    let s:startcol = s:col " The start column for processing text
    let s:id = synID(s:lnum, s:col, 1)
    let s:col = s:col + 1
    " Speed loop (it\'s small - that\'s the trick)
    " Go along till we find a change in synID
    while s:col <= s:len && s:id == synID(s:lnum, s:col, 1) | let s:col = s:col + 1 | endwhile

    " Output the text with the same synID, with class set to c{s:id}
    let s:name = synIDattr(s:id, \'name\')
    let s:id = synIDtrans(s:id)
    let s:class = synIDattr(s:id, \'name\')
    let s:new = s:new . \'>\' . s:class . \'>\' . s:name . \'>\' . substitute(substitute(substitute(strpart(s:line, s:startcol - 1, s:col - s:startcol), \'&\', \'\\&a\', \'g\'), \'<\', \'\\&l\', \'g\'), \'>\', \'\\&g\', \'g\') . \'<\' . s:class . \'<\' . s:name . \'<\'

    if s:col > s:len
      break
    endif
  endwhile

  exe "normal \\<C-W>pa" . strtrans(s:new) . "\\n\\e\\<C-W>p"
  let s:lnum = s:lnum + 1
  +
endwhile

" Strip whitespace from the ends of lines
%s:\\s\\+$::e

wincmd p
normal dd
'''

### Globals ###################################################################

SYNre=re.compile(r'>(?P<class>.*?)>(?P<name>.*?)>(.*?)<(?P=class)<(?P=name)<',re.S)

### Helper Functions ##########################################################

def xmlquote(s):
	return s.replace('&','&amp;').replace('>','&gt;').replace('<','&lt')

### Interface #################################################################

class VimColor:
	"""
    Vim Colorizer Class
    """

	def __init__(self, mode=MODE_HTML, syntaxmap=None):
		self.mode = mode
		self.syntaxmap = {}
		if syntaxmap is None:
			self.syntaxmap.update(SYNTAX)
		else:
			self.syntaxmap.update(syntaxmap)

	def _build_syntax(self, filepath, filetype=None):
		import os, tempfile
		self.syntax=[]
		vstemp=tempfile.mktemp('.vim','mark')
		open(vstemp,'w').write(VIM_MARK_RC)
		if not os.path.exists(filepath):
			raise IOError('File "%s" not found'%(filepath))
		# tempfile to save
		outtemp=tempfile.mktemp('.xml')
		# make vim script
		shtemp=tempfile.mktemp('.vim')
		sh=open(shtemp,'w')
		if filetype:
			filetype=':set filetype=%s\n'%(filetype,)
		else:
			filetype=''
		sh.write(''':filetype on
%(filetype)s:source %(vstemp)s
:write! %(outtemp)s
:qall!
'''%{'filetype': filetype,
	 'vstemp': vstemp,
	 'outtemp': outtemp})
		sh.close()
		# run colorize
		os.system("%s %s %s -s %s >/dev/null 2>&1"%(VIM_COMMAND, VIM_OPTIONS, filepath, shtemp))
		# load stuff and kill files
		lines=open(outtemp,'r').readlines()
		try:
			os.unlink(shtemp)
		except:
			pass
		try:
			os.unlink(outtemp)
		except:
			pass
		try:
			os.unlink(vstemp)
		except:
			pass
		# make syntax list
		for line in lines:
			self.syntax.append([('__NL__', '__NL__'), '\n'])
			for cls, syn, text in SYNre.findall(line.rstrip()):
				cls=self.syntaxmap.get(cls,'Unknown_'+cls)
				text=text.replace('&l','<')
				text=text.replace('&g','>')
				text=text.replace('&a','&')
				if len(self.syntax) and self.syntax[-1][0]==(cls, syn):
					self.syntax[-1][1]+=text
				else:
					self.syntax.append([(cls, syn),text])

	def _mark(self, filepath, filetype=None):
		self._build_syntax(filepath,filetype)
		if self.mode==MODE_RAW:
			return self.syntax
		elif self.mode==MODE_HTML:
			res='<pre>'
			for (cls, syn), text in self.syntax:
				if cls=='__NL__':
					res+='\n<span class="LineNumber"></span>'
				else:
					if syn!='':
						syn=' '+syn
					res+='<span class="%s%s">%s</span>'%(cls,syn,xmlquote(text))
			res+='\n</pre>'
			return res
		elif self.mode==MODE_ANSI:
			res=''
			for (cls, syn), text in self.syntax:
				if syn=='__NL__':
					res+='\n'
				else:
					res+='%s%s%s'%(ANSI_SEQ.get(cls,('',''))[0],
								   text,
								   ANSI_SEQ.get(cls,('',''))[1])
			res+='%s\n'%(ANSI_RESET,)
			return res[1:-1] # strip first and last \n
		raise ValueError("unknown mode %d"%(self.mode))
		
	def markFile(self, filepath, filetype=None):
		return self._mark(filepath, filetype)

	def markString(self, string, extension='.dat', filetype=None):
		import tempfile
		fn=tempfile.mktemp(extension)
		f=open(fn,'w')
		f.write(string)
		f.close()
		res=self._mark(fn, filetype)
		try:
			os.unlink(fn)
		except:
			pass
		return res

### MoinMoin parser ###########################################################

Dependencies = []

class Parser:
	"""
    MoinMoin Sytax Parser Class using VimColor
    """

	parsername = "VimColor"
	extensions = '*'

	def __init__(self, raw, request, **kw):
		""" Store the source text.
        """
		from MoinMoin.util.ParserBase import parse_start_step

		self.raw = raw.expandtabs().rstrip()
		self.request = request
		self.form = request.form
		self._ = request.getText
		self.show_num, self.num_start, self.num_step, args = parse_start_step(request, kw.get('format_args',''))
		self.filetype = None
		if args.has_key('type'):
			# get rid of those quotes no quotes and only the first valid part
			self.filetype = re.split(r'[^a-z0-9]', args['type'][1:-1])[0]

	def format(self, formatter):
		""" Parse and send the colored source.
		"""
		from MoinMoin import config
		import sha
		
		self._code_id = sha.new(self.raw.encode(config.charset)).hexdigest()

		self.request.write(formatter.code_area(1, self._code_id, 'VimColorizer', self.show_num, self.num_start, self.num_step))

		vim=VimColor(MODE_RAW, MOIN_SYNTAX)
		for (cls, syn), tok in vim.markString(self.raw,filetype=self.filetype):
			if cls=='__NL__':
				self.request.write(formatter.code_line(1))
			else:
				if syn!='':
					cls=cls+' '+syn
				self.request.write(formatter.code_token(1, cls) +
								   formatter.text(tok)+
								   formatter.code_token(0, cls))

		self.request.write(formatter.code_area(0, self._code_id))

### MAIN Test #################################################################

TEST="""#!/usr/bin/python

# just a test
import sys

def main(world='World'):
    sys.stdout.write('Hello %s!' % (world,))

if __name__=='__main__':
    main(sys.argv[1])
    sys.exit(0)

"""

if __name__=='__main__':
	import sys
	if len(sys.argv)>1:
		try:
			data=open(sys.argv[1],'r').read()
		except:
			sys.stderr.write("can't read %s\n"%(sys.argv[1]))
			sys.exit(1)
	else:
		data=TEST
	vc=VimColor(MODE_ANSI)
	print vc.markString(data)

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