Skip to main content

Ralsina.Me — Roberto Alsina's website

More fun with rst2pdf, poppler and PyQt

First: I re­al­ly, re­al­ly need a name for this thing. I am tired of say­ing "my rst2pdf pre­view­er/ed­i­tor ap­p".

Now, here's a video of how it looks nowa­days af­ter all the yak shav­ing (sor­ry about my en­glish, I write lots of it, but nev­er speak it):

As you can see, the ba­sic app is fair­ly com­plete, even if it lacks all the ameni­ties that would make some­one use it (like, search? save? ;-).

It has one big prob­lem, though: I can't pub­lish it yet.

Why? Be­cause I need to use pop­pler from PyQt, and the code I found to do it has no li­cense (see the code).

I am try­ing to con­tact the au­thor (Ra­jeev J Se­bas­tian), so there should be news soon!

As soon as that's cleared, the PDF wid­get is a whole post by it­self, be­cause it's pret­ty neat, if I may say so my­self.

UP­DATE: the bind­ing is now un­der MIT li­cense, thanks to Ra­jeev Se­bas­tian!

Yak Shavings for September 22, 2009

yak shaving

(id­iomat­ic) Any ap­par­ent­ly use­less ac­tiv­i­ty which, by al­low­ing you to over­come in­ter­me­di­ate dif­fi­cul­ties, al­lows you to solve a larg­er prob­lem.

This yak is start­ing to look bet­ter.

For my sec­ond pile of yak shav­ings: turn­ing QPlain­TextE­d­it in­to a de­cent edit­ing wid­get for pro­gram­mer­s.

As work ad­vanced in my rst2pdf ed­i­tor (BTW: need a name!), it be­came ob­vi­ous that the piece of the UI the us­er will use most is just a cou­ple of plain text ed­i­tors.

Qt comes with a wid­get for that, of course, called QPlain­TextE­d­it. How­ev­er, it's a very, very bad wid­get for pro­gram­mer­s.

Here's the least I want­ed:

  1. Syn­­tax high­­­light­ing for two lan­guages: re­struc­­tured text and javascrip­t. This yak is al­ready shaved.

  2. Line num­bers

  3. Cur­rent line high­­­light

  4. Er­ror high­­­light when it makes sense (like, in the stylesheet)

One way to achieve this would be to dump QPlain­TextE­d­it and use QS­ciScin­til­la which is the ba­sis for the code ed­i­tor in er­ic and (an­oth­er ver­sion) in scite.

How­ev­er, I ex­pe­ri­enced a bad bug in QS­ciScin­til­la, where I can't type ac­cent­ed char­ac­ter­s! With­out that, (de­cen­t) span­ish is im­pos­si­ble, and the bug seems to be at least two years old, so... no go.

So, did I get those fea­tures? I say yes!

Here is the video (yes, I am get­ting ad­dict­ed to mak­ing the­se, since qt-record­my­desk­top makes them so easy ;-):

The ba­sis for this is the Code Ed­i­tor ex­am­ple that comes with Qt it­self, plus a bit of my own handy­work.

First, I port­ed Code Ed­i­tor from C++ to Python, which was very sim­ple and took a few min­utes. That takes care of points 2 and 3.

Then, the syn­tax high­light was plugged in, which was point 1.

Then, how about re­al­time javascript val­i­da­tion? Easy us­ing sim­ple­j­son! Just make sure to run this when­ev­er you want val­i­da­tion (I do it on ev­ery keystroke).

Re­place self­.ui.style.­toPlain­Text with what­ev­er your wid­get is called, of course:

def validateStyle(self):
    style=unicode(self.ui.style.toPlainText())
    #no point in validating an empty string
    if not style.strip():
        return
    pos=None
    try:
        json.loads(style)
    except ValueError, e:
        s=str(e)
        print s
        if s == 'No JSON object could be decoded':
            pos=0
        elif s.startswith('Expecting '):
            pos=int(s.split(' ')[-1][:-1])
        else:
            print 'UNKNOWN ERROR'

    # This makes a red bar appear in the line
    # containing position pos
    self.ui.style.highlightError(pos)

highlightError(pos) simply stores pos in the Code Editor, which will draw a red bar in that line, the same way it highlights the current line.

And that's it. Here is the code for codeed­i­tor.py

Yak Shavings for september 21, 2009

yak shaving

(id­iomat­ic) Any ap­par­ent­ly use­less ac­tiv­i­ty which, by al­low­ing you to over­come in­ter­me­di­ate dif­fi­cul­ties, al­lows you to solve a larg­er prob­lem.

And boy, are my yaks hairy!

I start­ed try­ing to do a rst2pdf stylesheet ed­i­tor (see here).

One thing lead to an­oth­er, and I have now at least three in­ter­est­ing mini-pro­jects be­cause of it.

Here's to­day's: abuse pyg­ments to use it as a gener­ic syn­tax high­lighter in a Qt in­ter­face.

Why pyg­ments? Be­cause it's the on­ly re­Struc­tured Text high­lighter I found. That's prob­a­bly be­cause reSt is pret­ty damn hard to high­light!

AFAIK, this is the first time any­one has man­aged to use pyg­ments for this, in an ed­itable win­dow. And there are good rea­sons for that:

  • It's pure python, so maybe you ex­pect it to be too slow

  • It does­n't do par­­tial or pro­­gres­­sive lex­ing, so you need to lex the whole thing (a­­gain, maybe you ex­pect it to be too slow)

  • It has a file-ori­en­t­ed API, it gen­er­ates a file with all the for­­mat­t­ing in it, and for this kind of thing we need to ac­cess stuff in the mid­­dle of the da­­ta.

So, of course, it turns out it works pret­ty well, as you can see in this video:

Les­son learned: com­put­ers are fast nowa­days.

Here's the code for high­lighter.py with ex­ten­sive com­ments.

You can just run it and get the same de­mo you saw on the video (mi­nus the typ­ing ;-)

Having a little fun with poppler, PyQt and rst2pdf

In­spired by a post by An­dré Roberge I want­ed to see if rst2pdf was too slow to be used for re­al-­time pre­views in a re­struc­tured text ed­i­tor.

It would al­so be very use­ful, for ex­am­ple, as a way to test stylesheet changes, mak­ing rst2pdf much more use­ful in gen­er­al.

And af­ter a cou­ple of hours of gen­tle hack­ing, you know... it does­n't suck at all. I im­ple­ment­ed the (still very prim­i­tive) PDF view­er us­ing a python/pop­pler/Qt bind­ing I found via google, the UI is PyQt.

Here's the video:

A note: the video was record­ed us­ing qt-record­my­desk­top and that pro­gram is awe­some. It was triv­ial to do.

I ex­pect this will not be good enough when long doc­u­ments are pro­cessed, but the rst2pdf man­u­al (about 25 pages) ren­ders in 5 sec­ond­s.

An innocent question...

There is a very fun­ny thread cur­rent­ly in the PyAr (Python Ar­genti­na) mail­ing list.

It all start­ed when "Galileo Galilei" asked about how to do a very sim­ple thing. He pre­sent­ed this code:

age = int(raw_input('How old are you?'))
if age<18:
    print 'You are underage'
else:
    print 'You are a grownup'

Ok, the orig­i­nal was... not quite as po­lite, but the code is the same. So far, noth­ing strange. But then he asked this:

How can I make it so that if the us­er en­ters some­thing that is not a num­ber, it does some­thing like this:

print 'I have no supercow powers'

or maybe

print 'Typing error'

You can prob­a­bly imag­ine that ask­ing this kind of thing should pro­duce maybe two an­swer­s. Right?

That is in­deed the case, and you can see that in the an­swer by Fa­cun­do Batista or Eze­quiel.

Ex­cep­t... what if we want­ed it to keep ask­ing in case the us­er en­tered not-a-num­ber?

Then, my friend­s... it's about taste, and it's all Juan Pe­dro Fisan­ot­ti's fault.

Here's my take:

while True:
    edad=raw_input('¿Cuantos años tenes?')
    if edad.isdigit():
        break
    print 'No ingresaste un numero!'

Yes, I ad­mit, a bit old fash­ioned. And there was a cry of "no, break suck­s, it's not right", which leads to this by Manuel Aráoz

age = raw_input('Your age?')
while not age.isdigit():
    print "That's not a number!"
    age = raw_input('Your age?')

Which caused cries of "Hav­ing raw_in­put twice is ug­ly!", which leads to (a­gain by Manuel Aráoz):

get_age = lambda: raw_input('Your age?')
age = get_age()
while not age.isdigit():
    print 'Not a number!'
    age = get_age()

Here Patri­cio Moli­na digs up PEP 315.

And then Ale­jan­dro San­tos says some­thing like "This is eas­i­er in C, be­cause we can as­sign a val­ue to age in the while's con­di­tion". Please re­mem­ber this.

Now Pablo Zil­liani gives his ver­sion, which is, I must say, per­fect in some ways:

age = reset = msg = 'Age?: '
while not age.isdigit():
    age = raw_input(msg)
    msg = "%r is not a number!, %s" % (age, reset)

print age

Here Gabriel Genel­li­na de­cides to de­fend break by hit­ting ev­ery­one in the head us­ing Knuth which should have a much stronger ef­fect than Hitler.

And now, we start veer­ing in­to weird wa­ter­s. Here is what news pro­pos­es, which I must say, I ad­mire... from a re­spect­ful dis­tance.

First, the rel­e­vant code:

edad = "0" # Entra igual la primera vez

while firstTrue (not edad.isdigit()):
    edad = raw_input ("¿Cuantos años tenes? ")
    if not edad.isdigit():
        print "No ingresaste un nro!"

But what, ex­act­ly, is first­True?

import inspect

def firstTrue(cond):
    """ devuelve True siempre la primera vez que se la ejecuta,
    las veces subsiguientes evalua la condicion """
    stack = inspect.stack()[1] # El stack del programa llamador
    line = stack[2] # Nro de linea desde la que llame a firstTrue
    del stack

    if not "line" in firstTrue.__dict__:
        # Primera vez que llamo a la funcion
        firstTrue.line = line
        return True
    elif firstTrue.line != line:
        # Llame a la funcion desde otro punto del programa
        firstTrue.line = line
    return True

    return cond

Then, I bring up gen­er­a­tors, which leads to Clau­dio Freire's, which al­most work­s, too:

age = ''
def invalidAge():
    yield True
    while not age.isdigit():
        print "Not a number"
        yield True
    yield False

for i in invalidAge():
    age = raw_input("Age please: ")

print age

And then Fabi­an Gal­li­na is the sec­ond one to bring up C's as­sign­ments in­side con­di­tion­s.

You know, I can't ac­cept that. I will not ac­cept C be­ing eas­i­er for this.

So, with a lit­tle help from the python cook­book...

age=[1]

while not age |asig| raw_input('Age? '):
    print 'Not a number!'

print u'You are %s years old'%age[0]

You may ask, what's |asig|? Glad you asked!

class Infix:
    def __init__(self, function):
        self.function = function
    def __ror__(self, other):
        return Infix(lambda x, self=self, other=other: self.function(other, x))
    def __or__(self, other):
        return self.function(other)
    def __rlshift__(self, other):
        return Infix(lambda x, self=self, other=other: self.function(other, x))
    def __rshift__(self, other):
        return self.function(other)
    def __call__(self, value1, value2):
        return self.function(value1, value2)

def opasigna (x,y):
    x[0]=y
    return y.isdigit()

asig=Infix(opasigna)

And then, Pablo posts this gem:

import inspect

def assign(var, value):
    stack = inspect.stack()[1][0]
    stack.f_locals [var] = value
    del stack
    return value

while not assign("age", raw_input('Age? ')).isdigit():
    print u'Not a number!'

print u'You are %s years old' % age

Which is, IMVHO, about as far from triv­ial as you can get here. Of course the thread is not dead yet ;-)


Contents © 2000-2024 Roberto Alsina