#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of Pymetrick.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

"""Modulo para generar paginas html5 desde ficheros rST (reStructuredText)"""

try:
    from pymetrick.version import __author__, __version__, __license__, __copyright__
except ImportError:
    raise ImportError(
        'Failed to find (autogenerated) version.py '
    )

__date__ = '2013-06-16'
__credits__ = ''
__text__ = 'Generador de codigo HTML5 a partir de ficheros rST'
__file__ = 'rst4html.py'

#--- CHANGES ------------------------------------------------------------------
# 2012-09-21 v0.01 PL: - First version
# 2018-02-20 v0.43 PL: - Upgrade to Python 3.6


import string
import sys
import os
import re
import logging
import logging.handlers
from pymetrick.template import *
import datetime
import time

# Version PY
PY2 = True if sys.version[:1] == '2' else False;
PY3 = True if sys.version[:1] == '3' else False;

labels = {
      'author': b'Autor',
      'authors': b'Autores',
      'organization': b'Organizaci\u00f3n',
      'address': b'Direcci\u00f3n',
      'contact': b'Contacto',
      'version': b'Versi\u00f3n',
      'revision': b'Revisi\u00f3n',
      'status': b'Estado',
      'date': b'Fecha',
      'copyright': b'Copyright',
      'dedication': b'Dedicatoria',
      'abstract': b'Resumen',
      'attention': b'\u00a1Atenci\u00f3n!',
      'caution': b'\u00a1Precauci\u00f3n!',
      'danger': b'\u00a1PELIGRO!',
      'error': b'Error',
      'hint': b'Sugerencia',
      'important': b'Importante',
      'note': b'Nota',
      'tip': b'Consejo',
      'warning': b'Advertencia',
      'contents': b'Contenido'}

''' length_units = [
    em (ems, the height of the element's font)
    ex (x-height, the height of the letter "x")
    px (pixels, relative to the canvas resolution)
    in (inches; 1in=2.54cm)
    cm (centimeters; 1cm=10mm)
    mm (millimeters)
    pt (points; 1pt=1/72in)
    pc (picas; 1pc=12pt)
'''

'''
DEBUG - debug message
INFO - info message
WARNING - warn message
ERROR - error message
CRITICAL - critical message
'''
LOG_LEVEL_VALUE = {'DEBUG':10,'INFO':20,'WARNING':30,'ERROR':40,'CRITICAL':50}
if str(os.environ.get('PYMETRICK_LOG_LEVEL',None)).upper() in LOG_LEVEL_VALUE:
    LOG_LEVEL = LOG_LEVEL_VALUE[str(os.environ.get('PYMETRICK_LOG_LEVEL')).upper()]
else:
    LOG_LEVEL = 30
LOG_FILENAME = '-'.join([os.path.abspath(__file__).split(os.sep)[len(os.path.abspath(__file__).split(os.sep))-1],])[:-3]
LOG = logging.getLogger(LOG_FILENAME)

if 'LD_LIBRARY_PATH' in list(os.environ.keys()):
    # CGI environment
    sys.stdout = sys.stderr
    logging.basicConfig(stream = sys.stderr, level=LOG_LEVEL, format='%(filename)s[line:%(lineno)d] - %(levelname)s - %(message)s')
else:
    # not CGI environment
    logging.basicConfig(stream=sys.stderr)
    hdlr = logging.handlers.RotatingFileHandler(filename=LOG_FILENAME+'.log',mode='a', encoding='utf-8', maxBytes=1048576, backupCount=3)
    formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s - %(message)s')
    hdlr.setFormatter(formatter)
    LOG.addHandler(hdlr)
    LOG.setLevel(LOG_LEVEL)


acceptable_title_elements = ['-','`',':',"'",'"','~','^','_','*','+','#','<','>']

class rst2html(object):
    """Clase para generar objetos HTML5 desde rST reStructuredText"""

    def __init__( self,rst_file ):
        ''' Inicializa la clase rst4html'''
        self._text = []
        self.html = []
        self.title = []
        self._stack = []
        self._stack_reverse = []
        self._deep = 0
        self._empty = 0
        self._date = ''
        self._time = ''
        self._elements = list()
        self._listType = False
        _file = ''
        linea = ''
        f = ''
        try:
            _file = rst_file.lower()
            if _file and _file[-3:]=='rst' and os.path.isfile(_file):
                f = open(_file,"r")
                while True:
                    '''Procesar fichero'''
                    linea = self.readFile(f)
                    #transformar caracteres especiales a HTML
                    linea = template.char2Html(linea)
                    if linea.__len__()==0:
                        '''No existen datos y finaliza el proceso si supera 2 lineas en blanco'''
                        self._empty += 1
                        '''Cada vez que se encuentra una linea en blanco, se incorporan a html todas las lineas leidas'''
                        if self._text.__len__():
                            self.html.append('<p>%s</p>' % ''.join(self._text))
                            self._text = []

                        if self._empty > 1:
                            self._empty = 0
                            break
                        else:
                            self.html.append('<br><br>')
                            linea = ''
                    else:
                        '''Linea contiene datos, inicializar switch de lineas a 0'''
                        self._empty = 0

                        '''Sustituir fecha y hora en un texto'''
                        if re.search(r'^\.{2}?date:{2}',linea.replace(' ','')):
                            '''This document was generated on |date| at |time|.
                               .. date::
                               .. time::

                               A partir de la aparicion de esta directiva date y/o time, se pueden sustituir sus valores en el texto cuando figuren como |date| o |time|
                               Si no se hubiera incorporado ningun valor, el texto a incorporar sera el de la fecha y hora actual
                            '''
                            linea = linea.replace('..','').replace('date::','').strip()
                            if linea.strip().__len__()>0:
                                self._date = linea
                            else:
                                self._date = datetime.date.today().strftime("%d/%m/%Y")
                            linea = self.readFile(f)

                        if re.search(r'^\.{2}?time:{2}',linea.replace(' ','')):
                            linea = linea.replace('..','').replace('time::','').strip()
                            if linea.strip().__len__()>0:
                                self._time = linea
                            else:
                                self._time = time.strftime("%H:%M:%S")
                            linea = self.readFile(f)
                        if re.search(r'|date|',linea.replace(' ','')) and self._date.__len__()>0:
                            linea = linea.replace('|date|',self._date)
                        if re.search(r'|time|',linea.replace(' ','')) and self._time.__len__()>0:
                            linea = linea.replace('|time|',self._time)

                    if not re.search(r'^\.{2}include\:{2}',linea.replace(' ','')):
                        '''Buscar meta'''
                        if re.search(r'^..meta::',linea.replace(' ','')):
                            '''.. meta::
                               :description: The reStructuredText plaintext markup language
                               :keywords: plaintext, markup language

                               <meta name="description"
                               content="The reStructuredText plaintext markup language">
                               <meta name="keywords" content="plaintext, markup language">

                               .. meta::
                               :description lang=en: An amusing story
                               :description lang=fr: Une histoire amusante

                               <meta name="description" lang="en" content="An amusing story">
                               <meta name="description" lang="fr" content="Une histoire amusante">
                            '''
                            pass

                            '''Buscar header'''
                        elif re.search(r'^\.{2}header\:{2}',linea.replace(' ','')):
                            '''.. header:: space for rent

                            Se sustituye en html por :
                            <div class="header">
                               space for rent
                               <hr class="header"/>
                            </div>
                            <div class="document">
                            '''
                            linea = linea.replace('..','').replace('header::','').strip()
                            self.html.append('<div class="header">%s<hr class="header" /></div><div class="document">' % linea )
                            self._stack.append('</div>')
                            self._deep += 1
                            linea = ''

                            '''Buscar footer'''
                        elif re.search(r'^\.{2}footer\:{2}',linea.replace(' ','')):
                            '''.. footer:: space for rent
                            <div class="footer">
                               <hr class="footer" />
                               space for rent
                            </div>
                            '''
                            '''Si existe parrafo almacenado, incorporar antes'''
                            if self._text.__len__():
                                self.html.append('<p>%s</p>' % ''.join(self._text))

                            linea = linea.replace('..','').replace('footer::','').strip()
                            linea = '<div class="footer"><hr class="footer" />%s</div>' % linea
                            '''Antes de indicar los datos de footer comprobar pila stack'''
                            if self._stack.__len__():
                                self._stack_reverse = self._stack
                                self._stack_reverse.reverse()
                                for i in range(self._stack_reverse.__len__()):
                                    self.html.append(self._stack_reverse.pop())
                                    self._deep -= 1
                            self.html.append(linea)
                            self._stack = []
                            self._text = []
                            linea = ''

                            '''Buscar cualquier caracter repetido 5 veces o mas de forma continua en una linea sera un TITULO o encabezado de SECCION'''
                        elif re.search(r'^(\-|\`|\:|\'|\"|\~|\^|\_|\*|\+|\#|\<|\>){5,}',linea.replace(' ','')):

                            if linea.replace(' ','')[1:1] not in self.title:
                                self.title.append(linea.replace(' ','')[0:1])
                            for i in range(self.title.__len__()):
                                if self.title[i]==linea.replace(' ','')[0:1]:
                                    '''Recuperar linea anterior donde se encuentra el titulo'''
                                    if self._text.__len__():
                                        linea = self._text.pop()
                                    '''Si existe parrafo almacenado, incorporar antes'''
                                    if self._text.__len__():
                                        self.html.append('<p>%s</p>' % ''.join(self._text))
                                    '''Comprobar grado de identacion'''
                                    if i<self._deep:
                                        if self._stack.__len__():
                                            self._stack_reverse = self._stack
                                            self._stack_reverse.reverse()
                                            self._text.append(self._stack_reverse.pop())
                                            self._stack = self._stack_reverse
                                            self._stack.reverse()
                                            self._deep -= 1

                                    '''Crear TITULO o SECCION en html'''
                                    self.html.append('<div><h%d class="title">%s</h%d>' % (i+1,linea,i+1))
                                    self._stack.append('</div>')
                                    self._deep += 1
                            linea = ''
                            self._text = []


                            '''Buscar cualquier caracter 1 vez es una LISTA'''
                        elif re.search(r'^([a-zA-Z0-9]{1,2}|\-{1}|\+{1}|\*{1})(\)|\.){1}\s',linea):
                            '''Linea que comienza con un numero o letra, seguido de un punto "." o
                               de un parentesis ")"  sera reconocido como lista.
                               <OL> o <UL> se incluira para numeros o letras respectivamente
                               -. guio
                               n para lista
                               +. signo mas para lista
                               *. asterisco para lista
                               1. numeros
                               A. letra mayuscula
                               a. letra misnuscula
                               1) numero seguido de un parentesis
                            '''
                            '''Si existe parrafo almacenado, incorporar antes'''
                            if self._text.__len__():
                                self.html.append('<p>%s</p>' % ''.join(self._text))
                            self._elements = list()
                            # El indicador de lista, por defecto sera un punto
                            if re.search(r'^[0-9]{1,2}(\)|\.){1}\s',linea):
                                # excepto si el primer elemento de lista esta indicado con un numero ordinal
                                self._listType = True
                            else:
                                self._listType = False
                            # Eliminar codigo de lista y seleccionar solo item de lista                            repl = re.findall(r'^([a-zA-Z0-9]{1,2}|\-{1}|\+{1}|\*{1})(\)|\.|\S|\s)',linea)
                            repl = re.findall(r'^([a-zA-Z0-9]{1,2}|\-{1}|\+{1}|\*{1})(\)|\.){1}\s',linea)
                            self._elements.append(linea.replace(''.join(repl[0]),'').strip())
                            while linea.__len__()>0:
                                pos = f.tell()
                                linea = self.readFile(f)
                                if re.search(r'^(\w|\-|\+|\*){1}(\)|\.){1}',linea.replace(' ','')):
                                    # Eliminar codigo de lista y seleccionar solo item de lista
                                    repl = re.findall(r'^([a-zA-Z0-9]{1,2}|\-{1}|\+{1}|\*{1})(\)|\.){1}\s',linea)
                                    self._elements.append(linea.replace(''.join(repl[0]),'').strip())
                                else:
                                    f.seek(pos)
                                    break
                            self.listElement(self._listType,*self._elements)
                            self._elements = []
                            linea = ''
                            self._text = []

                            '''Buscar imagen'''
                        elif re.search(r'^\.{2}?image:{2}',linea.replace(' ','')):
                            '''.. image:: images/biohazard.png
                               :height: 100px
                               :width: 200px
                               :alt: alternate text

                               <img src="images/biohazard.png" alt="biohazard" height="42" width="42">
                            '''
                            token = {'image':'','height':'','width':'','alt':''}
                            token['image']= linea.replace('..','').replace('image::','').strip()
                            while linea.__len__()>0:
                                pos = f.tell()
                                linea = self.readFile(f)
                                if re.search(r'^\:{1}(.+?)\:{1}',linea):
                                    for i in list(token.keys()):
                                        if (':%s:' % i) in linea:
                                            token[i] = linea.replace(':%s:' % i,'').strip()
                                            break
                                else:
                                    f.seek(pos)
                                    break
                            '''Crear imagen en html'''
                            self._text.append('<img src="%s" alt="%s" height="%s" width="%s">' % (token["image"],token["alt"],token["height"],token["width"]))
                            token = {}
                            linea = ''

                            '''Sustituir imagen en un texto'''
                        elif re.search(r'^\.{2}?\|(.+?)\|?image:{2}',linea.replace(' ','')):
                            '''The |biohazard| symbol must be used on containers used to dispose of medical waste.
                               .. |biohazard| image:: biohazard.png
                            '''
                            replace_text = []
                            token = {'image':'','height':'','width':'','alt':''}
                            '''Buscar valor/es a reemplazar en el texto'''
                            replace_text = re.findall(r'\|(.+?)\|',linea)
                            for i in replace_text:
                                linea = linea.replace(('|%s|' % i),'')
                            '''Buscar link de imagen solo'''
                            token['image']= linea.replace('..','').replace('image::','').strip()
                            while linea.__len__()>0:
                                pos = f.tell()
                                linea = self.readFile(f)
                                if re.search(r'^\:{1}(.+?)\:{1}',linea):
                                    for i in list(token.keys()):
                                        if (':%s:' % i) in linea:
                                            token[i] = linea.replace(':%s:' % i,'').strip()
                                            break
                                else:
                                    f.seek(pos)
                                    break
                            linea = ('<figure><img src="%s" alt="%s" height="%s" width="%s"><figcaption><p>%s</p></figcaption></figure>' % (token["image"],token["alt"],token["height"],token["width"],token["alt"]))
                            if self._text.__len__():
                                '''Reemplaza los valores del texto por el link a la imagen'''
                                for j in replace_text:
                                    self._text = [i.replace( '|%s|' % j ,linea)  for i in self._text]
                            token = {}
                            replace_text = []
                            linea = ''


                            '''Incorporar codigo de programacion'''
                        elif re.search(r'^\.{2}?code-block:{2}',linea.replace(' ','')):
                            '''.. code-block::
                               def my_function():
                                   "just a test"
                                   print 8/2
                               ::

                               El codigo finalizara con :: en la ultima linea.

                               En el codigo, la libreria sustituira < por $lt; y > por &gt; para no confundirse con la
                               codificacion HTML.

                               Generando el siguiente codigo:
                               <code>   ...codigo ... </code>

                            '''
                            replace_text = []
                            '''La primera linea que identifica code-block no se utilizara'''
                            replace_text.append('<br><br><code display="block">')
                            while linea.__len__()>0:
                                '''La linea se debe leer sin tratamiento'''
                                linea = f.readline().rstrip()
                                if re.search(r'^\:{2}',linea) and linea.strip().__len__()==2:
                                    'Es el final del codigo'
                                    replace_text.append('</code><br><br>')
                                    break
                                else:
                                    replace_text.append(linea.replace('<','&lt;').replace('>','&gt;').replace('"','&quot;').replace(' ','&nbsp;')+'<br>')
                            self._text.append(''.join(replace_text))

                            '''Incorporar codificacion matematica'''
                        elif re.search(r'^\.{2}?math:{2}',linea.replace(' ','')):
                            '''.. math::

                               ::
                               Se emplea la notación MathML  (http://www.w3.org/TR/MathML)

                               La expresion se sustituira por <math display="block"> ...codigo matematico ... </math>
                            '''
                            replace_text = []
                            '''La primera linea que identifica code-block no se utilizara'''
                            replace_text.append('<br><br><math display="block">')
                            while linea.__len__()>0:
                                '''La linea se debe leer sin tratamiento'''
                                linea = f.readline().strip()
                                if re.search(r'^\:{2}',linea) and linea.strip().__len__()==2:
                                    'Es el final del codigo'
                                    replace_text.append('</math><br><br>')
                                    break
                                else:
                                    replace_text.append(linea)
                            self._text.append(''.join(replace_text))

                            '''Incorporar tablas'''
                        elif re.search(r'^\.{2}?table:{2}',linea.replace(' ','')):
                            '''.. table:: Truth table for "not"
                               =====  =====
                                 A    not A
                               =====  =====
                               False  True
                               True   False
                               =====  =====
                               ::


                               La expresion se sustituira por
                               <table border="1">
                               <colgroup>
                                  <col width="31%"> .. </col>
                                  <col width="31%"> .. </col>
                                  <col width="38%"> .. </col>
                               </colgroup>
                               <thead valign="bottom">
                                  <tr> .. </tr>
                                  <tr> .. </tr>
                               </thead>
                               <tbody valign="top">
                                  <tr> .. </tr>
                                  <tr> .. </tr>
                                  <tr> .. </tr>
                                  <tr> .. </tr>
                               </tbody>
                               </table>
                            '''
                            pass

                            '''Incorporar hiperlink'''
                        elif re.search(r'^\.{2}_{1}.*:{1}',linea.replace(' ','')):
                            '''passExternal hyperlinks, like Python_.
                               .. _Python: http://www.python.org/

                               se sustituye _Python en el lugar de Python_
                               <a href="http://www.python.org/">Python</a>

                            '''
                            replace_text = []
                            replace_text = re.findall(r'^\.{2}.*_{1}(.+?):{1}',linea)
                            for i in replace_text:
                                linea = linea.replace(' ','').replace(('.._%s:' % i),'')
                                linea = ('<a href="%s" >%s</a>' % (linea,i))
                                if self._text.__len__():
                                    '''Reemplaza los valores de hiperlink en el texto'''
                                    for j in range(self._text.__len__()):
                                        self._text[j] = self._text[j].replace(("%s_" % i), linea )
                            replace_text = []
                            linea = ''

                            '''Buscar hiperlink anonimos http'''
                        elif re.search(r'http://(.+?)\s+',linea) or re.search(r'https://(.+?)\s+',linea):
                            '''External hyperlinks anonimos
                               se sustituye http://www.python.org/ por
                               <a href="http://www.python.org/">http://www.python.org/</a>
                            '''
                            replace_text = []
                            replace_text = re.findall(r'http://(.+?)\s+',linea)
                            for i in replace_text:
                                linea = linea.replace(('http://%s' % i),('<a href="http://%s" >http://%s</a>' % (i,i)))
                            replace_text = []
                            replace_text = re.findall(r'https://(.+?)\s+',linea)
                            for i in replace_text:
                                linea = linea.replace(('https://%s' % i),('<a href="https://%s" >https://%s</a>' % (i,i)))
                            replace_text = []

                        '''inserta una linea en blanco'''
                        if re.search(r'^\:{2}',linea) and linea.strip().__len__()==2:
                            '''::
                               Identifica una linea de salto
                            '''
                            '''Si linea en blanco o salto, se incorporan a html todas las lineas leidas como parrafo'''
                            if self._text.__len__():
                                self.html.append('<p>%s</p>' % ''.join(self._text))
                                self._text = []
                            self.html.append('<br>')
                            linea = ''

                        '''inserta una linea de separacion'''
                        if re.search(r'^\_{3}',linea) and linea.strip().__len__()==3:
                            '''___
                               Inserta una linea <hr>
                            '''
                            '''Si linea de separacion, se incorporan a html todas las lineas leidas como parrafo'''
                            if self._text.__len__():
                                self.html.append('<p>%s</p>' % ''.join(self._text))
                                self._text = []
                            self.html.append('<hr>')
                            linea = ''

                        '''otras directivas no soportadas se anulan'''
                        if re.search(r'^\.{2}(.+?):{2}',linea.replace(' ','')):
                            '''..   ::
                            '''
                            linea = ''

                        '''texto con formato bold'''
                        if re.search(r'\*{2}.*\*{2}',linea.replace(' ','')):
                            replace_text = []
                            replace_text = re.findall(r'\*{2}.*\*{2}',linea)
                            if replace_text:
                                for i in replace_text:
                                    linea = linea.replace( i ,'<b>%s</b>' % i.replace('*',''))

                        '''texto con formato italic'''
                        if re.search(r'\*{1}.*\*{1}',linea.replace(' ','')):
                            replace_text = []
                            replace_text = re.findall(r'\*{1}.*\*{1}',linea)
                            if replace_text:
                                for i in replace_text:
                                    linea = linea.replace( i ,'<i>%s</i>' % i.replace('*',''))


                        '''Se carga el texto que no es de control'''
                        if linea.__len__()>0:
                            '''Guarda informacion hasta realizar tratamiento'''
                            self._text.append(' '+linea)

                        linea = ''


                    else:
                        ''' Buscar informacion literal'''
                        if re.search(r'^..include::',linea.replace(' ','')):
                            pass
            else:
                '''No existe fichero rST'''
                _file = ''

            '''Antes de salir comprobar pila de stack'''
            if self._stack.__len__():
                self._stack_reverse = self._stack
                self._stack_reverse.reverse()
                for i in range(self._stack_reverse.__len__()):
                    self._text.append(self._stack_reverse.pop())
                    self._deep -= 1
                self._stack = []

        except Exception as e:
            tb = sys.exc_info()[2]
            print('Error <%s> en linea %s !!!' % (str(e),tb.tb_lineno) )
        finally:
            if _file and os.path.isfile(_file):
                f.close()

    def get(self,withHtml=False):
        '''Recupera el contenido HTML generado desde el fichero leido'''
        if withHtml:
            # Puede devolver una página HTML totalmente funcional
            self.html = '<!DOCTYPE html><html><head></head><body>'+'\n'.join(self.html)+'</body></html>'
        else:
            # o codigo HTML para incluir en una pagina HTML
            self.html = '\n'.join(self.html)
        return self.html

    def readFile( self , f):
        linea = ' '+f.readline()
        # Pueden aparecer en la linea Byte Order Mark o marca en little-endian \xEF\xBB\xBF que debemos eliminar
        linea = linea.replace('\xef\xbb\xbf','')
        return linea.strip()

    def listElement(self, withNumber=False, *items):
        '''Creara una lista de numeros o puntos a partir de una lista de elementos'''
        if len(items):
            if withNumber:
                # lista con numeros
                self.html.append('<ol>')
            else:
                # lista con puntos
                self.html.append('<ul>')
            for item in items:
                self.html.append('<li>%s</li>' % item)
            if withNumber:
                # finaliza lista con numeros
                self.html.append('</ol>')
            else:
                # finaliza lista con puntos
                self.html.append('</ul>')

    def headingElement(self, content):

        newid = makeID(content, self.ids)
        self.ids.append(newid)

        self.write('<h2 id="%s">' % newid)
        self.write(content)
        self.write('</h2>')
        self.write('\n')

    def paragraphElement(self, content):
        self.write('<p>')
        self.write(content)
        self.write('</p>')
        self.write('\n')



if __name__ == "__main__":
    print ('''copyright {0}'''.format( __copyright__))
    print ('''license {0}'''.format( __license__))
    print ('''version {0}'''.format( __version__))
    if len(sys.argv) < 2:
        sys.stderr.write("for help use -h o --help")
    elif sys.argv[1]=='-h' or sys.argv[1]=='--help':
        print (b'''
        Puede cargar un texto read Structured Text que siempre debe tener una extension .rst :
        pagina = rst4html('rst_text.rst')
        posteriormente recuperara los datos html creados con :
        pagina.get()
        ''')

