Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre python (publicaciones antiguas, página 60)

Capítulo adelanto de "Grok 1.0 Web Development"

Lo es­toy le­yen­do, y voy a es­cri­bir al­go en unos días (De pa­so: es­tá bue­no! Pro­pon­go es­te slo­gan pa­ra Gro­k: Co­mo Zo­pe, pe­ro sin el Ja­va [1]), y acá hay un ca­pí­tu­lo de mues­tra pa­ra que vean de qué se tra­ta. Es fá­cil de leer sin el res­to del li­bro:

Chap­ter 5: For­ms

Es­ta se­ma­na es­tu­ve pe­lean­do con ese en­gen­dro que eran los Djan­go ol­dfor­ms (si­tio vie­jo, ni pre­gun­ten), así que los for­ms de Grok me pa­re­cie­ron en­can­ta­do­res! ;-)

[1] Sí, por su­pues­to que Zo­pe no tie­ne Ja­va, só­lo se sien­te co­mo si fue­ra Ja­va ;-)

¿Tenemos tan poca idea de lo que cuesta el software?

Real­men­te, Ma­ra­ve es co­mo mu­cho un mes de pro­gra­mar par­t-­ti­me. Có­mo pue­de eso hu­ma­na­men­te ser U$S71355 o "1 año per­so­na"?

¿Es­ta ba­su­ra es lo me­jor que te­ne­mos pa­ra es­ti­mar cos­to­s? Si es así, la pr­óxi­ma vez que vean al­go co­mo "El pro­gra­ma X lle­va­ría Y años y cos­ta­ría Z dó­la­res", re­cór­ten­lo por un fac­tor de 10 o má­s.

És­to es lo que cos­tó de­sa­rro­llar Ma­ra­ve:

  • Na­­da

Bue­no, és­to es lo que hu­bie­ra cos­ta­do si lo co­bra­ra:

Es­toy su­po­nien­do unas 100 ho­ras de mi tiem­po. A mi pre­cio de "o­ja­lá me lo pa­guen", U$S 40/ho­ra, son U$S 4000, que quie­re de­cir que Oh­loh le pi­fia por un 1600%.

Por otro la­do, por tan­to tra­ba­jo free­lan­ce no te voy a co­brar pre­cio com­ple­to, lo más se­gu­ro es que te co­bra al­go co­mo U$S 20/ho­ra lo que quie­re de­cir que Oh­loh es­tá un 3000% arri­ba.

En con­clu­sió­n: si te gus­ta mi có­di­go (y lo po­dés ver por vos mis­mo), con­tra­ta­me, soy o in­creí­ble­men­te ba­ra­to o in­creí­ble­men­te rá­pi­do.

Marave 0.6 en la calle

Co­sas nue­va­s:

  • Re­s­al­­ta­­do de si­n­­ta­­xis

  • Plu­­gins

  • Bugs arre­­gla­­dos

  • Ani­­ma­­cio­­­nes más bo­­­ni­­tas

  • Li­m­­pie­­za de có­­­di­­go

Cap­tu­ra de pan­ta­lla gra­tui­ta:

El momento ajá!

  1. Ha­­cer un "fa­­de in" de un wi­­dget

  2. Se­­tear una va­­ria­­ble

  3. Ha­­cer un "fa­­de in" de otro wi­­dget

Es im­por­tan­te ha­cer­lo en ese or­den y es im­por­tan­te que la apli­ca­ción si­ga res­pon­dien­do.

Acá es­tá el có­di­go que usé (sim­pli­fi­ca­do­):

def fadein(thing, target=1., thendo=None):
    """
    * thing is a QWidget
    * thing.proxy is a QGraphicsWidget
    * thendo is callable
    * target is the desired opacity
    """

    thing.anim=QtCore.QPropertyAnimation(thing.proxy, "opacity")
    thing.anim.setDuration(200)
    thing.anim.setStartValue(thing.proxy.opacity())
    thing.anim.setEndValue(target)
    thing.anim.start()
    thing.anim.finished.connect(thing.anim.deleteLater)
    if thendo:
        thing.anim.finished.connect(thendo)

Y se usa así:

def later():
    avar=avalue
    fadein(widget2)

fadein(widget1, thendo=later)

¿No es lindo? Tener funciones como objetos de primera clase significa que puedo tomar later como un closure, junto con widget2 y avar que sólo necesitan estar definidas en el scope local, y la cadena de llamadas funciona ¡exactamente como quiero!

Sí, en mu­chos otros len­gua­jes se ha­ce lo mis­mo, y en Ja­vas­cript es un tru­co co­mú­n... ¡pe­ro Py­Qt es un wra­pper de C++!

Me pa­re­ce que es­te ti­po de uso mues­tra el va­lor agre­ga­do que Py­Qt te da, no es so­la­men­te que con py­thon evi­tás la com­pi­la­ción abu­rri­da, o que te­nés la in­creí­ble bi­blio­te­ca es­tán­da­r, sino que el len­gua­je mis­mo te de­ja ha­cer co­sas que no son prác­ti­cas en C++.

La única manera que se me ocurre de hacer esto en C++ es crear un slot que sea el equivalente de later, y encadenarlo a la señal... lo que quiere decir que ese later descartable se convierte en parte de la interface de la clase. (!?)

Habría que definir later en algún otro lado del archivo, separado de su único uso (tal vez inine en el header).

Aún así, eso no es equivalente: avalue podría ser algo no fácil de acceder cuando se ejecuta later (por ejemplo, el timestamp del primer fadein), habría que buscar donde guardarlo para que later lo encuentre, no se puede volver a hacer esto hasta después que se ejecute later... se pone complicado.

A ve­ces pro­gra­mar es co­mo una ca­che­ta­da... te das cuen­ta que co­sas que usás sin pen­sar no son na­da tri­via­le­s.

Así que re­cuer­da jo­ven apren­di­z: po­dés ele­gir las he­rra­mien­ta­s. Ele­gí con cui­da­do.

Extendiendo Marave

En­ton­ces la so­lu­ció­n, en la an­ti­gua tra­di­ción de Ema­cs y Vim es... ha­cer­lo ex­ten­si­ble.

Soy un gran fan de los pro­gra­mas ex­ten­si­bles por el usua­rio.

Así que... acá es­tá la ana­to­mía de un plu­gin de Ma­ra­ve tal co­mo fun­cio­na aho­ra mis­mo en SVN trunk, lo que por su­pues­to pue­de cam­biar en cual­quier mo­men­to.

Creando un plugin

Só­lo hay que crear un ar­chi­vo .py en la car­pe­ta plu­gin­s. És­te es el plu­gin más bá­si­co, que no ha­ce na­da:

# -*- coding: utf-8 -*-

from plugins import Plugin
class Smarty(Plugin):
    name='smarty'
    shortcut='Ctrl+.'
    description='Smart quote and dash replacement'
    mode="qBde"

Va­lo­res por de­fault de al­go con­fi­gu­ra­ble (en es­te ca­so "mo­de") se po­nen en la cla­se.

Los cam­pos obli­ga­to­rio­s:

  • sho­r­­tcu­­t: un ata­­jo de te­­cla­­do que dis­­pa­­ra es­­te plu­­gin

  • na­­me: un no­m­­bre co­r­­to

  • des­­cri­p­­tio­­n: una des­­cri­p­­ción de una lí­­nea

¿Qué ha­ce es­to? Agre­ga el plu­gin a la lis­ta en el diá­lo­go de pre­fe­ren­cia­s, y se pue­de abrir el diá­lo­go de con­fi­gu­ra­ción del plu­gi­n, don­de se pue­de cam­biar el shor­tcu­t:

maraveplugin1

Si se ha­bi­li­ta es­te plu­gi­n, cuan­do el usua­rio use ese shor­tcu­t, se lla­ma al mé­to­do "run" del plu­gi­n.

Haciéndolo Configurable

És­te plu­gin so­por­ta dis­tin­tos mo­dos de ope­ra­ció­n. Pa­ra ha­cer que es­to sea ac­ce­si­ble al usua­rio, hay que im­ple­men­tar unos po­cos mé­to­dos ma­s.

El mé­to­do addCon­fi­gWi­dge­ts to­ma co­mo ar­gu­men­to un diá­lo­go, y agre­ga lo que uno quie­ra ahí:

@classmethod
def addConfigWidgets(self, dialog):
    print 'Adding widgets to smarty config'
    l=dialog.ui.layout
    self.q=QtGui.QCheckBox(dialog.tr('Replace normal quotes'))
    if 'q' in self.mode:
        self.q.setChecked(True)
    self.b=QtGui.QCheckBox(dialog.tr('Replace backtick-style quotes (` and ``)'))
    if 'B' in self.mode:
        self.b.setChecked(True)
    self.d=QtGui.QCheckBox(dialog.tr('Replace -- by en-dash, --- by em-dash'))
    if 'd' in self.mode:
        self.d.setChecked(True)
    self.e=QtGui.QCheckBox(dialog.tr('Replace ellipses'))
    if 'e' in self.mode:
        self.e.setChecked(True)
    l.addWidget(self.q)
    l.addWidget(self.b)
    l.addWidget(self.d)
    l.addWidget(self.e)

Y en­ton­ces el diá­lo­go de con­fi­gu­ra­ción se ve así:

maraveplugin2

Tam­bién que­re­mos que esas op­cio­nes se pue­dan guar­dar en al­gún la­do, en­ton­ces reim­ple­men­ta­mos save­Con­fi­g:

@classmethod
def saveConfig(self, dialog):

    self.shortcut=unicode(dialog.ui.shortcut.text())
    self.settings.setValue('plugin-'+self.name+'-shortcut', self.shortcut)

    newmode=""
    if self.q.isChecked():
        newmode+='q'
    if self.b.isChecked():
        newmode+='B'
    if self.d.isChecked():
        newmode+='d'
    if self.e.isChecked():
        newmode+='e'
    self.mode=newmode

    self.settings.setValue('plugin-smarty-mode',self.mode)
    self.settings.sync()

Y que­re­mos que esas op­cio­nes se lean an­tes de crear el plu­gi­n, en­ton­ce­s:

@classmethod
def loadConfig(self):
    print 'SMARTY loadconfig', self.settings
    if self.settings:
        sc=self.settings.value('plugin-'+self.name+'-shortcut')
        if sc.isValid():
            self.shortcut=unicode(sc.toString())
        mode=self.settings.value('plugin-smarty-mode')
        if mode.isValid():
            self.mode=unicode(mode.toString())

Que haga algo!

Y sí, hay que ha­cer que sir­va pa­ra al­go. El plu­gin tie­ne ac­ce­so a un "clien­t" que es la ven­ta­na prin­ci­pal de Ma­ra­ve. To­do es­tá ahí, en al­gu­na par­te ;-)

def run(self):
    print 'running smarty plugin'
    text=unicode(self.client.editor.toPlainText()).splitlines()
    prog=QtGui.QProgressDialog(self.client.tr("Applying smarty"),
                               self.client.tr("Cancel"),
                               0,len(text),
                               self.client)
    prog.show()
    output=[]
    for i,l in enumerate(text):
        output.append(unescape(smartyPants(l,self.mode)))
        prog.setValue(i)
        QtGui.QApplication.instance().processEvents()
    prog.hide()
    self.client.editor.setPlainText('\n'.join(output))

Y ya es­tá, si se ha­bi­li­ta el plu­gin smar­ty, se pue­den "a­rre­gla­r" las co­mi­lla­s, guio­nes y elip­sis con una com­bi­na­ción de te­clas :-)

Có­di­go fuen­te com­ple­to aquí: http://­co­de.­google.­co­m/­p/­ma­ra­ve/­sour­ce/­bro­wse/­trunk/­ma­ra­ve/­plu­gin­s/s­mar­ty.­py

Fal­ta ha­ce­r: otras ma­ne­ras de in­te­grar plu­gins en la in­ter­fa­ce, bo­to­nes, pa­ne­le­s, etc.


Contents © 2000-2020 Roberto Alsina