#!/usr/bin/python3
import argparse
import os
import datetime
import shutil
import gettext
import codecs
import sys

gettext.install('myweb', 'po/locale')
class SetCommons:
    def __init__(self):
        self.arr=[]

    def append(self, o):
        self.arr.append(o)

    def length(self):
        return len(self.arr)

class Screenshot:
    def __init__(self, title, file, dt, width=100):
        self.title=title
        self.file=file
        self.datetime=dt
        self.width=width

    def html(self):
        return '
\n'.format(self.file, self.width, self.datetime, self.title)

class SetScreenshots(SetCommons):
    def __init__(self):
        SetCommons.__init__(self)
    def sort(self):
        self.arr=sorted(self.arr, key=lambda s: s.datetime,  reverse=True)

class SetDistFiles(SetCommons):
    def __init__(self):
        SetCommons.__init__(self)

    def find(self, sourcecode, so):
        for d in self.arr:
            if d.sourcecode==sourcecode and  d.so==so:
                return d
        return None

class DistFile:
    def __init__(self, sourcecode, so, path):
        self.sourcecode=sourcecode#src or bin
        self.so=so #"Windows or Linux"
        self.path=path

class Release:
    def __init__(self,  dt,  version, subversion):
        self.datetime=dt
        self.subversion=subversion
        self.version=version
        self.distfiles=SetDistFiles()

    def appendDistfile(self, distfile):
        self.distfiles.append(distfile)

    def html(self):
        body=""
        body=body+'
\n'
        body=body+'        '+str(self.datetime)[:16]+'	\n'
        body=body+'        '+_('Versión {}').format(self.version)+'	\n'
        body=body+'        r{}	\n'.format(self.subversion)

        body=body+""

        d=self.distfiles.find(True,"Linux")
        if d!=None:
            body=body+''.format(d.path)
        d=self.distfiles.find(True,"Windows")
        if d!=None:
            body=body+''.format(d.path)
        body=body+"	"
        d=self.distfiles.find(False,"Linux")
        if d!=None:
            body=body+''.format(d.path)
        d=self.distfiles.find(False,"Windows")
        if d!=None:
            body=body+''.format(d.path)
        body=body+'	\n'
        body=body+'
\n'
        return body

    ##Date string from self.datetime and without -
    def date_linux(self):
        return str(self.datetime.date()).replace("-", "")

    ##Date string from self.datetime for windows versions
    def date_windows(self):
        return "{}.{}.{}".format(self.datetime.year, self.datetime.month, self.datetime.day)

class SetPages:
    def __init__(self,  output, language, projects):
        self.projects=projects
        self.output=output
        self.language=language

    def write(self):
        self.Index()
        self.QuienSoy()
#        self.Proyectos()

    def Index(self):
        self.pagina_proyectos("index.html")

    def pagina_proyectos(self, page):
        p=Page("Página principal de Turulomio", page)
        body='
'+_('Mis proyectos')+'
\n'
        body=body+ _('Todos mis proyectos de código abierto los puedes encontrar en el Repositorio de GITHUB.
')
        body=body+ _('De entre ellos destacan:')+'\n'
        body=body+ '
\n'
        body=body+ '
DevicesInLan
\n'
        body=body+ '
DidYouReadMe
\n'
        body=body+ '
glParchis
\n'
        body=body+ '
Kdepim2google
\n'
        body=body+ '
RecoverMyPartition
\n'
        body=body+ '
Xulpymoney
\n'
        body=body+ '
ManGenerator
\n'
        body=body+ '
OfficeGenerator
\n'
        body=body+ '
RecPermissions
\n'
        body=body+ '
Too many files
\n'
        body=body+ '
TtyRecGenerator
\n'
        body=body+ '

\n'
        body=body+ '
\n'
        p.write(body)

        body=body+ _('También participo o he participado en otros proyectos como:')+'\n'
        body=body+ '
\n'
        body=body+ '
Navit
\n'
        body=body+ '
OpenStreetMap
\n'
        body=body+ '
\n'
        p.write(body)

    def QuienSoy(self):
        p=Page(_("¿Quién soy?"), 'quiensoy.html')
        body="
{}
".format(_("¿Quién soy?"))
        body=body+ _('Trabajo en las tecnologías de la información en una empresa informática desde 1998.')+'
\n'
        body=body+ _('Tuve mi primer contacto con el Sistema Operativo Linux en el año 1999 y desde entonces lo he usado constantemente como administrador, desarrollador y usuario.')+'
\n'
        body=body+ _("He utilizado muchas distribuciones Linux, Red Hat, Mandrake, Suse, Ubuntu, Kubuntu, Debian.., pero ninguna me satisfizo completamente hasta que probe Gentoo. Llevo usándola desde el 2003 como desktop y server, con total satisfacción.")+"
\n"
        body=body+ _('Tengo mi propio portage que puedes encontar en')+' GitHub
\n'
        body=body+ _('Además pondré en esta página web todo tipo de noticias de interés general, relacionados con mis hobbies, la lectura, la ciencia, etcétera.')+'
\n'
        p.write(body)

    def Proyectos(self):
        self.pagina_proyectos("proyectos.html")

class SetProjects(SetCommons):
    def __init__(self, output, language):
        SetCommons.__init__(self)
        self.output=output
        self.language=language

    def write(self):
        self.kdepim2google()
        self.glparchis()
        self.didyoureadme()
        self.recovermypartition()
        self.spoken_uptime()
        self.devicesinlan()

    def kdepim2google(self):
        def installation():
            s="
"+_("Antes de la instalación")+"
"
            s=s+_("Antes de la instalación debo tener instaladas de las siguientas herramientas:")
            s=s+"
"
            s=s+"
" +_("Gcc: Compilador")+"
"
            s=s+"
" +_("Cmake: Utilidades de compilación")+"
"
            s=s+"
" +_("QtCore: Librerías de programación")+"
"
            s=s+"
"
            s=s+"
"+_("Instalación")+"
"
            s=s+_("Para la instalación del programa realizaré lo siguiente:")
            s=s+"
"
            s=s+"
"+_("Descagaré la última versión del programa y la descomprimiré")+"
"
            s=s+"
"+_("Entro en el directorio creado al descomprimir y creo la carpeta build con #mkdir build")+"
"
            s=s+"
"+_("Entro en el directorio build y ejecuto #cmake .. ")+"
"
            s=s+"
"+_("Ejecuto make y make install (por defecto se instala en /usr/local/) ")+"
"
            s=s+"
"
            p.installation=s

        def releases():
            p.releases.append(Release(datetime.datetime(2014,9,1,17,0),"0.1.1",3))
            p.releases.append(Release(datetime.datetime(2014,9,4,6,0),"0.2.0",11))

            r=Release(datetime.datetime(2014,9,11,6,0),"0.3.0", 15)
            r.appendDistfile(DistFile(True, "Linux", "https://sourceforge.net/projects/kdepim2google/files/kdepim2google/0.3.0/kdepim2google-src-0.3.0.tar.gz/download" ))
            r.appendDistfile(DistFile(True, "Windows", "https://sourceforge.net/projects/kdepim2google/files/kdepim2google/0.3.0/kdepim2google-src-0.3.0.tar.gz/download" ))
            p.releases.append(r)

            r=Release(datetime.datetime(2014,9,17,6,0),"0.4.0", 17)
            r.appendDistfile(DistFile(True, "Linux", "https://sourceforge.net/projects/kdepim2google/files/kdepim2google/0.4.0/kdepim2google-src-0.4.0.tar.gz/download" ))
            r.appendDistfile(DistFile(True, "Windows", "https://sourceforge.net/projects/kdepim2google/files/kdepim2google/0.4.0/kdepim2google-src-0.4.0.tar.gz/download" ))
            p.releases.append(r)

            r=Release(datetime.datetime(2014,12,30,19,25),"0.5.0", 18)
            r.appendDistfile(DistFile(True, "Linux", "https://sourceforge.net/projects/kdepim2google/files/kdepim2google/0.5.0/kdepim2google-src-0.5.0.tar.gz/download" ))
            r.appendDistfile(DistFile(True, "Windows", "https://sourceforge.net/projects/kdepim2google/files/kdepim2google/0.5.0/kdepim2google-src-0.5.0.tar.gz/download" ))
            p.releases.append(r)

        def news():
            p.news.append(News(datetime.datetime(2014,9,1,17,00),
                                                   _("Proyecto Kdepim2Google"),
                                                   p.htmlImage(),
                                                   _("""Primera versión de estas utilidades de integración de Kdepim en Google""")))

            p.news.append(News(datetime.datetime(2014,9,11,6,0),
                                                   _("Proyecto Kdepim2Google. Nueva versión {}").format("0.3.0"),
                                                   p.htmlImage(),
                                                   _("""Nueva versión de estas utilidades de integración de Kdepim en Google. Se han realizado los siguientes cambios:

Se han arreglado los contadores de contactos

Se ha mejorado la búsqueda de la cadena 'Ocultar cumpleaños'

Se ha mejorado la ayuda del programa

""")))

            p.news.append(News(datetime.datetime(2014,9,17,6,0),
                                                   _("Proyecto Kdepim2Google. Nueva versión {}").format("0.4.0"),
                                                   p.htmlImage(),
                                                   _("""Nueva versión de estas utilidades de integración de Kdepim en Google. Se han realizado los siguientes cambios:

Arreglado error en la traducción española

""")))
            p.news.append(News(datetime.datetime(2014,12,30,19,25),
                                                   _("Proyecto Kdepim2Google. Nueva versión {}").format("0.5.0"),
                                                   p.htmlImage(),
                                                   _("""Nueva versión de estas utilidades de integración de Kdepim en Google. Se han realizado los siguientes cambios:

Para extraer el nombre del contacto, ahora busca también en N:

""")))
        ##############################
        p=Project("kdepim2google", "Kdepim2Google", "images/google.png", "app-misc/kdepim2google")
        p.brief=_('Utilidades de integración entre Kdepim y Google')
        news()
        releases()
        installation()
        p.writePage()
        self.append(p)

    def didyoureadme(self):
        def screenshots():
            p.screenshots.append(Screenshot("", "images/didyoureadme-20121220-screenshot.2.png",  datetime.datetime(2012, 12, 20), 40))
            p.screenshots.append(Screenshot("", "images/didyoureadme-20121220-screenshot.3.png",  datetime.datetime(2012, 12, 20), 60))
            p.screenshots.append(Screenshot("", "images/didyoureadme-20160521-screenshot.1.png",  datetime.datetime(2016, 5, 21), 60))

        def releases():
            r=Release(datetime.datetime(2014,1,28,6,0),"20140128", 43)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/didyoureadme/files/didyoureadme/didyoureadme-20140128/didyoureadme-src-linux-20140128.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/didyoureadme/files/didyoureadme/didyoureadme-20140128/didyoureadme-bin-linux-20140128.tar.gz/download" ))
            p.releases.append(r)

            r=Release(datetime.datetime(2015,1,28,6,0),"20150128", 58)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/didyoureadme/files/didyoureadme/didyoureadme-20150128/didyoureadme-src-linux-20150128.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/didyoureadme/files/didyoureadme/didyoureadme-20150128/didyoureadme-bin-linux-20150128.tar.gz/download" ))
            p.releases.append(r)

            r=Release(datetime.datetime(2016,5,21,8,0),"20160521", 103)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/didyoureadme/files/didyoureadme/didyoureadme-20150128/didyoureadme-src-linux-20150128.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/didyoureadme/files/didyoureadme/didyoureadme-20150128/didyoureadme-bin-linux-20150128.tar.gz/download" ))
            p.releases.append(r)

        def news():
            p.news.append(News(datetime.datetime(2012,12,21,12,12),
                                                   _("Proyecto DidYouReadMe"),
                                                   p.htmlImage(),
                                                   _("Se ha publicado el código completamente funcional en el subversión del nuevo proyecto DidYouReadMe.")+" "+_("Es un programa que envía correo a una serie de usuarios controlando el envío y la recepción de los mismos.")))

            p.news.append(News(datetime.datetime(2013,1,8,11,12),
                                                   _("Proyecto DidYouReadMe. Versión")+" 20130108",
                                                   p.htmlImage(),
                                                   _("Se ha publicado la primera versión de DidYouReadMe.")))

            p.news.append(News(datetime.datetime(2013,1,14,14,12),
                                                   _("Proyecto DidYouReadMe. Versión")+" 20130114",
                                                   p.htmlImage(),
                                                   _("Nueva versión. Se ha mejorada la visualización de ficheros y la edición de comentarios de los documentos.")))

            p.news.append(News(datetime.datetime(2013,1,16,12,12),
                                                   _("Proyecto DidYouReadMe. Versión")+" 20130116",
                                                   p.htmlImage(),
                                                   _("""Nueva versión. Se han realizado los siguientes cambios:

Añadido soporte de backup del sistema.

Corregido bug importante al mandar correos sin comentario.

""")))

            p.news.append(News(datetime.datetime(2013,2,8,16,00),
                                                   _("Proyecto DidYouReadMe. Versión")+" 20130208",
                                                   p.htmlImage(),
                                                   _("""Nueva versión. Se han realizado los siguientes cambios:

Se han cambiado los combos de grupo y usuarios por listas, mejorando el interfaz de usuario

frmMain ahora actualiza las tablas automáticamente

Se ha quitado bottle.py del proyecto, se debe usar el packete bottle de tu distribución

Al añadir un documento ahora se comprueba si el fichero seleccionado existe y es un fichero

Se ha mejorado el interfaz de usuario

""")))

            p.news.append(News(datetime.datetime(2013,7,10,9,40),
                                                   _("Proyecto DidYouReadMe. Versión")+" 20130710",
                                                   p.htmlImage(),
                                                   _("""Nueva versión. Se han realizado los siguientes cambios:

Solucionado error al cambiar el puerto del servidor web

El correo muestra la fecha y hora en formato internacional

Se ha añadido un selector por meses para mostrar documentos cerrados

Ahora no recorta los datetimes con 00 segundos

Ahora members es un set, no un list

""")))

            p.news.append(News(datetime.datetime(2013,7,11,9,0),
                                                   _("Proyecto DidYouReadMe. Versión")+" 20130711",
                                                   p.htmlImage(),
                                                   _("""Nueva versión. Se han realizado los siguientes cambios:

Solucionado error al poner el día de la semana en el contenido del correo

""")))

            p.news.append(News(datetime.datetime(2014,1,28,9,0),
                                                   _("Proyecto DidYouReadMe. Versión")+" 20140128",
                                                   p.htmlImage(),
                                                   _("""Nueva versión. Se han realizado los siguientes cambios:

Solucionado error con las fechas de los correos

Añadida opción de actualizar tablas automaticamente en frmSettings

Los informes muestran ahora el identificador interno del documento

""")))

            p.news.append(News(datetime.datetime(2015,1,28,6,0),
                                                   _("Proyecto DidYouReadMe. Versión")+" 20150128",
                                                   p.htmlImage(),
                                                   _("""Nueva versión. Se han realizado los siguientes cambios:

Añadida vigencia a los documentos

Los documentos se almacenan ahora dentro de la base de datos

Mejorada la reutilización de código

Ahora puede borrar documentos en el modo administrador

""")))

            p.news.append(News(datetime.datetime(2016,5,21,9,0),
                                                   _("Proyecto DidYouReadMe. Versión")+" 20160521",
                                                   p.htmlImage(),
                                                   _("""Nueva versión. Se han realizado los siguientes cambios:

Se ha portado a Windows de 32 y 64 bits

Muchos cambios

""")))
        ##############################
        p=Project("didyoureadme", "DidYouReadMe", "images/noticia.jpg",  "app-office/didyoureadme")
        p.brief=_('Programa que envía correo a una serie de usuarios controlando el envío y la recepción de los mismos.')

        news()
        releases()
        screenshots()
        p.writePage()
        self.append(p)

    def glparchis(self):
        def screenshots():
            p.screenshots.append(Screenshot("", "images/glparchis-20120921-screenshot.1.png",  datetime.datetime(2012, 9, 21), 100))
            p.screenshots.append(Screenshot("", "images/glparchis-20120910-screenshot.1.png",  datetime.datetime(2012, 9, 10), 60))
            p.screenshots.append(Screenshot("", "images/glparchis-20120910-screenshot.2.png",  datetime.datetime(2012, 9, 10), 100))
            p.screenshots.append(Screenshot("", "images/glparchis-20120917-screenshot.1.png",  datetime.datetime(2012, 9, 17), 35))
            p.screenshots.append(Screenshot("", "images/glparchis-20120917-screenshot.2.png",  datetime.datetime(2012, 9, 17), 50))
            p.screenshots.append(Screenshot("", "images/glparchis-20180510-screenshot.4.png",  datetime.datetime(2018, 5, 10), 50))
            p.screenshots.append(Screenshot("", "images/glparchis-20180510-screenshot.3.png",  datetime.datetime(2018, 5, 10), 50))
            p.screenshots.append(Screenshot("", "images/glparchis-20180510-screenshot.2.png",  datetime.datetime(2018, 5, 10), 50))
            p.screenshots.append(Screenshot("", "images/glparchis-20180510-screenshot.1.png",  datetime.datetime(2018, 5, 10), 50))

        def releases():
            r=Release(datetime.datetime(2013,7,16,6,0),"20130716", 341)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20130716/glparchis-src-linux-20130716.tar.gz/download" ))
            r.appendDistfile(DistFile(True, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20130716/glparchis-src-windows-20130716.zip/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20130716/glparchis-bin-linux-20130716.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20130716/glparchis-bin-windows-20130716.exe/download" ))
            r=Release(datetime.datetime(2016,3,25,12,0),"20160325", 382)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160325/glparchis-src-20160325.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160325/glparchis-linux-20160325.x86_64.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160325/glparchis-windows-20160325.amd64.exe/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2016,6,23,0,0),"20160623", 392)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160623/glparchis-src-20160623.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160623/glparchis-linux-20160623.x86_64.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160623/glparchis-windows-20160623.amd64.exe/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2016,8,1,19,0),"20160801", 396)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160801/glparchis-src-20160801.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160801/glparchis-linux-20160801.x86_64.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160801/glparchis-windows-20160801.amd64.exe/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2016,8,12,18,30),"20160812", 402)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160812/glparchis-src-20160812.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160812/glparchis-linux-20160812.x86_64.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20160812/glparchis-windows-20160812.amd64.exe/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2017,7,26,5,30),"20170726", 432)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20170726/glparchis-20170726.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20170726/glparchis-linux-20170726.x86_64.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20170726/glparchis-2017.7.26-amd64.msi/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2018,3,7,18,50),"20180307", 442)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20180307/glparchis-20180307.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20180307/glparchis-linux-20180307.x86_64.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20180307/glparchis-2018.3.7-amd64.msi/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2018,3,8,18,50),"20180308", 443)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20180308/glparchis-20180308.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20180308/glparchis-linux-20180308.x86_64.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-20180308/glparchis-2018.3.8-amd64.msi/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2018,4,16,18,50), "20180416", 466)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-{0}/glparchis-{0}.tar.gz/download".format(r.date_linux())))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-{0}/glparchis-linux-{0}.x86_64.tar.gz/download".format(r.date_linux())))
            r.appendDistfile(DistFile(False, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-{0}/glparchis-{1}-amd64.msi/download".format(r.date_linux(), r.date_windows())))
            p.releases.append(r)
            r=Release(datetime.datetime(2018,5,10,5,0), "20180510", 489)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-{0}/glparchis-{0}.tar.gz/download".format(r.date_linux())))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-{0}/glparchis-linux-{0}.x86_64.tar.gz/download".format(r.date_linux())))
            r.appendDistfile(DistFile(False, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-{0}/glparchis-{1}-amd64.msi/download".format(r.date_linux(), r.date_windows())))
            p.releases.append(r)
            r=Release(datetime.datetime(2018,10,20,12,0), "20181020", 499)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-{0}/glparchis-{0}.tar.gz/download".format(r.date_linux())))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-{0}/glparchis-linux-{0}.x86_64.tar.gz/download".format(r.date_linux())))
            r.appendDistfile(DistFile(False, "Windows", "http://sourceforge.net/projects/glparchis/files/glparchis/glparchis-{0}/glparchis-{1}-amd64.msi/download".format(r.date_linux(), r.date_windows())))
            p.releases.append(r)
        def news():
            p.news.append(News(datetime.datetime(2010,3,14,10,27),
                                                   _("Publicación del proyecto glParchis"),
                                                   p.htmlImage(),
                                                   _("He integrado en mi página personal el proyecto glParchis")))

            p.news.append(News(datetime.datetime(2012,9,2,10,0),
                                                    _("Proyecto glParchis. Versión")+" 20120902",
                                                   p.htmlImage(),
                                                    _("Nueva versión basada en PyQt y opengl. Funciona para con varios usuarios. La inteligencia artificial debe ser mejorada")))

            p.news.append(News(datetime.datetime(2012,9,10,23,0),
                                                    _("Proyecto glParchis. Versión")+" 20120910",
                                                    p.htmlImage(),
                                                    _("Nueva versión con inteligencia artificial y corrigiendo numerosos bugs")))

            p.news.append(News(datetime.datetime(2012,9,14,17,0),
                                                    _("Proyecto glParchis. Versión")+" 20120914",
                                                    p.htmlImage(),
                                                    _("Sale la primera versión para el sistema operativo Windows. Además se corrigen varios errores")))

            p.news.append(News(datetime.datetime(2012,9,17,16,10),
                                                    _("Proyecto glParchis. Versión")+" 20120917",
                                                    p.htmlImage(),
                                                    _("Nueva versión con grandes mejoras en internacionalización y estabilidad del programa.")))

            p.news.append(News(datetime.datetime(2012,9,21,19,5),
                                                    _("Proyecto glParchis. Versión")+" 20120921",
                                                    p.htmlImage(),
                                                    _("Nueva versión con nuevos sonidos y texturas.")))

            p.news.append(News(datetime.datetime(2013,2,28,21,00),
                                                    _("Proyecto glParchis. Versión")+" 20130228",
                                                    p.htmlImage(),
                                                    _("""Nueva versión. Se han realizado los siguientes cambios:

Añadido soporte para mejores puntuaciones y estadísticas

Ahora pueden jugar 6 y 8 jugadores

""")))

            p.news.append(News(datetime.datetime(2013,7,16,16,10),
                                                    _("Proyecto glParchis. Versión")+" 20130716",
                                                    p.htmlImage(),
                                                    _("""Nueva versión. Se han realizado los siguientes cambios:

Cuando se come una ficha en la casilla de salida, esta ficha es la última en llegar si son dos distintas al color de la casilla

Cambiado el pink por el fuchsia, el cyan por darkturquoise y el orange por darkorange en colores web.

Se ha añadido compatibilidad con los highscores que usaban los antiguos

Se ha añadido soporte para autoguardado

Mejorado el interfaz de usuario

Solucionado error al cerrar la aplicación

Añadida opción de que el panel de usuario siga al jugador actual o no

Al guardar la partida se graba, el tiempo de inicio y el número de fichas comidas.

Se ha migrado a python3, yo lo he testeado con python 3.3

""")))

            p.news.append(News(datetime.datetime(2013,7,13,19,00),
                                                    _("Proyecto glParchis. Referencias en revistas"),
                                                    p.htmlImage(),
                                                    _("""Hoy he estado surfeando por la web y me encontré con un artículo de una revista digital cubana de software libre SWL-X con un artículo de glParchis. La revista te la puedes bajar en el siguiente enlace. Gracias por dar publicidad al proyecto y un saludo""")))

            p.news.append(News(datetime.datetime(2016,3,25,12,00),
                                                    _("Proyecto glParchis. Versión")+" 20160325",
                                                    p.htmlImage(),
                                                    _("""Hoy he publicado una nueva versión de glParchis, después de dos años, con los siguientes cambios:

La configuración del sonido se graba ahora en glparchis.cfg

Se ha cambiado el makefile para que se compile con make y se instale con make install

Ya no se van a distribuir los sources de windows

Ahora se generan versiones de Windows de 32 bits y de 64 bits

Se ha eliminado el soporte para phonon, ahora se utiliza QMultimedia

Se ha migrado a PyQt5

Se ha añadido el modo de pantalla completa

  """)))

            p.news.append(News(datetime.datetime(2016,6,23,9,00),
                                                    _("Proyecto glParchis. Versión")+" 20160623",
                                                    p.htmlImage(),
                                                    _("""Hoy he publicado una nueva versión de glParchis con los siguientes cambios:

Añadido el script project_i18n para traducir la documentación del proyecto

Ahora el tablero rota por su centro pulsando la letra m

Rendimiento mejorado

Corregido error con el icono de fullscreen

  """)))

            p.news.append(News(datetime.datetime(2016,8,1,19,00),
                                                    _("Proyecto glParchis. Versión")+" 20160801",
                                                    p.htmlImage(),
                                                    _("""Hoy he publicado una nueva versión de glParchis con los siguientes cambios:

La configuración del splitter del juego se guarda ahora en pantalla normal y en pantalla completa

  """)))

            p.news.append(News(datetime.datetime(2016,8,12,18,30),
                                                    _("Proyecto glParchis. Versión")+" 20160812",
                                                    p.htmlImage(),
                                                    _("""Hoy he publicado una nueva versión de glParchis con los siguientes cambios:

El estado de pantalla completa se guarda en el fichero de configuración

Añadido sistema de estadística en la base de datos de Sourceforge

Añadida opción de no contribuir a la estadística mundial

  """)))

            p.news.append(News(datetime.datetime(2017,7,26,5,30),
                                                    _("Proyecto glParchis. Versión")+" 20170726",
                                                    p.htmlImage(),
                                                    _("""Hoy he publicado una nueva versión de glParchis con los siguientes cambios:

Añadida la opción de configuración de retraso entre movimientos

Añadida la opción de configuración de nivel de dificultad

Añadida la opción de subir y bajar el tablero en el eje z con las teclas + y -

Añadidas estadísticas individuales de juego

El distribuible de windows se ha cambiado por un msi. DESINSTALE EL ANTIGUO GLPARCHIS ANTES DE INSTALAR EL NUEVO

  """)))
            p.news.append(News(datetime.datetime(2018,3,7,18,50),
                                                    _("Proyecto glParchis. Versión")+" 20180307",
                                                    p.htmlImage(),
                                                    _("""Hoy he publicado una nueva versión de glParchis con los siguientes cambios:

Ahora se puede tirar el dado pulsando ENTER

Añadida la opción de acercar y alejar el tablero en el menú

  """)))
            p.news.append(News(datetime.datetime(2018,3,8,18,50),
                                                    _("Proyecto glParchis. Versión")+" 20180308",
                                                    p.htmlImage(),
                                                    _("""Hoy he publicado una nueva versión de glParchis con los siguientes cambios:

Linux: Solucionado error al cargar los ficheros de traducción

  """)))
            p.news.append(News(datetime.datetime(2018,4,16,18,50),
                                                    _("Proyecto glParchis. Versión")+" 20180416",
                                                    p.htmlImage(),
                                                    _("""Hoy he publicado una nueva versión de glParchis con los siguientes cambios:

Windows: Solucionado bug molesto. El usuario necesitaba hacer varios clicks para mover una ficha

Mejorado el código de OpenGL y su documentación

Añadido el sistema de documentación de Doxygen para desarrolladores

Windows: Solucionado error al mostrar objetos en el menu Acerca De

  """)))
            p.news.append(News(datetime.datetime(2018,5,10,5,0),
                                                    _("Proyecto glParchis. Versión")+" 20180510",
                                                    p.htmlImage(),
                                                    _("""Hoy he publicado una nueva versión de glParchis con los siguientes cambios:

Añadido el modo de 3 jugadores

Ahora puedes informar de errores desde glParchis

  """)))
            p.news.append(News(datetime.datetime(2018,10,20,5,0),
                                                    _("Proyecto glParchis. Versión")+" 20181020",
                                                    p.htmlImage(),
                                                    _("""Hoy he publicado una nueva versión de glParchis con los siguientes cambios:

Añadida acción para ocultar/mostrar el panel izquierdo

Añadido el automatismo del dado

Añadido automatismo a las fichas cuando solo se puede mover una

  """)))

        ##############################
        p=Project("glparchis", "glParchis", "images/parchis.png",  "games-board/glparchis")

        brief='
'+_('Juego de parchís de código abierto. Su licencia es GPL. Esta desarrollado para Linux y Windows. Es una versión Beta. Si encuentras cualquier error no dudes en publicarlo en el foro o en ponerte en contacto conmigo')+'\n'
        brief=brief+'
'+_('Se cambio el desarrollo desde C++ a python utilizando las librerías PyQt y pyopengl, a partir de la versión 20120902')+'
\n'
        brief=brief+'
'+_('Puedes contribuir al mantenimiento de este proyecto haciendo una donación:')+'  \n'
        brief=brief+'
'+_('Puedes consultar las estadísticas de juego en este ') + ' '+_('enlace')+'\n'
        p.brief=brief
        news()
        releases()
        screenshots()
        p.writePage()
        self.append(p)

    def recovermypartition(self):
        def screenshots():
            p.screenshots.append(Screenshot("", "images/recovermypartition-20100520-screenshot.1.png",  datetime.datetime(2010, 5, 20), 100))

        def releases():
            r=Release(datetime.datetime(2010,5,21,6,0),"0.1", 8)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/recovermypartit/files/recovermypartit/0.1/recovermypartit-0.1.tar.gz/download" ))
            p.releases.append(r)

            r=Release(datetime.datetime(2010,8,12,6,0),"0.2", 20)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/recovermypartit/files/recovermypartit/0.2/recovermypartit-0.2.tar.gz/download" ))
            p.releases.append(r)

            r=Release(datetime.datetime(2014,9,12,6,0),"0.3", 34)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/recovermypartit/files/recovermypartit/0.3/recovermypartit-0.3.tar.gz/download" ))
            p.releases.append(r)

            r=Release(datetime.datetime(2018,1,24,7,0),"0.4", 38)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/recovermypartit/files/recovermypartit/{0}/recovermypartit-{0}.tar.gz/download".format(r.version)))
            p.releases.append(r)

        def news():
            p.news.append(News(datetime.datetime(2010,3,13,15,37),
                                                   _("Proyecto RecoverMyPartition"),
                                                   p.htmlImage(),
                                                   _("""Se ha añadido un nuevo proyecto para recuperar una partición utilizando python y sleuthkit.""")))
            p.news.append(News(datetime.datetime(2010,8,12,19,00),
                                                   _("Proyecto RecoverMyPartition"),
                                                   p.htmlImage(),
                                                   _("""He publicado una nueva versión de este proyecto:

Los cambios más importantes son:

Solucionado error si no se declaraba --partition

Se ha añadido soporte internacional para la documentación con omegat

Se ha cambiado la forma interna del proyecto

""")))
            p.news.append(News(datetime.datetime(2014,9,12,6,0),
                                                   _("Proyecto RecoverMyPartition"),
                                                   p.htmlImage(),
                                                   _("""Se ha publicado la versión 0.3 del proyecto que mejora el sistema de instalación y el de traducción.""")))
            p.news.append(News(datetime.datetime(2018,1,24,7,0),
                                                   _("Proyecto RecoverMyPartition"),
                                                   p.htmlImage(),
                                                   _("""Se ha publicado la versión 0.4 del proyecto que actualiza el código a PyQt5 y mejora el sistema de distribución.""")))

        ##############################
        p=Project("recovermypartit", "RecoverMyPartition", "images/forensics.png", "app-forensics/recovermypartition")
        p.brief=_('Programa de análisis forense para recuperar una partición')+'
\n'+_('Este programa surge de la necesidad de recuperar una partición rápidamente, incluyendo ficheros borrados y clusters sin asignar. Utiliza de backend a Sleuthkit. Es un programa en consola y tiene una gran velocidad.') + "\n"
        news()
        releases()
        screenshots()
        p.writePage()
        self.append(p)

    def spoken_uptime(self):
        def screenshots():
            p.screenshots.append(Screenshot("", "images/spoken-uptime-20150329-screenshot.1.png",  datetime.datetime(2015, 3, 29), 70))

        def releases():
            r=Release(datetime.datetime(2015,3,21,16,0),"0.1.0", 5)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/spoken-uptime/files/spoken-uptime/0.1.0/spoken-uptime-0.1.0.tar.gz/download" ))
            p.releases.append(r)

        def news():
            p.news.append(News(datetime.datetime(2015,3,21,16,0),
                                                   _("Proyecto Spoken-Uptime"),
                                                   p.htmlImage(),
                                                   _("""Se ha publicado una nueva versión:

Los cambios más importantes son:

Versión estable

""")))
        p=Project("spoken-uptime", "Spoken-Uptime", "images/spoken-uptime.png", "sys-apps/spoken-uptime")
        p.brief=_('Spoken linux system uptime') + "\n"

    def devicesinlan(self):
        def screenshots():
            p.screenshots.append(Screenshot("", "images/devicesinlan-20150519-screenshot.1.png",  datetime.datetime(2015, 5, 19), 60))

        def releases():
            r=Release(datetime.datetime(2015,5,9,15,0),"0.1.0", 3)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/0.1.0/devicesinlan-0.1.0.tar.gz/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2015,5,9,17,0),"0.2.0", 5)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/0.2.0/devicesinlan-0.2.0.tar.gz/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2015,5,9,19,0),"0.3.0", 7)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/0.3.0/devicesinlan-0.3.0.tar.gz/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2015,5,24,8,14),"0.4.0", 13)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/0.4.0/devicesinlan-0.4.0.tar.gz/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2015,6,17,16,0),"0.5.0", 15)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/0.5.0/devicesinlan-0.5.0.tar.gz/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2015,8,19,16,10),"0.6.0", 30)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/0.6.0/devicesinlan-0.6.0.tar.gz/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2017,2,5,15,10),"0.9.0", 76)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/0.9.0/devicesinlan-0.9.0.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/0.9.0/devicesinlan-linux-0.9.0.x86_64.tar.gz/download" ))
            r.appendDistfile(DistFile(False, "Windows", "https://sourceforge.net/projects/devicesinlan/files/devicesinlan/0.9.0/devicesinlan-windows-0.9.0.amd64.exe/download" ))
            p.releases.append(r)
            r=Release(datetime.datetime(2017,2,6,6,10),"0.10.0", 80)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-{0}.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-linux-{0}.x86_64.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Windows", "https://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-windows-{0}.amd64.exe/download".format(r.version) ))
            p.releases.append(r)
            r=Release(datetime.datetime(2017,2,7,6,10),"0.11.0", 85)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-{0}.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-linux-{0}.x86_64.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Windows", "https://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-windows-{0}.amd64.exe/download".format(r.version) ))
            p.releases.append(r)
            r=Release(datetime.datetime(2017,2,8,20,0),"1.0.0", 108)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-{0}.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-linux-{0}.x86_64.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Windows", "https://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-windows-{0}.amd64.exe/download".format(r.version) ))
            p.releases.append(r)
            r=Release(datetime.datetime(2017,2,9,6,0),"1.0.1", 110)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-{0}.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-linux-{0}.x86_64.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Windows", "https://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-windows-{0}.amd64.exe/download".format(r.version) ))
            p.releases.append(r)
            r=Release(datetime.datetime(2017,2,22,6,0),"1.0.2", 116)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-{0}.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-linux-{0}.x86_64.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Windows", "https://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-windows-{0}.amd64.exe/download".format(r.version) ))
            p.releases.append(r)
            r=Release(datetime.datetime(2017,2,26,6,0),"1.1.0", 119)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-{0}.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-linux-{0}.x86_64.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Windows", "https://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-windows-{0}.amd64.exe/download".format(r.version) ))
            p.releases.append(r)
            r=Release(datetime.datetime(2017,12,28,6,0),"1.2.0", 136)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-{0}.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Windows", "https://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-windows-{0}.amd64.exe/download".format(r.version) ))
            p.releases.append(r)
            r=Release(datetime.datetime(2018,1,21,8,0),"1.3.0", 150)
            r.appendDistfile(DistFile(True, "Linux", "http://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-{0}.tar.gz/download".format(r.version) ))
            r.appendDistfile(DistFile(False, "Windows", "https://sourceforge.net/projects/devicesinlan/files/devicesinlan/{0}/devicesinlan-windows-{0}.amd64.exe/download".format(r.version) ))
            p.releases.append(r)

        def news():
            p.news.append(News(datetime.datetime(2015,5,9,19,0),
                                                   _("Proyecto DevicesInLAN"),
                                                   p.htmlImage(),
                                                   _("""He publicado la primera versión de este proyecto:

Los cambios más importantes son:

Añadida la funcionalidad básica

Traducción española

""")))
            p.news.append(News(datetime.datetime(2015,5,24,8,14),
                                                    _("Proyecto DevicesInLAN. Versión")+" 0.4.0",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto:

Los cambios más importantes son:

Los dispositivos conocidos son listados y ordenados por su alias

Los dispositivos detectados en la red son listados ahora por su dirección IP

Ya se puede añadir y borrar dispositivos conocidos desde la línea de comandos

El listado de dispositivos de red no muestra más duplicados

""")))
            p.news.append(News(datetime.datetime(2015,6,17,16,0),
                                                    _("Proyecto DevicesInLAN. Versión")+" 0.5.0",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto.

La salida por consola muestra ahora el número de dispositivos conectados en la red""")))
            p.news.append(News(datetime.datetime(2015,8,19,16,10),
                                                    _("Proyecto DevicesInLAN. Versión")+" 0.6.0",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto.

Los cambios más importantes son:

Añadida búsqueda de dispositivos por ping

Añadido un escáner propio de ARP

Añadido el fichero ieee-oui.txt con la base de datos de marcas de MAC con get-oui de arp-scan

""")))
            p.news.append(News(datetime.datetime(2017,2,5,15,10),
                                                    _("Proyecto DevicesInLAN. Versión")+" 0.9.0",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto.

Los cambios más importantes son:

Mejorada la salida de lista en consola

Se ha añadido los tipos de dispositivo a las listas

""")))
            p.news.append(News(datetime.datetime(2017,2,6,6,10),
                                                    _("Proyecto DevicesInLAN. Versión")+" 0.10.0",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto.

Los cambios más importantes son:

Sustituido la clase Color por el packete Colorama

Ahora se ve Color en los informes de consola de windows

""")))
            p.news.append(News(datetime.datetime(2017,2,7,6,10),
                                                    _("Proyecto DevicesInLAN. Versión")+" 0.11.0",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto.

Los cambios más importantes son:

Sustituido Threads por PoolThreadExecutor para la concurrencia de peticiones

Añadido a las configuraciones la concurrencia

Mejorados los informes de consola

""")))
            p.news.append(News(datetime.datetime(2017,2,8,20,0),
                                                    _("Proyecto DevicesInLAN. Versión")+" 1.0.0",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto.

Los cambios más importantes son:

Página man creada

Carga y guarda listas de dispositivos

Comprueba nuevas versiones

Obtiene información estadística de instalaciones

""")))
            p.news.append(News(datetime.datetime(2017,2,9,6,0),
                                                    _("Proyecto DevicesInLAN. Versión")+" 1.0.1",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto.

Los cambios más importantes son:

Mejorado el sistema de estadísticas

Arreglados pequeños bugs

""")))
            p.news.append(News(datetime.datetime(2017,2,22,6,0),
                                                    _("Proyecto DevicesInLAN. Versión")+" 1.0.2",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto.

Los cambios más importantes son:

Añadido sistema de logs

Las estadisticas funcionan también en modo consola

Se puede añadir un tipo de dispositivo también en modo consola

Se puede cargar un ficheros, guardar la lista de dispositivos conocidos y borrar la base de datostambién en modo consola

""")))
            p.news.append(News(datetime.datetime(2017,2,26,6,0),
                                                    _("Proyecto DevicesInLAN. Versión")+" 1.1.0",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto.

Los cambios más importantes son:

Los logs de depuración están desactivados por defecto

El sistema de estadísticas manda ahora la plataforma

""")))
            p.news.append(News(datetime.datetime(2017,12,28,6,0),
                                                    _("Proyecto DevicesInLAN. Versión")+" 1.2.0",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto.

Los cambios más importantes son:

Ya no se necesita ser superusuario para ejecutar el programa

Mejorada la documentación y la traducción al español

Se ha quitado la dependencia de netifaces

Eliminado un acceso directo erróneo

Añadida busqueda por socket más rápida

Cambiado el sistema de distribución de innoreader al nativo de python

ANTES DE INSTALAR ESTA VERSION EN WINDOWS ELIMINE LA ANTERIOR DESDE PROGRAMAS DE WINDOWS
""")))
            p.news.append(News(datetime.datetime(2018,1,21,8,0),
                                                    _("Proyecto DevicesInLAN. Versión")+" 1.3.0",
                                                   p.htmlImage(),
                                                   _("""Nueva versión del proyecto.

Los cambios más importantes son:

Arreglado un error con el directorio de las traducciones

El dispositivo usado para lanzarr DevicesInLAN se muestra en azul

Ahora hay dos ejecutables uno para consola y otro para el interfaz gráfico

Se ha creado documentación para ambos ejecutables

Se ha eliminado la dependencia de man2html

""")))
        ##############################
        p=Project("devicesinlan", "DevicesInLAN", "images/devicesinlan.jpg", "net-analyzer/devicesinlan")
        p.brief=_('Muestra dispositivos en una red LAN. Puedes introducir dispositivos conocidos,  para detectar dispositivos extraños.') + "\n" + \
                    '
'+_('Puedes consultar las estadísticas de instalaciones en este ') + ' '+_('enlace')+'\n'
        news()
        releases()
        screenshots()
        p.writePage()
        self.append(p)

class SetNews(SetCommons):
    def __init__(self):
        SetCommons.__init__(self)

    def sort(self, reversed=True):
        self.arr=sorted(self.arr, key=lambda n: n.datetime,  reverse=reversed)

    def year_subarr(self, year):
        """Returns an arr with News Object of the year passed as parameter"""
        r=[]
        for n in self.arr:
            if n.datetime.year==year:
                r.append(n)
        return r

    def writePageAllNews(self, title, htmlpage):
        """title is the page title
        htmlpage name of the page.html"""
        p=Page(title, htmlpage)

        #Gets max and min years
        self.sort(False)
        minn=self.arr[0]
        maxn=self.arr[self.length()-1]
        years=range(minn.datetime.year, maxn.datetime.year+1)

        #Javascript code
        bo="
{}
".format(title)
        bo=bo+"""
"""

        #Genera el select con los años
        bo=bo+'\n
'+_('Seleccione un año:')+' \n'
        bo=bo+'    <select  onchange="optionCheck()" id="combo" size="1" class="form-select">\n'

        for year in years:
            if year==maxn.datetime.year:
                selected="selected"
            else:
                selected=""
            bo=bo+'        <option value="{0}" {1}>{0}</option>\n'.format(year,selected)
        bo=bo+'    </select>\n'
        bo=bo+'
\n'

        #Genera el listado de noticias
        for year in years:
            if year==maxn.datetime.year:
                display="inline-block"
            else:
                display="none"
            bo=bo+'\n
\n'.format(year,display)
            for n in self.year_subarr(year):
                bo=bo+n.html()
            bo=bo+'
\n'.format(year)

        p.write(bo)

    def linkToPageAllNews(self, page):
        return _("Para ver noticias anteriores pulse {}aquí{}").format('', '')

class SetReleases(SetCommons):
    def __init__(self):
        SetCommons.__init__(self)

    def html(self):
        self.sort()
        body=""
        body=body+'
\n'
        body=body+'
\n'
        body=body+'
\n'
        body=body+'         '+_('Fecha y hora')+'	\n'
        body=body+'         '+_('Versión del programa')+'	\n'
        body=body+'         '+_('Versión de subversión')+'	\n'
        body=body+'         '+_('Código fuente')+'	\n'
        body=body+'         '+_('Binario')+'	\n'
        body=body+'
\n'
        body=body+'
\n'
        for r in self.arr:
            body=body+r.html()
        body=body+'

\n'
        return body

    def sort(self):
        self.arr=sorted(self.arr, key=lambda n: n.subversion,  reverse=True)

class Project:
    def __init__(self, name,  beauty, image,  portagepath=None):
        self.name=name
        self.beauty=beauty
        self.portagepath=portagepath
        self.releases=SetReleases()
        self.news=SetNews()
        self.screenshots=SetScreenshots()
        self.image=image
        self.brief=""
        self.installation=""#instructions, dependencies request. HTML text. titles start by h3
        self.manual=""#instructions. HTML text

    def url_project(self):
        return "https://sourceforge.net/projects/{}".format(self.name)

    def htmlImage(self):
        return htmlImage(self.image, 90, 90)

    def writePage(self):
        """Write project page"""
        p=Page(_("Proyecto {}").format(self.beauty), self.name+".html")
        body='\n'.format(self.url_project())

        body=body+'
'+_('Proyecto {}').format(self.beauty)+'
\n'
        body=body+'
'+ self.brief+'
\n'
        body=body+'
'+_('Puedes consultar las {}estadísticas históricas{} del proyecto o las de {}los últimos 12 meses{} en Sourceforge.').format('','','','') +'\n'
        body=body+"""

""".format(self.name)

        body=body+'
\n'

        if self.news.length()>0:
            self.news.sort()
            body=body+'

'+_('Noticias')+'
\n'
            for n in self.news.arr[0:5]:#Only last 5
                body=body +n.html()
            body=body+"
"+self.news.linkToPageAllNews("allnews_"+self.name+".html")

            self.news.writePageAllNews(_("Todas las noticias del proyecto {}").format(self.beauty), "allnews_"+self.name+".html")

            body=body+'
\n'

        if self.screenshots.length()>0:
            self.screenshots.sort()
            body=body+'

'+_('Capturas de pantalla')+'
\n'
            for s in self.screenshots.arr:
                body=body +s.html()
            body=body+'
\n'

        body=body+'

Releases
\n'
        body=body+'   '+_('En esta sección se muestran las distintas versiones del programa y sus correspondencias con el subversión.')+'
\n'
        body=body+self.releases.html()

        if self.portagepath!=None:
            body=body+'   '+_('Si utilizas gentoo puedes usar el siguiente {}ebuild{}.').format('','')+'
\n'
        body=body+'
\n'

        if self.installation!="":
            body=body+'

'+_('Instalación')+'
\n'
            body=body+self.installation
            body=body+'
\n'

        if self.manual!="":
            body=body+'

'+_('Manual de usuario')+'
\n'
            body=body+self.manual
            body=body+'
\n'

        body=body+'

'+_('Código fuente')+'
\n'
        body=body+_('El código fuente en desarrollo te lo puedes bajar del subversion de {}.').format('Sourceforge')+'
\n'

        body=body+_('La documentación del código es generada automáticamente con la herramienta {}Doxygen{}.').format(''.format(self.name), "")+'\n'

        body=body+'
\n'
        body=body+'
\n'

        p.write(body)

class News:
   def __init__(self, dt,  title,    htmlimage, text):
      self.title=title
      self.datetime=dt
      self.htmlimage=htmlimage
      self.text=text

   def html(self):
      body='
\n'
      body=body + '
'+ self.title +'
\n'
      body=body + '
'+ str(self.datetime)[:16] +'
\n'
      body=body + '

' + self.htmlimage +'
	'+self.text+'

\n'
#      body=body + '

'+self.text+'
' + self.htmlimage +'

\n'
      body=body + '
 \n'
      return body

class Page:
    def __init__(self, title, file):
        self.title=title
        self.file=file
        self.dirsalida="publico/{0}".format(_("es"))
        self.generateSVG()

    def generateSVG(self):
        s='<?xml version="1.0" encoding="utf-8"  standalone="no"?>\n'
        s=s+'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n'
        s=s+'<svg flex="2" id="gi" width="800" height="50"  viewBox="0 0 800 50"  xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" >\n'
        s=s+'<defs>\n'
        s=s+'   <linearGradient id="two_hues">\n'
        s=s+'      <stop offset="0%" style="stop-color: #8799c7;"/>\n'
        s=s+'      <stop offset="100%" style="stop-color: #e3e3e3;"/>\n'
        s=s+'   </linearGradient>\n'
        s=s+'</defs>\n'
        s=s+'<rect x="0" y="0" width="800" height="50" style="fill: url(#two_hues);"/>\n'
        s=s+'<text x="20" y="40" style="font-weight:bold; fill: white; font-size: 14pt;">' + self.title + '</text>\n'
        s=s+'<image xlink:href="spanish.png" x="698" y="10" width="50" height="30"/>\n'
        s=s+'<image xlink:href="english.png" x="750" y="10" width="50" height="30"/>\n'
        s=s+'</svg>\n'
        f=codecs.open(self.dirsalida + "/images/"+self.file+".svg",'w','utf-8')
        f.write(s)
        f.close()

    def footer(self):
        s=  '
Copyright © 1998-{0} Mariano Muñoz. {1}: {2}.
\n'.format( datetime.date.today().year ,_("Última actualización"), datetime.datetime.now())
        s=s+'\n'
        return s

    def header(self):
       s=  '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n'
       s=s+'\n'
       s=s+'\n'
       s=s+'   <title>Mariano Muñoz > ' + self.title+ '</title>\n'
       s=s+'   <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">\n'
       s=s+'   <meta name="robots" content="index, follow"/>\n'
       s=s+'   <meta name="revisit-after" content="5 days"/>\n'
       s=s+'   <link rel="shortcut icon" href="/images/penguin.png"/>\n'
       s=s+'   <link rel="stylesheet" type="text/css" href="mariano.css"/>\n'
       s=s+'   <link rel="stylesheet" href="example.css" TYPE="text/css" MEDIA="screen">\n'
       s=s+'      <link rel="stylesheet" href="example-print.css" TYPE="text/css" MEDIA="print">\n'
       s=s+'   \n'

       s=s+'\n'
       s=s+'\n'
       return s

    def toc(self, noticias, descargas):
        toc=    '
\n'
        toc=toc+'
Tabla de contenidos
\n'
        if len(noticias)>0:
          toc=toc+'
Noticias\n'
          for i in noticias:
             toc=toc+'
' + i + '
\n'
          toc=toc+'
\n'
        if len(descargas)>0:
          toc=toc+'
Descargas\n'
          for i in descargas:
             toc=toc+'
' + i + '
\n'
          toc=toc+'
\n'
        toc=toc+'
\n'
        return toc

    def menu(self):
        s=  '
<object width="800" height="50" data="images/' + self.file+".svg"+ '" type="image/svg+xml">Uses SVG web standard, Internet Explorer has not. Use Firefox or Chromium.</object>
\n'
        s=s+'
\n'
        s=s+'

\n'
        s=s+'
\n'
        s=s+'
\n'
        s=s+'
'+_('Inicio')+'
\n'
        s=s+'
'+_('¿Quién soy?')+'
\n'
        s=s+'
\n'
        s=s+'
\n'
        s=s+'
\n'
        return s

    def body(self, bo):
        bod=     '
\n'
        bod=bod+bo
        bod=bod+'
\n'
        return bod

    def write(self, bo):
        res=self.header() + self.menu() + self.body(bo) + self.footer()
        f=codecs.open(self.dirsalida + "/"+self.file,'w','utf-8')
        f.write(res)
        f.close()

def htmlImage(file, height=90, width=90):
    return ''.format(file, height, width)

def redirect_index_html(output):
    body=     '\n'
    body=body + '<META HTTP-EQUIV="refresh" CONTENT="0;URL=http://turulomio.users.sourceforge.net/es/index.html">\n'
    body=body + '\n'
    f=codecs.open(output + "/index.html",'w','utf-8')
    f.write(body)
    f.close()

def redirect_projects():
    os.system("find redirects -type d -exec chmod -c 755 {} \;")
    os.system("find redirects -type f -exec chmod -c 644 {} \;")

    os.system("rsync -avzP -e 'ssh -l turulomio,devicesinlan' redirects/devicesinlan/ web.sourceforge.net:/home/groups/d/de/devicesinlan/htdocs/ --delete-after")
    os.system("rsync -avzP -e 'ssh -l turulomio,glparchis' redirects/glparchis/ web.sourceforge.net:/home/groups/g/gl/glparchis/htdocs/ --delete-after")
    os.system("rsync -avzP -e 'ssh -l turulomio,recovermypartit' redirects/recovermypartit/ web.sourceforge.net:/home/groups/r/re/recovermypartit/htdocs/ --delete-after")
    os.system("rsync -avzP -e 'ssh -l turulomio,didyoureadme' redirects/didyoureadme/ web.sourceforge.net:/home/groups/d/di/didyoureadme/htdocs/ --delete-after")
    os.system("rsync -avzP -e 'ssh -l turulomio,kdepim2google' redirects/kdepim2google/ web.sourceforge.net:/home/groups/k/kd/kdepim2google/htdocs/ --delete-after")

## This function generates myweb in a specific language
## @param language. Can be en or es
def generate_myweb(output, language):
    if language=="es":
        gettext.install('myweb', 'badlocale')
    else:
        lang1=gettext.translation('myweb', 'po/locale', languages=[language])
        lang1.install()
    print(_("  - Página generada con idioma '{}'").format(language))

    pages=SetPages(output, language, None)
    pages.write()
    redirect_index_html(output)

def main():
    parser=argparse.ArgumentParser(prog='myweb', description=_('Genera una página web personal, estática y multilingue'), epilog=_("Desarrollado por Mariano Muñoz 1998-{}".format(datetime.date.today().year)), formatter_class=argparse.RawTextHelpFormatter)
    group= parser.add_mutually_exclusive_group(required=True)
    group.add_argument('--redirects', help=_("Redirects several project pages to my personal web page"),  action='store_true', default=False)
    group.add_argument('--upload', help=_("Sube la página web a SourceForge"),  action='store_true', default=False)
    parser.add_argument('--output', help=_("Directorio de salida de la página web estática"),  action='store', default="publico/")
    args=parser.parse_args()

    if args.redirects==True:
        redirect_projects()
        sys.exit(0)

    if args.upload==True:
        os.system("xgettext -L Python --no-wrap --no-location --from-code UTF-8 -o po/myweb.pot myweb.py ")
        os.system("msgmerge -N --no-wrap -U po/en.po po/myweb.pot")
        os.system("mcedit po/en.po")
        os.system("msgfmt -cv -o po/locale/en/LC_MESSAGES/myweb.mo po/en.po")

    os.system("rm -rf {}".format(args.output))
    os.mkdir(args.output)
    os.mkdir("{}/es/".format(args.output))
    os.mkdir("{}/en/".format(args.output))
    os.mkdir("{}/es/images".format(args.output))
    os.mkdir("{}/en/images".format(args.output))
    shutil.copy("mariano.css", args.output)
    os.system("cp --preserve=all images/* {}/es/images/".format(args.output))
    os.system("cp --preserve=all images/* {}/en/images/".format(args.output))
    os.system("cp --preserve=all *.css js/tab/*.js js/tab/*.css {}/es".format(args.output))
    os.system("cp --preserve=all *.css js/tab/*.js js/tab/*.css {}/en".format(args.output))

    for language in ['es', 'en']:
        generate_myweb(args.output, language)

    if args.upload==True:
        os.system("find publico/ -type d -exec chmod 755 {} \;")
        os.system("find publico/ -type f -exec chmod 644 {} \;")
        os.system("rsync -avzP -e 'ssh -l turulomio' publico/ frs.sourceforge.net:/home/users/t/tu/turulomio/userweb/htdocs/ --exclude=doxygen --delete-after")

if __name__ == '__main__':
    main()

_=gettext.gettext#To avoid warnings in eric


import tkinter as tk
from quiz_brain import QuizBrain

THEME_COLOR = "#375362"

CATEGORIES = [
    "Any category",
    "Science & Nature",
    "Science: Computers",
    "Science: Mathematics",
    "Mythology",
    "Geography",
    "History",
    "Politics",
    "Animals",
    "Vehicles"
]

CATEGORY_IDS = [
    0,
    17,
    18,
    19,
    20,
    22,
    23,
    24,
    27,
    28
]

DIFFICULTY_LEVELS = [
    "Any difficulty",
    "Easy",
    "Medium",
    "Hard"
]

NO_QS = [
    '5',
    '10',
    '15',
    '20',
    '25'
]

class App(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs) # essentially you inherit from the class directly. this can be done by super but i need to test it out
        self.quiz = {}
        self.title(string="Quizzler")
        self.config(bg=THEME_COLOR)
        self.container = tk.Frame(self, padx=20, pady=20, bg=THEME_COLOR) # the window instance. this is needed to store and display the frames
        self.container.grid(row=0, column=0)
        self.frames = {
            "settings": Settings(parent=self.container, controller=self), #the controller argument lets you retreive any information you need from the object that is passed in as the controller
        }
        self.frames["settings"].grid(row=0, column=0)
        self.show_frame("settings")

    def show_frame(self, ui_area):
        frame = self.frames[ui_area]
        frame.tkraise()

class Settings(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.parameters = {"type": "boolean", "amount": 5, }

        self.controller = controller

        self.difficulty_label = tk.Label(self, text="Difficulty:")
        self.difficulty_label.grid(column=0, row=0)
        self.selected_difficulty = tk.StringVar()
        self.selected_difficulty.set(DIFFICULTY_LEVELS[0])
        self.difficulty_select = tk.OptionMenu(self, self.selected_difficulty, *DIFFICULTY_LEVELS)
        self.difficulty_select.grid(column=1, row=0)

        self.category_label = tk.Label(self, text="Category:")
        self.category_label.grid(column=0, row=1)
        self.selected_category = tk.StringVar()
        self.selected_category.set(CATEGORIES[0])
        self.category_select = tk.OptionMenu(self, self.selected_category, *CATEGORIES)
        self.category_select.grid(column=1, row=1)

        self.no_qs_label = tk.Label(self, text="Number of Questions:")
        self.no_qs_label.grid(column=0, row=2)
        self.selected_no_qs = tk.StringVar()
        self.selected_no_qs.set(NO_QS[0])
        self.no_qs = tk.OptionMenu(self, self.selected_no_qs, *NO_QS)
        self.no_qs.grid(column=1, row=2)

        self.submit_button = tk.Button(self, text="Submit", command=self.submit_data)
        self.submit_button.grid(column=0, row=3, columnspan=2)

    def submit_data(self):
        controller = self.controller
        category = self.selected_category.get()
        difficulty = self.selected_difficulty.get()
        no_qs = int(self.selected_no_qs.get())

        if category != "Any category":
            selection = CATEGORY_IDS[CATEGORIES.index(category)]
            self.parameters["category"] = selection

        if difficulty != "Any difficulty":
            self.parameters["difficulty"] = difficulty

        self.parameters["amount"] = no_qs
        controller.quiz = QuizBrain(self.parameters)
        controller.frames["quizui"] = QuizUI(parent=controller.container, controller=controller)
        controller.show_frame("quizui")
        self.destroy()

class QuizUI(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.config(bg=THEME_COLOR)
        self.controller = controller

        self.quiz_canvas = tk.Canvas(width=300, height=250, bg="white")
        self.question_text = self.quiz_canvas.create_text(150,
                                                          125,
                                                          text="PLACEHOLDER",
                                                          font=("Arial", 20, "italic"),
                                                          width=280)
        self.quiz_canvas.grid(row=1, column=0, columnspan=2, pady=50)

        self.score_tally = tk.Label(text="Score: 0", highlightthickness=0, bg=THEME_COLOR, fg="white")
        self.score_tally.grid(row=0, column=1)

        self.chk_img = tk.PhotoImage(file="./images/true.png")
        self.fls_img = tk.PhotoImage(file="./images/false.png")
        self.chk_btn = tk.Button(image=self.chk_img, highlightthickness=0, command=self.check_answer_true)
        self.chk_btn.grid(column=0, row=2, sticky="w", padx=40, pady=20)
        self.fls_btn = tk.Button(image=self.fls_img, highlightthickness=0, command=self.check_answer_false)
        self.fls_btn.grid(column=1, row=2, sticky="w")

        self.get_next_question()

    def get_next_question(self):
        self.quiz_canvas.config(bg="white")
        if self.controller.quiz.still_has_questions():
            self.chk_btn.config(state="normal")
            self.fls_btn.config(state="normal")
            self.score_tally.config(text=f"Score: {self.controller.quiz.score}")
            next_question = self.controller.quiz.next_question()
            self.quiz_canvas.itemconfig(self.question_text, text=next_question)
        else:
            self.quiz_canvas.itemconfig(self.question_text, text="You have completed the quiz")
            self.chk_btn.config(state="disabled")
            self.fls_btn.config(state="disabled")

    def check_answer_true(self):
        self.chk_btn.config(state="disabled")
        self.fls_btn.config(state="disabled")
        self.give_feedback(self.controller.quiz.check_answer('True'))

    def check_answer_false(self):
        self.chk_btn.config(state="disabled")
        self.fls_btn.config(state="disabled")
        self.give_feedback(self.controller.quiz.check_answer('False'))

    def give_feedback(self, answer):
        if answer:
            self.quiz_canvas.config(bg="green")
        else:
            self.quiz_canvas.config(bg="red")
        self.after(1000, self.get_next_question)

app = App()
app.mainloop()


import os
from time import sleep

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument('--incognito')
options.add_argument('--headless')

driver = webdriver.Chrome(
    executable_path='',
    options=options
)
driver.implicitly_wait(10)

top_page_url = 'https://www.mizuhobank.co.jp/retail/takarakuji/numbers/backnumber/index.html'

driver.get(top_page_url)
sleep(3)

latest_links = driver.find_elements_by_css_selector(
    '.js-backnumber-temp-a > td:first-of-type > a')

backnumber_links = driver.find_elements_by_css_selector(
    '.js-backnumber-temp-b > td > a')

urls = [e.get_attribute('href') for e in latest_links+backnumber_links]

print(len(urls))

dir_path = os.path.dirname(os.path.abspath(__file__))
for i, url in enumerate(urls):
    print('='*30, i, '='*30)
    print(url)
    driver.get(url)
    sleep(5)

    html = driver.page_source

    p = os.path.join(dir_path, 'html', f'{driver.title}.html')
    with open(p, 'w') as f:
        f.write(html)

sleep(3)
driver.quit()


import logging
import datetime
import time
import datetime
import matplotlib
import pandas as pd
import sys
matplotlib.use('Agg')
import matplotlib.pylab as plt
import shutil
import tensorflow as tf
import os
import numpy as np

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'

def plot_acc_loss(mhistory, mname, delete_all=False):

    acc = mhistory.history['acc']
    val_acc = mhistory.history['val_acc']
    loss = mhistory.history['loss']
    val_loss = mhistory.history['val_loss']

    plt.figure(figsize=(8, 8))
    plt.subplot(2, 1, 1)
    plt.autoscale(enable=True, axis='y')

    plt.plot(acc, label='Training Accuracy')
    plt.plot(val_acc, label='Validation Accuracy')
    plt.legend(loc='lower right')
    plt.ylabel('Accuracy')
    # plt.ylim([min(plt.ylim()),1])
    plt.title('Training and Validation Accuracy')
    plt.show()
    plt.subplot(2, 1, 2)
    plt.plot(loss, label='Training Loss')
    plt.autoscale(enable=True, axis='y')

    plt.plot(val_loss, label='Validation Loss')
    plt.legend(loc='upper right')
    plt.ylabel('Cross Entropy')
    # plt.ylim([0,15.0])
    plt.title('Training and Validation Loss')
    plt.xlabel('epoch')
    plt.show()
    plt.savefig('graphs/' +'al_'+mname+ '.png')
    if delete_all:
        os.remove('graphs/' +'al_'+mname+ '.png')

def make_sub_dir(name, into):
    path =  into +'/' + name
    if not os.path.exists(path):
        os.makedirs(path)
    return path + '/'

def convert_to_tf(save_file_name, model, delete_pb_model=True, save_converted=True, delete_all=False):
    saved_model_dir = 'pb_for_tf_models/' + save_file_name
    tf.saved_model.save(model, saved_model_dir)
    converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
    tflite_model = converter.convert()
    if save_converted:
        with open('tflite_models/'+ save_file_name+'.tflite', 'wb') as f:
          f.write(tflite_model)
    if delete_pb_model:
        shutil.rmtree('pb_for_tf_models/' + save_file_name)
    if delete_all:
        os.remove('tflite_models/'+ save_file_name+'.tflite')
    del converter
    del tflite_model

def train(dataframe, image_dir, category, name=None, epochs=3, ft_epochs=3, patience=2, ft_patience=2,
          train_categories=False, old_dir=None):

    start_time=time.time()

    if name==None:
        FILE_NAME = datetime.datetime.now().strftime("%d%m%H%M")
    else:
        FILE_NAME=name
    IMAGE_SIZE = 224
    IMG_SHAPE = (IMAGE_SIZE, IMAGE_SIZE, 3)
    EPOCHS=epochs
    FT_EPOCHS=ft_epochs
    STOPPING_CONDITION = 'val_loss'
    STOPPING_PATIENCE = patience
    FT_STOPPING_CONDITION = 'val_loss'
    FT_STOPPING_PATIENCE = ft_patience
    BATCH_SIZE=16
    loss_function = 'categorical_crossentropy'

    datagen = tf.keras.preprocessing.image.ImageDataGenerator(
        rescale=1. / 255,
        validation_split=0.2)

    table = dataframe
    if train_categories:
        column='category'
    else:
        column='product'

    train_generator = datagen.flow_from_dataframe(
        dataframe=table,
        directory=image_dir,
        x_col="file name",
        y_col=column,
        has_ext=True, subset="training",
        class_mode="categorical",
        batch_size=BATCH_SIZE,
        target_size=(IMAGE_SIZE, IMAGE_SIZE))

    val_generator = datagen.flow_from_dataframe(
        dataframe=table,
        directory=image_dir,
        x_col="file name",
        y_col=column,
        class_mode="categorical",
        has_ext=True, subset="validation",
        batch_size=BATCH_SIZE,
        target_size=(IMAGE_SIZE, IMAGE_SIZE))

    for image_batch, label_batch in train_generator:
        image_batch.shape, label_batch.shape
        break
    image_batch.shape, label_batch.shape

    print(train_generator.class_indices)

    classes = train_generator.class_indices.keys()
    labels = '\n'.join(sorted(classes))
    number_of_categories = len(classes)
    misc_path='products_in_categories/'+ category

    with open(make_sub_dir(category, 'misc')+ 'labels.txt', 'w') as f:
        for cls in train_generator.class_indices.keys():
            f.write(cls+'\n')

    optimizer = tf.keras.optimizers.Adam()

    if old_dir==None:
        '''Create the base model from the pre-trained model MobileNet V2'''
        base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
                                                       include_top=False,
                                                       weights='imagenet')
        base_model.trainable = False
        model = tf.keras.Sequential([
            base_model,
            #tf.keras.layers.Conv2D(32, 3, activation='relu'),
            tf.keras.layers.Dropout(0.1),
            tf.keras.layers.GlobalAveragePooling2D(),
            tf.keras.layers.Dense(number_of_categories, activation='softmax')
        ])

    else:
        model = tf.keras.models.load_model(old_dir)
        base_model = model.get_layer('mobilenetv2_1.00_224')
        base_model.trainable = False

    model.compile(optimizer=optimizer,
                  loss=loss_function,
                  metrics=['acc'])

    model.summary()

    print('Number of trainable variables = {}'.format(len(model.trainable_variables)))

    from tensorflow_core.python.keras.callbacks import ModelCheckpoint, EarlyStopping, TensorBoard

    logdir = "logs/tv_test_1/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
    tensorboard_callback = TensorBoard(log_dir=logdir + category, update_freq='batch')

    es = EarlyStopping(monitor=STOPPING_CONDITION, mode='min', verbose=1, patience=STOPPING_PATIENCE)
    bm = ModelCheckpoint(make_sub_dir(category, 'best_models')+'first_phase', monitor='val_acc', verbose=1,
                         save_best_only=True, mode='max')
    history = model.fit(train_generator,
                        epochs=EPOCHS, callbacks=[es, tensorboard_callback],
                        validation_data=val_generator)
    layers_string = ', '.join([layer.name for layer in model.layers])

    one_min_loss= min(history.history['val_loss'])
    one_max_acc=max(history.history['val_acc']),
    one_lass_acc = history.history['val_loss'][-1]
    one_last_loss = history.history['val_acc'][-1],
    learning_rate=str(tf.keras.backend.eval(model.optimizer.lr))

    tf.saved_model.save(model, (make_sub_dir(category, 'saved_bp')))
    #plot_acc_loss(history, name + '_' + category)

    base_model = model.get_layer('mobilenetv2_1.00_224')
    base_model.trainable = True

    # Fine tune from this layer onwards
    fine_tune_at = 100

    '''Freeze all the layers before the `fine_tune_at` layer'''
    for layer in base_model.layers[:fine_tune_at]:
        layer.trainable = False
    opt_learning_rate = 1e-5
    model.compile(loss=loss_function,
                  optimizer=tf.keras.optimizers.Adam(opt_learning_rate),
                  metrics=['acc'])

    model.summary()
    print('Number of trainable variables = {}'.format(len(model.trainable_variables)))
    logdir = "logs/tv_test_2/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")

    tensorboard_callback_ft = TensorBoard(log_dir=logdir + '/' +category, update_freq='batch')

    bm_ft = ModelCheckpoint(make_sub_dir(category, 'best_models'), monitor='val_acc',
                         verbose=1, save_best_only=True, mode='max')

    ft_es = EarlyStopping(monitor=FT_STOPPING_CONDITION, mode='min', verbose=1, patience=FT_STOPPING_PATIENCE)
    history_fine = model.fit(train_generator,
                             epochs=FT_EPOCHS,
                             callbacks=[ft_es, bm_ft, tensorboard_callback_ft],
                             validation_data=val_generator)
    del val_generator
    del train_generator
    del dataframe

    train_time = time.time() - start_time
    plot_acc_loss(history_fine, 'ft_'+ FILE_NAME + '_' + category)

    '''Save optimized model in Keras format for re-training'''
    model.save(make_sub_dir(category, 'keras_models') + FILE_NAME + '.h5')
    two_min_loss = min(history_fine.history['val_loss'])
    two_max_acc = max(history_fine.history['val_acc'])
    two_last_loss = history_fine.history['val_loss'][-1],
    two_last_acc = history_fine.history['val_acc'][-1]
    del history_fine
    tbl_path='tables/'+ category + '.csv'
    if os.path.isfile(tbl_path):
        table = pd.read_csv(tbl_path, index_col=0)
    else:
        if not os.path.exists('tables'):
            os.mkdir('tables')
        table=pd.DataFrame(columns=['name', 'time', 'opt_best_val_loss', 'opt_best_val_acc',
       'opt_last_val_loss', 'opt_last_val_acc', 'best_val_loss',
       'best_val_acc', 'last_val_loss', 'last_val_acc', 'sourcefile', 'loaded',
       'epochs', 'batch_size', 'stopping_cond', 'stop_p', 'optimizer', 'lr',
       'loss_f', 'opt_epochs', 'opt_batch_size', 'opt_stopping_cond',
       'opt_stop_p', 'opt_optimizer', 'opt_lr', 'opt_loss_f', 'layers',
       'training time'])

    table = table.append(pd.Series([FILE_NAME, train_time, two_min_loss, two_max_acc, two_last_loss, two_last_acc,
                                    one_min_loss, one_max_acc, one_lass_acc, one_last_loss,
                                    image_dir, 'new_model', EPOCHS, BATCH_SIZE, STOPPING_CONDITION, STOPPING_PATIENCE,
                                    str(optimizer), 'default_lr', loss_function, FT_EPOCHS, BATCH_SIZE,
                                    FT_STOPPING_CONDITION, FT_STOPPING_PATIENCE, str(optimizer),learning_rate
                                    , loss_function, layers_string,
                                    train_time], index=table.columns ), ignore_index=True)

    table.to_csv(tbl_path)
    del table
    return model

def train_multi(name, dataframe, image_dir, eepochs=12, ft_epochs=12, patience=5, ft_patience=4, old_dir=None):
    from tensorflow.keras.layers import Dense, Activation, Flatten, Dropout, BatchNormalization
    from tensorflow.keras.layers import Conv2D, MaxPooling2D
    from tensorflow.keras import regularizers, optimizers
    import pandas as pd
    import numpy as np
    start_time=time.time()
    IMAGE_SIZE = 224
    IMG_SHAPE = (IMAGE_SIZE, IMAGE_SIZE, 3)
    EPOCHS=eepochs
    FT_EPOCHS=ft_epochs
    STOPPING_CONDITION = 'val_loss'
    STOPPING_PATIENCE = patience
    FT_STOPPING_CONDITION = 'val_loss'
    FT_STOPPING_PATIENCE = ft_patience
    BATCH_SIZE=16
    loss_function = 'categorical_crossentropy'
    table = pd.read_csv('t.csv', dtype=str)
    table_shuffled = table.sample(frac=1)

    test_sample_num = int(len(table) * .2)
    test_sample = table_shuffled.sample(test_sample_num)
    table = table_shuffled.drop(test_sample.index.values)

    table['combined'] = table[['product', 'category']].values.tolist()
    test_sample['combined'] = test_sample[['product', 'category']].values.tolist()
    test_sample.to_csv('sample_shuffled_multi.csv')
    table.to_csv('table_shuffled_multi.csv')
    datagen=tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. / 255.)
    test_datagen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255.)
    train_generator = datagen.flow_from_dataframe(
        dataframe=table[:2300],
        directory=image_dir,
        x_col="file name",
        y_col="combined",
        batch_size=16,
        seed=42,
        shuffle=True,
        class_mode="categorical",
        target_size=(224, 224))
    valid_generator = test_datagen.flow_from_dataframe(
        dataframe=table[2300:],
        directory=image_dir,
        x_col="file name",
        y_col="combined",
        batch_size=16,
        seed=42,
        shuffle=True,
        class_mode="categorical",
        target_size=(224, 224))
    test_generator = test_datagen.flow_from_dataframe(
        dataframe=test_sample,
        directory=image_dir,
        x_col="file name",
        y_col="combined",
        batch_size=1,
        seed=42,
        shuffle=False,
        class_mode=None,
        target_size=(224, 224))
    from tensorflow.keras.layers import Input
    from tensorflow.keras.models import Model
    inp = Input(shape=IMG_SHAPE)
    x = Conv2D(32, (3, 3), padding='same')(inp)
    x = Activation('relu')(x)
    x = Conv2D(32, (3, 3))(x)
    x = Activation('relu')(x)
    x = MaxPooling2D(pool_size=(2, 2))(x)
    x = Dropout(0.20)(x)
    x = Conv2D(64, (3, 3), padding='same')(x)
    x = Activation('relu')(x)
    x = Conv2D(64, (3, 3))(x)
    x = Activation('relu')(x)
    x = MaxPooling2D(pool_size=(2, 2))(x)
    x = Dropout(0.25)(x)
    x = Flatten()(x)
    x = Dense(512)(x)
    x = Activation('relu')(x)
    x = Dropout(0.5)(x)
    output1 = Dense(1, activation='sigmoid')(x)
    output2 = Dense(1, activation='sigmoid')(x)
    model = Model(inp, [output1, output2])
    model.compile(optimizers.RMSprop(lr=0.0001, decay=1e-6),
                  loss=["binary_crossentropy", "binary_crossentropy"], metrics=["accuracy"])

    classes = train_generator.class_indices.keys()
    labels = '\n'.join(sorted(classes))
    number_of_categories = len(classes)
    with open('misc/multi_labels.txt', 'w') as f:
        f.write(labels)

    def generator_wrapper(generator):
        for batch_x, batch_y in generator:
            yield (batch_x, [batch_y[:, i] for i in range(2)])

    STEP_SIZE_TRAIN = train_generator.n // train_generator.batch_size
    STEP_SIZE_VALID = valid_generator.n // valid_generator.batch_size
    STEP_SIZE_TEST = test_generator.n // test_generator.batch_size
    model.fit_generator(generator_wrapper(train_generator), steps_per_epoch=STEP_SIZE_TRAIN,
                        validation_data = generator_wrapper(valid_generator), validation_steps = STEP_SIZE_VALID,
                        epochs = 15, verbose = 2)

    test_generator.reset()
    tf.saved_model.save(model, 'multi_cat2')

"""for product, assumes that only itens of right category are inclided"""
def compare_models(image_dir, test_sample, labels, prod_or_cat, model1, model2=None):
    products=[]
    categories=[]
    for index, row in test_sample.iterrows():
        products.append(row['product'])
        categories.append(row['category'])

    if prod_or_cat=='category':
        sample_labels=categories
    else:
        sample_labels=products

    test_datagen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. / 255.)

    test_generator = test_datagen.flow_from_dataframe(
        dataframe=test_sample,
        directory=image_dir,
        x_col="file name",
        y_col=prod_or_cat,
        batch_size=1,
        seed=42,
        shuffle=False,
        class_mode=None,
        target_size=(224, 224))

    predictions1 = model1.predict_generator(test_generator)
    pred_classes1 = []
    for pred in predictions1:
        pred_classes1.append(np.argmax(pred))
    right1 = 0
    for n in range(len(predictions1)):
        if sample_labels[n] == labels[pred_classes1[n]]:
            right1 += 1
    acc1 = right1 / len(products)

    if model2 is None:
        print('No old model given for comparison')
        acc2 = 100
    else:
        print('Old model given for comparison')
        predictions2 = model2.predict_generator(test_generator)
        pred_classes2 = []
        for pred in predictions2:
            pred_classes2.append(np.argmax(pred))

        right2 = 0
        for n in range(len(predictions2)):
            if sample_labels[n] == labels[pred_classes2[n]]:
                right2 += 1

        acc2 = right2 / len(products)
    return acc1, acc2

def copy_and_overwrite(from_path, to_path):
    if os.path.exists(to_path):
        shutil.rmtree(to_path)
    shutil.copytree(from_path, to_path)

def check_labels_models(thing, models_dir, labels_dir):

    thing_model_dir = models_dir + '/' + thing
    thing_labels_path = labels_dir + '/' + thing + '/labels.txt'
    print(thing)
    if os.path.exists(thing_model_dir):
        print('Old ' + thing + " model exists at " + thing_model_dir)
        model_exists = True
    else:
        print('No old ' + thing + " model exists at " + thing_model_dir)
        model_exists = False
    try:
        with open(thing_labels_path, 'r') as f:
            unique_labels = (f.read()).split('\n')
            unique_labels = list(filter(None, unique_labels))

            labels_loaded = True
        print('Old labels exist for ' + thing + ', at ' + thing_labels_path)
    except:
        print('Old labels don\'t exist for ' + thing + ', at ' + thing_labels_path)
        unique_labels = []
        labels_loaded = False

    return model_exists and labels_loaded, unique_labels


"""
401. Binary Watch
A binary watch has 4 LEDs on the top to represent the hours (0-11), and 6 LEDs on the bottom to represent the minutes (0-59). Each LED represents a zero or one, with the least significant bit on the right.
- For example, the below binary watch reads "4:51".

Given an integer turnedOn which represents the number of LEDs that are currently on (ignoring the PM), return all possible times the watch could represent. You may return the answer in any order.

The hour must not contain a leading zero.
- For example, "01:00" is not valid. It should be "1:00".

The minute must be consist of two digits and may contain a leading zero.
- For example, "10:2" is not valid. It should be "10:02".

Example1:
Input: turnedOn = 1
Output: ["0:01","0:02","0:04","0:08","0:16","0:32","1:00","2:00","4:00","8:00"]

Example2:
Input: turnedOn = 9
Output: []

Constraints:
0 <= turnedOn <= 10
"""

"""
Note:
1. Brute-Force + HashTable: O(1) time | O(1) space
"""
from typing import List
import collections
class Solution:
    def readBinaryWatch(self, turnedOn: int) -> List[str]:
        onHours = collections.defaultdict(list)
        onMinutes = collections.defaultdict(list)

        for minute in range(60):
            on = sum([int(char) for char in bin(minute)[2:]])
            minStr = str(minute) if len(str(minute)) == 2 else "0" + str(minute)
            onMinutes[on].append(minStr)

        for hour in range(12):
            on = sum([int(char) for char in bin(hour)[2:]])
            hourStr = str(hour)
            onHours[on].append(hourStr)

        output = []
        for onForHour in range(min(turnedOn, 3) + 1):
            onForMinute = turnedOn - onForHour
            for h in onHours[onForHour]:
                for m in onMinutes[onForMinute]:
                    output.append(h+":"+m)
        return output

# Unit Tests
import unittest
funcs = [Solution().readBinaryWatch]

class TestReadBinaryWatch(unittest.TestCase):
    def testReadBinaryWatch1(self):
        for func in funcs:
            turnedOn = 1
            self.assertEqual(sorted(func(turnedOn=turnedOn)), sorted(["0:01","0:02","0:04","0:08","0:16","0:32","1:00","2:00","4:00","8:00"]))

    def testReadBinaryWatch2(self):
        for func in funcs:
            turnedOn = 9
            self.assertEqual(sorted(func(turnedOn=turnedOn)), sorted([]))

if __name__ == "__main__":
    unittest.main()