Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre nerdness

Creando un foro de la manera fácil (32 líneas)

Aquí es­tán al­gu­nos de los fea­tu­res que quie­ro:

  • Lo­­­gin via twi­­tter / Fa­­ce­­book / Google / Ope­­nID

  • Nú­­me­­ro ili­­mi­­ta­­do de th­­rea­­ds

  • So­­­po­r­­te de like / dis­­like en th­­rea­­ds y en po­s­­ts

  • Ava­­ta­­res

  • HT­­ML en los po­s­­ts

  • Que man­­de mail al usua­­rio si le res­­po­n­­den

  • Fee­­ds RSS pa­­ra los th­­rea­­ds

Se lo pue­de ver en ac­ción en http://­fo­ro­.­ne­t­ma­na­ger­s.­co­m.ar (por un tiem­po li­mi­ta­do ;-)

Y aquí es­tá el có­di­go:

import bottle
import disqusapi as disqus
import json
shortname = 'magicmisteryforum'
api = disqus.DisqusAPI(open("key").read().strip())

@bottle.route('/', method='GET')
def index():
    msg = bottle.request.GET.get('msg', '')
    threads = api.forums.listThreads(forum=shortname, limit=100)
    print threads[0]
    return bottle.template('main.tpl', threads=threads, shortname=shortname, msg=msg)

@bottle.route('/new', method='POST')
def new():
    title = bottle.request.forms.get('title', None)
    if not title:
        bottle.redirect('/?msg=Missing%20Thread%20Name')
        return
    thread = api.threads.create(forum=shortname, title = title)
    thread_id = thread.__dict__['response']['id']
    # Redirecting to /thread/thread_id doesn't work
    # because threads take a few seconds to appear on the listing
    bottle.redirect('/')

@bottle.route('/thread/:id')
def thread(id):
    t = api.threads.details(thread=id)
    return bottle.template('thread.tpl', shortname=shortname, id=id, thread=t.__dict__['response'])

@bottle.route('/static/:path#.+#')
def server_static(path):
    return bottle.static_file(path, root='./static')

app = bottle.app()
app.catchall = False #Now most exceptions are re-raised within bottle.
bottle.run(host='184.82.108.14', port=80, app=app)

Re­quie­re Bo­ttle y la Dis­qus py­thon API

Por su­pues­to que hay un po­qui­to de tem­pla­tes, acá es­tá mai­n.­tpl y th­rea­d.­tpl. Co­mo apes­to pa­ra el HT­M­L, usa Blue­trip CSS y es sen­ci­llo de cus­to­mi­za­r.

POR SU­PUES­TO QUE HA­GO TRAM­PA!

Es­ta co­sa es ape­nas una ca­pa de pin­tu­ra en­ci­ma de Dis­qus! Más un blog sin pos­ts pe­ro con co­men­ta­rios que un fo­ro! Pe­ro­... qué le fal­ta pa­ra ser un fo­ro de ver­da­d? Fun­cio­na, no? Has­ta se po­drían usar ca­te­go­rías de Dis­qus pa­ra crear su­bfo­ro­s...

Te­nien­do to­do en cuen­ta, creo que es un ha­ck bo­ni­to.

Y si es­pe­rás unos día­s, es­to lle­va a otra co­sa que es mu­cho más má­gi­ca...

Có­di­go fuen­te com­ple­to en http://­ma­gi­cfo­ru­m.­google­co­de.­com

De Vicenzo: un mini browser más copado

Si no que­rés leer eso de nue­vo, la idea es ver cuán­to có­di­go fal­ta pa­ra con­ver­tir el mo­tor We­bKit de Qt en un bro­w­ser "en se­rio­".

Pa­ra ello, me pu­se una me­ta com­ple­ta­men­te ar­bi­tra­ria de 128 lí­neas de có­di­go. En es­te mo­men­to lo de­cla­ro fea­tu­re-­com­ple­te (pe­ro bu­gg­y).

Los nue­vos fea­tu­res so­n:

  • Ta­­bbed bro­­w­­sing (se pue­­de agre­­ga­­r/s­a­­car ta­bs)

  • Book­­ma­­rks (se pue­­den agre­­ga­­r/s­a­­car y ele­­gir de una lis­­ta)

Es­to es lo que ya fun­cio­na­ba:

  • Zoom in (C­­tr­­l++)

  • Zoom out (C­­tr­­l+-)

  • Re­set Zoom (C­­tr­­l+=)

  • Bus­­car (C­­tr­­l+­­F)

  • Es­­co­n­­der bús­­que­­da (Es­­c)

  • Bo­­­to­­­nes de atrá­s/a­­de­­lan­­te y re­­ca­r­­gar

  • En­­tra­­da de URL que coi­n­­ci­­de con la pá­­gi­­na + au­­to­­­co­m­­ple­­ta­­do des­­de la his­­to­­­ria + arre­­gla la URL pues­­ta a ma­no (a­­gre­­ga http://, esas co­­sas)

  • Plu­­gins (i­n­­cluí­­do fla­s­h, que hay que ba­­jar apa­r­­te ;-)

  • El tí­­tu­­lo de la ven­­ta­­na mues­­tra el tí­­tu­­lo de la pá­­gi­­na (sin pro­­­pa­­gan­­da del bro­­w­se­­r)

  • Ba­­rra de pro­­­gre­­so pa­­ra la ca­r­­ga de la pá­­gi­­na

  • Ba­­rra de es­­ta­­do que mues­­tra el des­­tino de los li­nks cuan­­do pa­sas el mou­­se

  • To­­­ma una URL en la lí­­nea de co­­­man­­do (o abre http://­­p­­y­­tho­­n.org

  • Mu­l­­ti­­pla­­ta­­fo­r­­ma (fun­­cio­­­na do­n­­de fun­­cio­­­na QtWe­­bKi­­t)

Y cuan­to có­di­go es eso? 87 LI­NEAS.

O si pre­fe­rís la ver­sión que cum­ple con la PE­P8: 115 LI­NEAS.

Me ata­jo an­tes que al­guien lo di­ga: sí, el mo­tor de ren­de­ring y el toolkit son enor­mes. Lo que es­cri­bí es el "ch­ro­me" al­re­de­dor de eso, igual que ha­cen Aro­ra, Rekon­q, Ga­leo­n, Epi­phan­y, y mu­chos otros bro­w­ser­s.

Es un ch­ro­me sim­ple y mi­ni­ma­lis­ta, pe­ro fun­cio­na bas­tan­te bien, creo yo.

Aquí es­tá el de­mo (bu­gg­y):

Mas o me­nos ha­ce lo que es­pe­ra­ba que se pue­die­ra lo­gra­r, pe­ro le fal­tan arre­glo­s.

Pa­ra ver el có­di­go, va­yan a su ho­me pa­ge: http://­de­vi­cen­zo­.­google­co­de.­com

¿Cuanto browser entra en 128 líneas de código?

Lo que me gus­ta­ba de ese bro­w­ser de 42 lí­neas era que no era el ejem­plo tí­pi­co, don­de me­ten una vis­ta de We­bkit en una ven­ta­na, car­gan una pá­gi­na y te tra­tan de con­ven­cer de que son unos ba­na­na­s. Esa ver­sión son 7 lí­nea­s:

import sys
from PyQt4 import QtGui,QtCore,QtWebKit
app=QtGui.QApplication(sys.argv)
wb=QtWebKit.QWebView()
wb.setUrl(QtCore.QUrl('http://www.python.org'))
wb.show()
sys.exit(app.exec_())

O 6 si lo so­por­ta­ra un po­co más feo.

¡Pe­ro igua­l, el de 42 se veía úti­l!

This 42-line web browser, courtesy of #python and #qt -- http... on Twitpic

Esos bo­to­nes que se ven fun­cio­na­ban co­rrec­ta­men­te, ha­bi­li­tan­do y des­ha­bi­li­tan­do­se en el mo­men­to co­rrec­to, la en­tra­da de URL cam­bia­ba cuan­do ha­cías cli­ck en un li­nk, y otras co­si­tas así.

Ahí de­ci­dí em­pe­zar un pe­que­ño pro­yec­to in­ter­mi­ten­te de co­de gol­f: me­ter el me­jor bro­w­ser que pue­da en 128 lí­neas de có­di­go (sin con­tar co­men­ta­rios ni blan­co­s), usan­do so­lo Py­Q­t4.

Eso tie­ne un pro­pó­si­to úti­l: siem­pre sos­pe­ché que si uno asu­me Py­Qt co­mo par­te del sis­te­ma ba­se, la ma­yo­ría de las apli­ca­cio­nes en­tra­rían en diske­ttes. Es­ta en­tra unas 500 ve­ces en uno de 1.44MB (¡a­sí que po­dés usar los de 360 de co­m­mo­do­re sin du­pli­disk!)

has­ta aho­ra van 50 lí­nea­s, y tie­ne los si­guien­tes fea­tu­res:

  • Zoom in (C­­tr­­l++)

  • Zoom out (C­­tr­­l+-)

  • Re­set Zoom (C­­tr­­l+=)

  • Bus­­car (C­­tr­­l+­­F)

  • Es­­co­n­­der bús­­que­­da (Es­­c)

  • Bo­­­to­­­nes de atrá­s/a­­de­­lan­­te y re­­ca­r­­gar

  • En­­tra­­da de URL que coi­n­­ci­­de con la pá­­gi­­na + au­­to­­­co­m­­ple­­ta­­do des­­de la his­­to­­­ria + arre­­gla la URL pues­­ta a ma­no (a­­gre­­ga http://, esas co­­sas)

  • Plu­­gins (i­n­­cluí­­do fla­s­h, que hay que ba­­jar apa­r­­te ;-)

  • El tí­­tu­­lo de la ven­­ta­­na mues­­tra el tí­­tu­­lo de la pá­­gi­­na (sin pro­­­pa­­gan­­da del bro­­w­se­­r)

  • Ba­­rra de pro­­­gre­­so pa­­ra la ca­r­­ga de la pá­­gi­­na

  • Ba­­rra de es­­ta­­do que mues­­tra el des­­tino de los li­nks cuan­­do pa­sas el mou­­se

  • To­­­ma una URL en la lí­­nea de co­­­man­­do (o abre http://­­p­­y­­tho­­n.org

  • Mu­l­­ti­­pla­­ta­­fo­r­­ma (fun­­cio­­­na do­n­­de fun­­cio­­­na QtWe­­bKi­­t)

Fal­tan ta­bs y so­por­te de pro­x­y. Es­pe­ro que lle­ven unas 40 lí­neas má­s, pe­ro creo que ya es el más ca­paz de to­dos es­tos bro­w­sers de ejem­plo.

El có­di­go­... no es tan te­rri­ble. Uso mu­chos lamb­da­s, y los ar­gu­men­tos ke­yword de Py­Qt pa­ra co­nec­tar se­ña­le­s, que ha­cen que al­gu­nas lí­neas sean muy lar­ga­s, pe­ro no muy di­fí­ci­le­s. Se po­dría achi­car bas­tan­te to­da­vía!

Aquí es­tá en ac­ció­n:

Y aquí es­tá el có­di­go:

#!/usr/bin/env python
"A web browser that will never exceed 128 lines of code. (not counting blanks)"

import sys
from PyQt4 import QtGui,QtCore,QtWebKit

class MainWindow(QtGui.QMainWindow):
    def __init__(self, url):
        QtGui.QMainWindow.__init__(self)
        self.sb=self.statusBar()

        self.pbar = QtGui.QProgressBar()
        self.pbar.setMaximumWidth(120)
        self.wb=QtWebKit.QWebView(loadProgress = self.pbar.setValue, loadFinished = self.pbar.hide, loadStarted = self.pbar.show, titleChanged = self.setWindowTitle)
        self.setCentralWidget(self.wb)

        self.tb=self.addToolBar("Main Toolbar")
        for a in (QtWebKit.QWebPage.Back, QtWebKit.QWebPage.Forward, QtWebKit.QWebPage.Reload):
            self.tb.addAction(self.wb.pageAction(a))

        self.url = QtGui.QLineEdit(returnPressed = lambda:self.wb.setUrl(QtCore.QUrl.fromUserInput(self.url.text())))
        self.tb.addWidget(self.url)

        self.wb.urlChanged.connect(lambda u: self.url.setText(u.toString()))
        self.wb.urlChanged.connect(lambda: self.url.setCompleter(QtGui.QCompleter(QtCore.QStringList([QtCore.QString(i.url().toString()) for i in self.wb.history().items()]), caseSensitivity = QtCore.Qt.CaseInsensitive)))

        self.wb.statusBarMessage.connect(self.sb.showMessage)
        self.wb.page().linkHovered.connect(lambda l: self.sb.showMessage(l, 3000))

        self.search = QtGui.QLineEdit(returnPressed = lambda: self.wb.findText(self.search.text()))
        self.search.hide()
        self.showSearch = QtGui.QShortcut("Ctrl+F", self, activated = lambda: (self.search.show() , self.search.setFocus()))
        self.hideSearch = QtGui.QShortcut("Esc", self, activated = lambda: (self.search.hide(), self.wb.setFocus()))

        self.quit = QtGui.QShortcut("Ctrl+Q", self, activated = self.close)
        self.zoomIn = QtGui.QShortcut("Ctrl++", self, activated = lambda: self.wb.setZoomFactor(self.wb.zoomFactor()+.2))
        self.zoomOut = QtGui.QShortcut("Ctrl+-", self, activated = lambda: self.wb.setZoomFactor(self.wb.zoomFactor()-.2))
        self.zoomOne = QtGui.QShortcut("Ctrl+=", self, activated = lambda: self.wb.setZoomFactor(1))
        self.wb.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, True)

        self.sb.addPermanentWidget(self.search)
        self.sb.addPermanentWidget(self.pbar)
        self.wb.load(url)


if __name__ == "__main__":
    app=QtGui.QApplication(sys.argv)
    if len(sys.argv) > 1:
        url = QtCore.QUrl.fromUserInput(sys.argv[1])
    else:
        url = QtCore.QUrl('http://www.python.org')
    wb=MainWindow(url)
    wb.show()
    sys.exit(app.exec_())

The Outhouse and the Mall

Wear­ing the soft­ware en­gi­neer's hat: Code is the most triv­ial and the least im­por­tant part of a fea­ture.

—Michael Ia­trou

Michael tweet­ed that, I replied, he replied, but what the heck, I think some­times things can be bet­ter ex­plained in more than 140 char­ac­ter­s, thus this post [1].

So, why the mall and the out­house? Be­cause when we talk about soft­ware and code and fea­tures, we are not all talk­ing about the same thing. Imag­ine if I told you that bricks are triv­ial. Af­ter al­l, they have ex­ist­ed in their cur­rent form for thou­sands of years, they are pret­ty sim­ple to man­u­fac­ture, and have no in­ter­est­ing fea­tures, re­al­ly, ex­cept a cer­tain re­sistence.

Now, sup­pose you are build­ing an out­house. Since you are a fun­ny guy, you want to build an ac­tu­al brick out­house so you will use bricks to do it.

Now, since bricks are so bor­ing, you may feel com­pelled to be­lieve bricks are the least im­por­tant part of your ed­i­fice, and that the over­all de­sign is more im­por­tan­t. Should you carve a moon-shaped hole in the door? How deep should the la­trine be?

How­ev­er, that po­si­tion is fa­tal­ly flawed, since if you ig­nore those triv­ial, bor­ing brick­s, all you have is shit in a hole in the ground. That is be­cause you are con­sid­er­ing the bricks as just a mean to your end. You on­ly care about the bricks in­so­far as they help you re­al­ize your grand out­house vi­sion. I am here to tell you that you are wrong.

The first way in which you are wrong is in that ar­ti­fi­cial sep­a­ra­tion be­tween means and end­s. Ev­ery­one is fa­mil­iar with the eth­i­cal co­nun­drum about whether the ends jus­ti­fy the mean­s, but that's garbage. That'swhat you say when you try to con­vince your­self that do­ing things hap­haz­ard­ly is ok, be­cause what you do is just the means to what­ev­er oth­er thing is the end. Life is not so eas­i­ly di­vid­ed in­to things that mat­ter and things that don't.

Your work, your cre­ation is not just some ide­al iso­lat­ed end to­wards which you trav­el across a sea of dirty mean­s, try­ing to keep your sil­ver ar­mour clean. It's one whole thing. You are cre­at­ing the mean­s, you are cre­at­ing your goal, you are re­spon­si­ble for both, and if you use shod­dy brick­s, your out­house should shame you.

In the same way, if you do crap­py code, your fea­ture is de­meaned. It may even work, but you will know it's built out of crap. You will know you will have to fix and main­tain that crap for years to come, or, if you are luck­y, ru­in your kar­ma by dump­ing it on the head of some poor suck­er who fol­lows your step­s.

I am pret­ty much a ma­te­ri­al­ist. If you re­move the code, you don't have a fea­ture, or soft­ware, you have a con­cep­t, maybe an idea, per­haps a de­sign (or maybe not) but cer­tain­ly not soft­ware, just like you don't have a brick out­house with­out pil­ing some damn bricks one on top of the oth­er.

I al­ways say, when I see some­one call­ing him­self a soft­ware en­gi­neer, that I am mere­ly a soft­ware car­pen­ter. I know my tool­s, I care about them, I use them as well as I can ac­cord­ing to my lights [2] and I try to pro­duce as good a piece of fur­ni­ture as I can with what I am giv­en.

This tends to pro­duce hum­ble soft­ware, but it's soft­ware that has one re­deem­ing fea­ture: it knows what it should do, and does it as well as I can make it. For ex­am­ple, I wrote rst2pdf. It's a pro­gram that takes some sort of tex­t, and pro­duces PDF files. It does that as well as I could man­age. It does noth­ing else. It works well or not, but it is what it is, it has a pur­pose, a de­scrip­tion and a goal, and I have tried to achieve that goal with­out em­bar­ras­ing my­self.

My pro­grams are out­hous­es, made of care­ful­ly se­lect­ed and con­sid­ered brick­s. They are not fan­cy, but they are what they are and you know it just by look­ing at them. And if you ev­er need an out­house, well, an out­house is what you should get.

Al­so, peo­ple tend to do weird stuff with them I nev­er ex­pect­ed, but that's just the luck of the anal­o­gy.

But why did I men­tion malls in the ti­tle? Be­cause malls are not out­hous­es. Malls are not done with a goal by them­selves be­yond mak­ing mon­ey for its builder­s. The ac­tu­al func­tion of a piece of mall is not even known when it's be­ing built. Will this be a Mc­Don­ald­s, or will it be a com­ic book store? Who knows!

A mall is built quick­ly with what­ev­er makes sense mon­ey­wise, and it should look bland and recog­nis­able, to not scare the herd. It's a build­ing made for pedes­tri­an­s, but it's in­tend­ed to con­fuse them and make the path form A to B as long and me­an­der­ing as pos­si­ble. The premis­es on which its de­sign is based are all askew, cor­rupt­ed and self­-­con­tra­dict­ing.

They al­so give builders a chance to make lots of mon­ey. Or to lose lots of mon­ey.

Nowa­days, we live in an age of mall soft­ware. Peo­ple build star­tup­s, get fi­nanc­ing, build crap­py soft­ware and some­times they hit it big (Twit­ter, Face­book) or, more like­ly, fade in­to ob­scu­ri­ty leav­ing be­hind noth­ing at al­l, ex­cept mon­ey lost and sad pro­gram­mers who spent nights cod­ing stuff noone will ev­er see or use, and not much else.

Far from me say­ing star­tups are not a no­ble or wor­thy en­deav­our. They are! It's just that peo­ple who work on them should re­al­ize that they are not build­ing soft­ware. That's why code does­n't look im­por­tant to them, be­cause they are ac­tu­al­ly sell­ing eye­balls to ad­ver­tis­er­s, or col­lect­ed per­son­al da­ta from their users to who­ev­er buys that, or cap­tive pub­lic for game de­vel­op­er­s, or what­ev­er your busi­ness mod­el says (if you have one!).

They are build­ing mall­s, where the val­ue is not in the build­ing, which is pret­ty ghast­ly and use­less by it­self, but on the peo­ple in it, those who rent space in the mal­l, those who will use the mal­l, the soft­ware, the so­cial net­work, what­ev­er it is you are build­ing.

Twit­ter is not soft­ware, Face­book is not soft­ware. If they were, iden­ti.­ca and di­as­po­ra would be big­ger! What they are is peo­ple in one place, like a mall is not a re­al build­ing, but a col­lec­tion of peo­ple un­der a roof.

So, there is noth­ing wrong with build­ing mall­s. Just re­mem­ber that your ends and your means are one and a whole, that code is im­por­tan­t, that with­out code Face­book and Twit­ter don't work, and that with­out peo­ple they are a bad­land, and know what you are do­ing.

Be­cause the on­ly hard thing in life is know­ing what you want to do. The rest is the easy part. And be­cause malls with­out toi­lets suck.


Contents © 2000-2023 Roberto Alsina