Modularizando tu aplicación: Yapsy

Ca­si to­dos es­ta­mos más o me­nos de acuer­do en que ha­cer que la­s a­pli­ca­cio­nes sean mo­du­la­res es bue­na idea. Una co­sa en la que no­ hay tan­to acuer­do es co­mo miérco­les ha­ce­mos eso.

Una ma­ne­ra (si es­tás pro­gra­man­do en Py­tho­n) es usar Yap­sy..

Yap­sy es asom­bro­so. Tam­bién, ca­re­ce com­ple­ta­men­te de do­cu­men­ta­ció­n en­ten­di­ble. Vea­mos si es­te post arre­gla un po­co esa par­te y de­ja ­só­lo lo asom­bro­so.

Up­da­te: No ha­bía vis­to la do­cu­men­ta­ción nue­va de Yap­s­y. Es mu­cho me­jor que la que ha­bía an­tes :-)

Es­ta es la idea ge­ne­ral con yap­s­y:

  • Creás un Plu­­gin Ma­­na­­ger que pue­­de en­­co­n­­trar y ca­r­­gar plu­­gins de una ­­lis­­ta de lu­­ga­­res (por eje­m­­plo, de [“/us­­r/s­ha­­re/a­­pp­­na­­me/­­plu­­gi­n­s”, ”~/.a­­pp­­na­­me/­­plu­­gi­n­s”]).

  • Una ca­­te­­go­­­ría de plu­­gins es una cla­­se.

  • Hay un ma­­peo en­­tre no­m­­bres de ca­­te­­go­­­ría y cla­­ses de ca­­te­­go­­­ría.

  • Un plu­­gin es un mó­­­du­­lo y un ar­­chi­­vo de me­­ta­­da­­ta. El mó­­­du­­lo de­­fi­­ne u­­na cla­­se que he­­re­­da de una cla­­se de ca­­te­­go­­­ría, y pe­r­­te­­ne­­ce a e­­sa ca­­te­­go­­­ría.

    El ar­­chi­­vo de me­­ta­­da­­ta tie­­ne co­­sas co­­­mo el no­m­­bre del plu­­gi­n, ­­la des­­cri­p­­ció­­n, la UR­­L, ve­r­­sió­­n, etc.

Una de las me­jo­res co­sas de Yap­sy es que no es­pe­ci­fi­ca de­ma­sia­do. Un plu­gin va a ser sim­ple­men­te un ob­je­to Py­tho­n, po­dés po­ner lo que ­quie­ras ahí, o lo po­dés li­mi­tar de­fi­nien­do la inte­faz en la cla­se ­de ca­te­go­ría.

De he­cho, lo que ven­go ha­cien­do con las cla­ses de ca­te­go­ría es:

  • Arran­co con una cla­se va­cía
  • Im­ple­men­to dos plu­gins de esa ca­te­go­ría
  • Los pe­da­zos en co­mún los mue­vo den­tro de la ca­te­go­ría.

Pe­ro créan­me, es­to va a ser mu­cho más cla­ro con un ejem­plo :-)

Lo voy a ha­cer con una apli­ca­ción grá­fi­ca en Py­Q­t, pe­ro Yap­sy fun­cio­na i­gual de bien pa­ra apli­ca­cio­nes “head­le­ss” o pa­ra lí­neas de co­man­do.

Co­men­ce­mos con al­go sim­ple: un edi­tor HT­ML con un wi­dget pre­view.

http://ralsina.me/static/yapsy/editor1.jpeg

Un edi­tor sim­ple con pre­view

Es­te es el có­di­go de la apli­ca­ció­n, que es real­men­te sim­ple (no pue­de guar­da­r ar­chi­vos ni na­da in­te­re­san­te, es só­lo un ejem­plo­):

edi­to­r1.­py

from PyQt4 import QtCore, QtGui, QtWebKit
import os, sys

class Main(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.layout = QtGui.QVBoxLayout()
        self.editor = QtGui.QPlainTextEdit()
        self.preview = QtWebKit.QWebView()
        self.layout.addWidget(self.editor)
        self.layout.addWidget(self.preview)
        self.editor.textChanged.connect(self.updatePreview)
        self.setLayout(self.layout)

    def updatePreview(self):
        self.preview.setHtml(self.editor.toPlainText())

def main():
    # Again, this is boilerplate, it's going to be the same on
    # almost every app you write
    app = QtGui.QApplication(sys.argv)
    window=Main()
    window.show()
    # It's exec_ because exec is a reserved word in Python
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

No­ta

De aho­ra en más los lis­ta­dos no in­clu­yen la fun­ción main por­que no cam­bia.

Pe­ro es­ta apli­ca­ción tie­ne un ob­vio lí­mi­te: hay que es­cri­bir HT­M­L! Por qué no­ es­cri­bir py­thon y que lo mues­tre re­sal­ta­do en HT­M­L? O ma­rkup de Wiki! O reS­truc­tu­re­d ­tex­t!

Uno po­dría, en prin­ci­pio, im­ple­men­tar to­dos esos mo­do­s, pe­ro es­tás asu­mien­do­ ­la res­pon­sa­bi­li­dad de so­por­tar ca­da co­sa-­que-se-­con­vier­te-en-HT­M­L. Tu apli­ca­ció­n ­se­ría un mo­n­oli­to. Ahí en­tra Yap­s­y.

Cree­mos en­ton­ces una ca­te­go­ría de plu­gin­s, lla­ma­da “For­ma­tte­r” que to­ma tex­to pla­no­ y de­vuel­ve HT­M­L. Des­pués agre­gue­mos co­sas en la UI pa­ra que el usua­rio pue­da ele­gi­r ­que for­ma­tter usar, e im­ple­men­te­mos un pa­r.

Es­ta es la cla­se de ca­te­go­ría de plu­gin­s:

ca­te­go­rie­s.­py

class Formatter(object):
    """Plugins of this class convert plain text to HTML"""

    name = "No Format"

    def formatText(self, text):
        """Takes plain text, returns HTML"""
        return text

Por su­pues­to que no sir­ve de na­da sin plu­gin­s! Asi que cree­mos un pa­r.

Pri­me­ro, un plu­gin qye to­ma có­di­go py­thon y de­vuel­ve HT­M­L, usan­do pyg­men­ts.

plu­gin­s/­p­yg­men­ti­ze.­py

from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter

from categories import Formatter

class Pygmentizer(Formatter):
    name = "Python Code"

    def formatText(self, text):
        return highlight(text, PythonLexer(), HtmlFormatter(full=True))

Co­mo ven, eso va en una car­pe­ta plu­gin­s. Des­pués le de­ci­mos a Yap­sy que ­bus­que los plu­gins ahi aden­tro.

Pa­ra ser re­co­no­ci­do co­mo un plu­gi­n, ne­ce­si­ta me­ta­da­ta:

plu­gin­s/­p­yg­men­ti­ze.­yap­s­y-­plu­gin

[Core]
Name = Python Code
Module = pygmentize

[Documentation]
Author = Roberto Alsina
Version = 0.1
Website = http://ralsina.me
Description = Highlights Python Code

Y real­men­te, eso es to­do lo que hay que ha­cer pa­ra ha­cer un plu­gi­n. Acá hay otro pa­ra com­pa­ra­r, que usa do­cu­tils pa­ra for­ma­tear reS­truc­tu­red Tex­t:

plu­gin­s/­res­t.­py

from categories import Formatter
import docutils.core
import docutils.io


class Rest(Formatter):
    name = "Restructured Text"

    def formatText(self, text):
        output = docutils.core.publish_string(
            text, writer_name = 'html'
        )
        return output

plu­gin­s/­res­t.­yap­s­y-­plu­gin

[Core]
Name = Restructured Text
Module = rest

[Documentation]
Author = Roberto Alsina
Version = 0.1
Website = http://ralsina.me
Description = Formats restructured text

Y acá es­tán en ac­ció­n:

http://ralsina.me/static/yapsy/editor2.jpeg

reSt mo­de

http://ralsina.me/static/yapsy/editor3.jpeg

Py­thon mo­de

Of cour­se using ca­te­go­ries you can do things like a “Tool­s” ca­te­go­r­y, whe­re the ­plu­gins get added to a Tools me­nu, too.

Es­te es el có­di­go del la­do de la apli­ca­ció­n:

edi­to­r2.­py

from categories import Formatter
from yapsy.PluginManager import PluginManager

class Main(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.layout = QtGui.QVBoxLayout()
        self.formattersCombo = QtGui.QComboBox()
        self.editor = QtGui.QPlainTextEdit()
        self.preview = QtWebKit.QWebView()

        self.layout.addWidget(self.formattersCombo)
        self.layout.addWidget(self.editor)
        self.layout.addWidget(self.preview)

        self.editor.textChanged.connect(self.updatePreview)
        self.setLayout(self.layout)

        # Create plugin manager
        self.manager = PluginManager(categories_filter={ "Formatters": Formatter})
        self.manager.setPluginPlaces(["plugins"])

        # Load plugins
        self.manager.locatePlugins()
        self.manager.loadPlugins()

        # A do-nothing formatter by default
        self.formattersCombo.addItem("None")
        self.formatters = {}
        print self.manager.getPluginsOfCategory("Formatters")
        for plugin in self.manager.getPluginsOfCategory("Formatters"):
            print  "XXX"
            # plugin.plugin_object is an instance of the plugin
            self.formattersCombo.addItem(plugin.plugin_object.name)
            self.formatters[plugin.plugin_object.name] = plugin.plugin_object

    def updatePreview(self):
        # Check what the current formatter is
        name =  unicode(self.formattersCombo.currentText())
        text = unicode(self.editor.toPlainText())
        if name in self.formatters:
            text = self.formatters[name].formatText(text)
        self.preview.setHtml(text)

Re­su­mien­do: es fá­ci­l, y te lle­va a me­jo­rar la es­truc­tu­ra in­ter­na de tu apli­ca­ció­n y ter­mi­nás con me­jor có­di­go.

Có­di­go fuen­te de to­do.

Comentarios

Comments powered by Disqus