Skip to main content

Ralsina.Me — Roberto Alsina's website

Posts about programming (old posts, page 46)

Planeta PyAr, or how to generate multiple planets with rawdog

I just fin­ished im­ple­ment­ing my first pub­lic plan­et, Plan­e­ta PyAr us­ing raw­dog. It con­tains the blogs of mem­bers of Python Ar­genti­na

To avoid the clas­si­cal ar­gu­ment about whether this is "the plan­et of peo­ple in PyAr" or "pos­ta about python from peo­ple in PyAr", I de­cid­ed to hack raw­dog in­to pro­duc­ing both.

I did­n't want to have two sep­a­rate raw­dog con­fig­u­ra­tions, so I start­ed look­ing at plug­ins.

The ba­sis was the se­lect­feeds plug­in and the rss plug­in.

Then I merged se­lect­feeds in­to rss to make things sim­pler.

Here is my hacked ver­sion which takes the se­lect­feed ar­gu­ment and us­es on­ly those feeds when cre­at­ing the HTM­L, FOAF, OPML and RSS files.

The bad side of this is that se­lect­feed re­quires you post the feed URLs all in one line!

With just a cou­ple dozen blogs in the plan­et, that means sev­er­al hun­dred char­ac­ter­s. And then when you de­cide to change one URL... ick.

So, I wrote a tiny script to take sep­a­rate feed files and cre­ate sep­a­rate con­fig files, merge-­con­fig.py.

Now, I cre­ate a feed­s-­full file with the non-python-spe­cif­ic feeds in raw­dog for­mat:

#############
# Feeds full
#############

# Anthony Lenton
feed 20m http://anthony.lenton.com.ar/feed/es/
# Rivendel
feed 20m http://www.ceportela.com.ar/?feed=rss2
# Facundo Batista
feed 20m http://feeds2.feedburner.com/com/wEER
:
:

And the same for a feed­s-python:

###################
# Feeds solo python
###################

# Anthony Lenton
feed 20m http://anthony.lenton.com.ar/?feed=rss2&cat=4&language=es
# Rivendel
# No tiene RSS solo python
# Facundo Batista
# No tiene RSS solo python
# Gabriel Patiño
feed 20m http://gepatino.blogspot.com/feeds/posts/default/-/Python
:
:
:

Put all the com­mon con­fig­u­ra­tions in con­fig-base.

And merge-­con­fig.py pro­duces a con­fig, a con­fig-­full and a con­fig-python.

Then run raw­dog like this:

cd ~/.rawdog ; LANG=es_ES rawdog -c config-full -w ; LANG=es_ES rawdog -c config-python -w

And that's it, two plan­ets for the cost of one and a half.I won­der why oth­er plan­ets don't pro­vide this.

uRSSus 0.2.12 released!

Yay!

An­nounc­ing re­lease 0.2.12 or uRSSus a RSS/Atom ag­gre­ga­tor.

This re­lease fix­es the big crashy up­dater bug in 0.2.11, and makes some mi­nor im­prove­ments, like nicer date dis­play, and a xdg re­source in­stall­er.

Indeed screw all gui builders... for java!

Read­ing DZone I ran in­to an in­ter­est­ing post ti­tled Screw all GUI builders which ad­vo­cates drop­ping all GUI builders and in­stead cod­ing your UI by hand.

I al­ways ad­vo­cate us­ing Qt De­sign­er in­stead of cod­ing by hand, so I want­ed to see, even it's talk­ing about Java, I thought, "why don't I feel that way"?

My con­clu­sion? I don't see it be­cause in PyQt we are just lucky be­cause our tools don't suck quite as much.

It gives sev­er­al ar­gu­ments:

  • You don't know how ex­ac­t­­ly the gen­er­at­ed code work­s. You don't need to. You start not to care and GUI ap­­pli­­ca­­tion de­vel­op­­ment be­­comes a process of draw­ing and adding sim­­ple event han­dlers here and there.

To that, my re­ac­tion was yes, in­deed I don't care, as long as it work­s, which it has 99.99% of the time. When it did­n't, I did un­der­stand the gen­er­at­ed code, though.

The rea­son for this will be more ob­vi­ous once you see the code in both cas­es, I think.

  • Most GUI builders force you to use sin­­gle class for sin­­gle win­­dow, so gen­er­at­ed class­es tend to have thou­sands of lines of code.

Hm­m­m... I re­al­ly don't know what this means in con­tex­t, but that's just my ja­va ig­no­rance. OTO­H, yes, one class per win­dow, but al­most nev­er thou­sands of lines of code.

For ex­am­ple, the UI for uRSSus main win­dow is quite com­plex, and it is on­ly 530 lines of code. Here's how it look­s, so you see it's not a triv­ial win­dow:

urssus23 * Most GUI builders don't want you to modify the generated code. And if you do, they either break or rewrite your code.

In­deed De­sign­er's code is not meant to be mod­i­fied. That's what in­her­i­tance is there for. Over­load what­ev­er you want changed. Maybe this is eas­i­er in PyQt be­cause Python is more dy­nam­ic than Java? Not sure.

  • GUI builders force you to use an IDE, most­­ly one you start­ed cod­ing with. So if you start with Net­Bean­s, you most like­­ly be forced to stay with it for the whole pro­jec­t.

Just not true for De­sign­er. I can see how that would suck, though.

  • The gen­er­at­ed code is far from be­ing op­ti­­mal. It's not re­­size-friend­­ly, not dy­­nam­ic enough, it has many hard-­­cod­ed val­ues, refac­­tor­ing is most like­­ly im­­pos­si­ble, be­­cause builder would not al­low that.

De­sign­er does gen­er­ate re­size-friend­ly di­alogs if you use it cor­rect­ly. The hard-­cod­ed val­ues are run­time-ed­itable, refac­tor­ing is pret­ty sim­ple (take what­ev­er you wan­t, cre­ate a wid­get with it?)

And since I want­ed to com­pare ap­ples to ap­ples...

Here is his di­alog, done via PyQt and De­sign­er:

hawkscope1

No, I did­n't both­er in­sert­ing text in the di­a­log so it loks the same ;-)

The "close" but­ton clos­es the win­dow, the URL opens in your sys­tem brows­er.

The text wid­get ac­tu­al­ly sup­ports a sub­set of HTM­L, so there is a valid HTML doc­u­ment there, in­stead of just plain tex­t.

Al­so, an­oth­er thing is ... De­sign­er's (or rather pyuic's) gen­er­at­ed code is straightor­ward stuff.

Here's the code, which I think is rough­ly equiv­a­lent to his, on­ly it's just 123 LOC, in­stead of 286 (his gen­er­at­ed ver­sion) or 279 (his hand-­made ver­sion). And I did­n't delete the com­ments.

If I were do­ing this for re­al, all wid­gets would have de­scrip­tive names in­stead of Push­But­ton1 and what­ev­er.

Al­so, it's i18n-ready, un­like the Ja­va ver­sion­s, un­less I missed some­thing.

You can al­so get hawkscope.ui from this site to play with, it's done with De­sign­er from Qt 4.4.

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

# Form implementation generated from reading ui file 'hawkscope.ui'
#
# Created: Fri Feb 13 22:39:47 2009
#      by: PyQt4 UI code generator 4.4.4
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(444, 357)
        self.verticalLayout_2 = QtGui.QVBoxLayout(Dialog)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.horizontalLayout_2 = QtGui.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.label_2 = QtGui.QLabel(Dialog)
        self.label_2.setPixmap(QtGui.QPixmap("hawkscope.png"))
        self.label_2.setAlignment(QtCore.Qt.AlignCenter)
        self.label_2.setObjectName("label_2")
        self.horizontalLayout_2.addWidget(self.label_2)
        self.verticalLayout = QtGui.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.label_3 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.label_3.setFont(font)
        self.label_3.setObjectName("label_3")
        self.verticalLayout.addWidget(self.label_3)
        self.label_4 = QtGui.QLabel(Dialog)
        self.label_4.setObjectName("label_4")
        self.verticalLayout.addWidget(self.label_4)
        self.gridLayout = QtGui.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.label_5 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.label_5.setFont(font)
        self.label_5.setObjectName("label_5")
        self.gridLayout.addWidget(self.label_5, 0, 0, 1, 1)
        self.label_10 = QtGui.QLabel(Dialog)
        self.label_10.setObjectName("label_10")
        self.gridLayout.addWidget(self.label_10, 0, 1, 1, 1)
        self.label_9 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.label_9.setFont(font)
        self.label_9.setObjectName("label_9")
        self.gridLayout.addWidget(self.label_9, 1, 0, 1, 1)
        self.label_8 = QtGui.QLabel(Dialog)
        self.label_8.setObjectName("label_8")
        self.gridLayout.addWidget(self.label_8, 1, 1, 1, 1)
        self.label_7 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.label_7.setFont(font)
        self.label_7.setObjectName("label_7")
        self.gridLayout.addWidget(self.label_7, 2, 0, 1, 1)
        self.label_6 = QtGui.QLabel(Dialog)
        self.label_6.setOpenExternalLinks(True)
        self.label_6.setObjectName("label_6")
        self.gridLayout.addWidget(self.label_6, 2, 1, 1, 1)
        self.verticalLayout.addLayout(self.gridLayout)
        self.horizontalLayout_2.addLayout(self.verticalLayout)
        self.verticalLayout_2.addLayout(self.horizontalLayout_2)
        self.label = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.verticalLayout_2.addWidget(self.label)
        self.textBrowser = QtGui.QTextBrowser(Dialog)
        self.textBrowser.setObjectName("textBrowser")
        self.verticalLayout_2.addWidget(self.textBrowser)
        self.horizontalLayout = QtGui.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.pushButton = QtGui.QPushButton(Dialog)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout.addWidget(self.pushButton)
        self.pushButton_2 = QtGui.QPushButton(Dialog)
        self.pushButton_2.setObjectName("pushButton_2")
        self.horizontalLayout.addWidget(self.pushButton_2)
        self.verticalLayout_2.addLayout(self.horizontalLayout)

        self.retranslateUi(Dialog)
        QtCore.QObject.connect(self.pushButton_2, QtCore.SIGNAL("clicked()"), Dialog.accept)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.label_3.setText(QtGui.QApplication.translate("Dialog", "Hawkscope", None, QtGui.QApplication.UnicodeUTF8))
        self.label_4.setText(QtGui.QApplication.translate("Dialog", "Access anything with single click!", None, QtGui.QApplication.UnicodeUTF8))
        self.label_5.setText(QtGui.QApplication.translate("Dialog", "Version:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_10.setText(QtGui.QApplication.translate("Dialog", "0.4.1", None, QtGui.QApplication.UnicodeUTF8))
        self.label_9.setText(QtGui.QApplication.translate("Dialog", "Released:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_8.setText(QtGui.QApplication.translate("Dialog", "2009-02-06", None, QtGui.QApplication.UnicodeUTF8))
        self.label_7.setText(QtGui.QApplication.translate("Dialog", "Homepage:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_6.setText(QtGui.QApplication.translate("Dialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Droid Sans\'; font-size:8pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><a href=\"http://hawkscope.googlecode.com\"><span style=\" text-decoration: underline; color:#3c7dbe;\">http://hawkscope.googlecode.com</span></a></p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
        self.label.setText(QtGui.QApplication.translate("Dialog", "Environment", None, QtGui.QApplication.UnicodeUTF8))
        self.textBrowser.setHtml(QtGui.QApplication.translate("Dialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Droid Sans\'; font-size:8pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Text goes here</p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"></p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton.setText(QtGui.QApplication.translate("Dialog", "C&opy To Clipboard", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton.setShortcut(QtGui.QApplication.translate("Dialog", "Alt+O", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton_2.setText(QtGui.QApplication.translate("Dialog", "&Close", None, QtGui.QApplication.UnicodeUTF8))

Counting unread items is HARD

I mean, if google can get it wrong, I have an ex­cuse, right?

urssus24

See how at the same time you see:

  • At the left: "All items (8)"

  • At the top-­­cen­ter: "8 new item­s"

  • In the tree: 8 items

  • In the list: 14 new items

Each of those num­bers can get out of sync with each oth­er if you do some­thing the "wrong" way.

But what the heck, it seems as of r678 urssus does it right. The list of un­read posts even up­dates when you have it open adding the new posts in the right places with­out dis­turb­ing you, and keep­ing the right num­bers ev­ery­where, AFAIC­S!

urssus23

So here, in uRSSUs:

  • 13 un­read ar­ti­­cles in the "Un­read Ar­ti­­cles" item

  • 13 in "All Feed­s"

  • 13 adding each fold­er

  • 13 in the ar­ti­­cle list

Sad­ly, this is all post-0.2.11 re­lease.


Contents © 2000-2024 Roberto Alsina