Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Sunday, August 8

A Python Syntax Highlighter for HTML, Written in Python

As of late, I wanted to post some python code on my blog and the formatting options I found kind of sucked (actually I was getting bored and looked for excuses to roll my own).

I had a look at some options, but the most known, either had to be installed locally (and I didn't want to do that), or they worked online, but the generated code was incomplete or the formatting sucked (in my self-absorbed opinion).

Long story short, I wrote my own, in Python.

The formater generates HTML code separating operators, keywords, tokens (identifiers, variables and numbers), comments and text.

The colors for these can be set in the COLORS global map.

Here's the code (the formatter err ... formatted itself for this post, as a test):

import sys
import re

# alphanumeric character escaped
# for regular expressions
ALPHANUMS = 'a-zA-Z0-9_'

# python operators, escaped
# for regular expressions
OPERATORS = r'\.\(\)\[\]\{\}\:\'\"\!=,%\+\-\*\/\^\&<>'

# python keywords
KEYWORDS = ['if', 'for', 'while', 'do', 'def',
    'class', 'None', 'True', 'False', 'and',
    'or', 'not', 'import', 'else', 'elif',
    'raise', 'except', 'break',    'continue',
    'lambda', 'return', 'yield', 'global']

COLORS = {
    'bg':'#030303',
    'kw':'blue',
    'tk':'silver',
    'op':'teal',
    'cm':'green',
    'txt':'red',
    'qu':'darkred',
    '??':'white'}

# how many spaces should a tab character be:
TAB_LENGTH = 4

escape = lambda source, replacements: \
    ''.join( [ c if c not in replacements \
        else replacements[c] \
        for c in source ] )

# escape a string so it will be parsable
# by the re module
def rxEscaped(source):
    replacements = {
        r'[':r'\[',
        r']':r'\]',
        r'{':r'\{',
        r'}':r'\}'}
    return escape(source, replacements)

SPACES = ' \t'
SPLITTERS = rxEscaped( OPERATORS + SPACES )

class TokensList(list):

    replacements = {
        '<':'&lt;',
        '>':'&gt;',
        '&':'&amp;',
        ' ':'&nbsp;',
        '\t':'&nbsp;'*TAB_LENGTH}

    def __init__(self):
        list.__init__(self)

    def add(self, key, value):
        self.append(
            (key, \
            escape(value,
                TokensList.replacements)) )


class Tokenizer(object):
    '''Transforms a line in a group of tokens,
    where each token is represented by a pair:
        line -> [ (key, token), (key, token), ... ]

    The key represents the type of the token:
        kw = keyword
        tk = word
        sp = spacing
        op = operator
        cm = comment
        qu = quotes
        txt = string contents
        ?? = unidentified (for any errors)

    '''

    rxToken = re.compile(r'''
        ^(?P<tk>[%s]+)        # string token
        |(?P<sp>[%s]{1})    # or space
        |(?P<op>[%s]{1})    # or operators
        |(?P<cm>\#.*$)        # or comment
        ''' % (ALPHANUMS, SPACES, OPERATORS), \
        re.VERBOSE)

    quotes = None

    def __init__(self):
        self._init()

    def _init(self, line=''):
        self.line, self.parsed = line, line
        self.tokens = TokensList()

    def _parseRx(self, rx):
        '''Search for rx against line and return
        the found match and the groups dict.

        '''
        found = re.search(rx, self.parsed)
        if found:
            return (found, found.groupdict())
        else:
            return (None, None)

    def _parseStringStart(self):
        quotes = \
            self._parseRx(r'^(?P<qu>[\']{3})')[1] or \
            self._parseRx(r'^(?P<qu>[\"]{3})')[1] or \
            self._parseRx(r'^(?P<qu>\')')[1] or \
            self._parseRx(r'^(?P<qu>\")')[1]
        if quotes:
            quotes = quotes['qu']
            self.parsed = self.parsed[len(quotes):]
            Tokenizer.quotes = quotes
            return True
        return False

    def _getTokens(self):

        while self.parsed:
            ## are we in a string?
            if Tokenizer.quotes:
                if len(Tokenizer.quotes) == 3:
                    # rx for multiline string
                    rx = '(?P<qu>%s)' % \
                        (('\\' + Tokenizer.quotes[0])*3)
                else:
                    # rx for single line string
                    rx = r'(?P<qu>%s)' % \
                        Tokenizer.quotes
                token, gd = self._parseRx(rx)
                if gd:
                    start, end = token.span('qu')
                    if start > 2 and \
                        self.parsed[start-1] == '\\' and \
                        self.parsed[start-2] != '\\':
                        self.tokens.add('txt',
                            self.parsed[:end])
                        self.parsed = self.parsed[end:]
                    else:
                        self.tokens.add('txt',
                            self.parsed[:start])
                        self.tokens.add('qu', gd['qu'])
                        Tokenizer.quotes = None
                        self.parsed = self.parsed[end:]
                    continue
                elif len(Tokenizer.quotes) == 3:
                    # a multiline string is legal
                    # add everything as text
                    self.tokens.add('txt', self.parsed)
                else:
                    # singleline string not closed
                    # pass everything as '??'
                    self.tokens.add('??', self.parsed)
                    # process next line correctly
                    Tokenizer.quotes = None
                break

            # are qe opening a string now?
            if self._parseStringStart():
                self.tokens.add('qu', Tokenizer.quotes)
                continue

            # we're not parsing a string
            token, gd = self._parseRx(Tokenizer.rxToken)
            if not gd:
                self.tokens.add('??', self.parsed)
                break

            for key in gd:
                value = gd[key]
                if not value:
                    continue
                elif value in KEYWORDS:
                    self.tokens.add('kw', value)
                else:
                    self.tokens.add(key, value)
            self.parsed = self.parsed[token.end():]

    def parse(self, line):
        # reset the object
        self._init(line)
        # get list of tokens
        self._getTokens()
        # merge like tokens
        retval, currentKey, currentList = [], '', []
        for key, value in self.tokens:
            if key == currentKey:
                currentList.append(value)
            else:
                retval.append( (currentKey, currentList) )
                currentKey, currentList = key, [value]
        if len(currentList):
            retval.append( (currentKey, currentList) )
        self.tokens = retval
        return self.tokens

def generateFormattedFile(source, destination):
    '''Generate a formatted HTML block
    from the source code.

    '''

    dest = open(destination, 'wt')
    dest.write('''<div style="background-color:%s;">
        <pre>''' % COLORS['bg'])

    tokenizer = Tokenizer()

    NEWLINE = '\n'

    for line in open(source).readlines():
        line = line.rstrip()
        if not line:
            dest.write(NEWLINE)
            continue
        formattedLine = ''
        for name, list in tokenizer.parse(line):
            if name in COLORS:
                formattedLine += \
                    '<span style="color:%s;">' % \
                        COLORS[name] + \
                    ''.join(list) + '</span>'
            else:
                formattedLine += ''.join(list)
        dest.write(formattedLine + NEWLINE)
    dest.write('''
        </pre>
    </div>''')

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('Syntax: %s <sourcefile>' % sys.argv[0])
    else:
        generateFormattedFile(sys.argv[1], sys.argv[1] + '.html')

  

Wednesday, June 24

A Letter from Bill Gates

BoycottNovell has a posting on a letter on strategy, written by Bill Gates. The spin they put on the story is on how Bill Gates is afraid of linux, but I want to focus on something else: is it interesting?

First, a small paranthesis: Is the letter authentic?

I'd say yes, for the following reasons:
1. I'm not very familiar with BoycottNovell and their sources of information, but from what I've read of the letter (provided in it's entirety in the article) I would say that it's either authentic, or whoever made it is very insightfull and has lots of "vision".

2. If they published a fake letter, they be very open to attacks (which would only serve them in the short run - publicity stunt/shock value/whatever).


The letter talks (among other things) about linux and java being Windows' largest competitors and the need to preventing commodization by linux (you may be too late on that) about taking good ideas for improvement from a variety of sources, about a major overhaul of the file/storage system (the points being made there sounded to me both revolutionary and obvious at the same time), about a need (stated explicitely) to improve system monitoring enough to be better than on linux, difficulty of setting up windows for a speciffic purpose, unifying the application platform technologies to win back Java and J2EE developers, improving Windows boot time and improving asynchronous communication (email, Gmail, CRM, scheduling, etc) - maybe they should take a look at Google Wave.

All in all it's an interesting letter, and if they manage to achieve all that, it will make for interesting times.

Sunday, May 24

"Miles per gallon" to "litres per 100 km" conversion

I'm tired of reading(or watching) mpg comparisions, when they don't give me a frame of reference. As such, I'd decided to write my own online convertor for it.

This is the algorithm (in python):
def mpg2lpckm(mpg):
one_mile=1.609344
one_gallon=3.78541178
kmpg = mpg * one_mile # km/gallon
kmpl = kmpg / one_gallon # km / l
lpkm = 1. / kmpl # l / km
return lpkm * 100.
and here is the online conversion:

Thursday, July 17

Windows doesn't suck; it lies!

So, yesterday I got myself a new HDD, for extra storage space, at home. I installed it with no problems, then booted Vista (for the more familiar interface) and attempted to format it using FAT32.

Apparently though, 150Gb is way too much for a FAT32 partition and Vista only allowed me to format using NTFS.

I would respect that it didn't allow me to perform an invalid formatting, that would have ruined my new hard-drive, except it isn't so: I booted under Linux and was able to format the partitions with no problems whatsoever.

It turns out that windows is simply lying to you, so that you'd be forced to use a proprietary partition format (leading to platform lock-in) :(
At least, that's the only conclusion I could come up with.

In this case however, all they managed was make me give another vote to Linux.

Friday, June 27

ooops! did I break del.icio.us?

So ... I've been learning from Collective Intelligence ... and this morning I made a script to get links from del.icio.us, score their top users in regards to how similar their tastes are to mine, then recommend me links they preferred, that I haven't bookmarked ...

In short, it's a recommendation system (yeah ... buy the book!).

Either way, I omitted the time.sleep(4) between failed requests to the del.icio.us api and I ... kind of started getting this:


danone@utnapistix:~/work/python/CollectiveIntelligence$ python deliciousrec.py
Traceback (most recent call last):
  File "deliciousrec.py", line 51, in
    del_data=buildPostersDict(<tag>, <user>)
  File "deliciousrec.py", line 43, in buildPostersDict
    users_dict=initializeUserDict(tag)
  File "deliciousrec.py", line 8, in initializeUserDict
    for popular in get_popular(tag=tag)[0:count]:
  File "/usr/lib/python2.5/site-packages/pydelicious.py", line 786, in get_popular
    return getrss(tag = tag, popular = 1)
  File "/usr/lib/python2.5/site-packages/pydelicious.py", line 774, in getrss
    return dlcs_rss_request(tag=tag, popular=popular, user=user, url=url)
  File "/usr/lib/python2.5/site-packages/pydelicious.py", line 414, in dlcs_rss_request
    rss = http_request(url).read()
  File "/usr/lib/python2.5/site-packages/pydelicious.py", line 261, in http_request
    raise PyDeliciousException, "%s" % e
pydelicious.PyDeliciousException: HTTP Error 503: Service Temporarily Unavailable


Del.icio.us guys, if it was me that caused a denial of service on your website, I'm sorry!

Thursday, June 19

Friday, May 9

good management lesson

A manager went to the master programmer and showed him the requirements document for a new application. The manager asked the master: "How long will it take to design this system if I assign five programmers to it?"

"It will take one year," said the master promptly.

"But we need this system immediately or even sooner! How long will it take if I assign ten programmers to it?"

The master programmer frowned. "In that case, it will take two years."

"And what if I assign a hundred programmers to it?"

The master programmer shrugged. "Then the design will never be completed," he said.

Excerpt from The Tao of Programming, Book Three

Tuesday, April 22

another python easter egg

PS C:\work> python
ActivePython 2.5.1.1 (ActiveState Software Inc.) based on
Python 2.5.1 (r251:54863, May 1 2007, 17:47:05) [MSC v.1310 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from __future__ import braces
File "", line 1
SyntaxError: not a chance

Monday, April 21

Why I'm interested in Python

It's been said that Tim Peters succinctly channels the BDFL's guiding principles for Python's design into 20 aphorisms, only 19 of which have been written down.
-- (pep0020)

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!


... and that is why I'm interested in python ... or maybe not.

Thursday, February 28

Ticket #10919 (incorrect pluralization) A.K.A. Why Ruby Just Might Be Cooler than Python

I haven't laughed so much since ... yesterday? the day before? I laugh a lot, so that's probably not relevant.

Either way, Ticket #10919 appears to be a real defect opened for the Ruby language.

(I received the link from A.C., on a jokes list I subscribe to).

Saturday, January 12

on commenting your code

Speaking about an action and doing an action are two entirely different things. As such, whatever you say, will never change your actions (if your behaviour is reprehensible, no matter how you justify it, your behaviour is still reprehensible).

More than that, when you feel the need to justify yourself, this should tell you not that your action is just because you managed to justify it), but quite the opposite: if it actually were just, you wouldn't feel the need to justify it.

This is why I don't believe in commenting the purpose of your code.

The code should be the comments; that is, it should be self-evident what the code does, by looking at it.

There are, probably some border cases when this doesn't apply (when writing libraries you need to comment the public API for example), but usually, obscure code just tells you of poor coding style.

Adding comments to obscure code is not bettering the code; it's just a justification. As such there are some cases that you need to verify, to see if you need to revise your coding style:

Your (older) code is unclear to you. When you pass through it, you need to add comments to it, to make it clearer.

To summarize:
.A Lacking comments are not the problem; What you need to do (if the code will still need to be modified in the future) is to seriously consider refactoring it.

.B It may be that your code is clear to you, but not your coleagues. Not only is your coding style poor, you don't even know enough to realize your coding style is poor. More, you don't even know enough to feel your coding style is poor. <<GOTO .A>>

.C It may be that your code is clear to you and clear to your coleagues.
Stop deluding yourself!

Saturday, January 5

the right notation is half of the solution

I'm not sure who* created the presentation for Subtext, but he knows what he's talking about.

The presentation is interesting (enough that I thought I'd share it further) and I will definitely look for more details on it.

The project homepage is at subtextual.org.



* Later edit: The guy's name is Jonathan Edwards.

Friday, November 30

Please do not port software to Windows!

I found a small plea on the net for not porting GNU software to Windows. At first sight, the arguments sounded convincing, but I find them to be mainly colour-blind arguments.

They sounded to me close to believer-fervour (and that should, I believe, always be closely examined, lest it degrades to fanaticism).

The writer says (among other things):
Many people using Windows don't care about their freedom. They do care about quality software and for that reason try to replace all the user space software from Microsoft with better free alternatives. This is the sole reason for the existance of cygwin.
which is a gross generalization. The "sole reason" for the existence of cygwin is more than one (so to speak). Among these reasons, is providing a compatibility layer.

It is the same purpose that is provided by the wine on *NIX, by the way.

Is cygwin more powerful than Windows' default software?
Arguably, it is.
Still, the reason I had it installed at my old job, was because I needed to run some shell scripts that needed it. For a Windows shell, I now use Windows PS (easier to install, though not as powerful or complete in features, as cygwin, but it's enough).

More of the arguments, made me remember the how-to that I commented on, a few days ago: instead of having an open mind, they start from "Windows is evil, how do we get about replacing it?"

Do I think windows is evil?
Not in purpose.

Are they trying to make (more) money?
Yes.

Does that make them evil?
Not as such, no.

Does it make them unethical?
Sometimes (OK, most of the time), but still, not "evil", not "bend on sabotage/destruction" which "evil" would mean.

The writer of the plea is ignoring completely Microsoft's purpose here: they don't care for "making software", but for "making money". Once you keep this in mind, they're just another corporation.

Does that make them unfit for any purpose?
No.

It is (arguably) easier for a company to get up a Windows network, than a *NIX one (if for no other reasons, then for the number of Windows administrators available in the workforce, compared to *NIX administrators).

It is also easier for a commercial development team to use a suite of products that integrate together than five or ten separate ones.

When, as a team leader, you know you have to deliver in six months, you'd rather go for the software package with the smaller learning curve, not for "yes, but after we learn vi, we'll write code really fast" (especially if the month spent customizing vi and fighting the vi learning curve, is a month the team could be writing software in).

That said, I use Kubuntu Linux at home. It is fit for my purpose at home (playing with it's settings, adding lots of things and trying them out, browsing and a bit of development), and it's free (yes, freedom in software matters, both in price and in choice).

I feel having more of the world switching to free operating systems would generally be a good idea (if for no other reasons, then for encouraging competition), but using partial arguments and trying to limit choice ("please don't port my software to windows" so windows users might be forced to switch) is the same kind of play that Microsoft does, but on a smaller scale:

It is limiting freedom, all over again.

Wednesday, November 14

deadlock party

The deadlock party is a programmer-specific celebration that occurs sporadically, whenever programmers have to solve a deadlock bug ... past eight o'clock in the evening.

Let us see how the Deadlock Party is celebrated:

I'm in office, drinking mint tea (no sugar) and debugging some stuff, at close to eleven PM. I have around 30 meters of cable (I exaggerate a bit, but I think it would be at least between five and ten meters) on my desk connected to all kinds of USB hubs, monsters, a custom machine, seven mobile phones (one of which mine), lots of papers under everything, a small laptop (closed) and - of course, my tea.

I'm currently recompiling the application to rerun with some added diagnostics code, in the hope this will cast some light over what's happening; I'm looking for the source of a deadlock.

For my programmaticaly-challenged friends out there, a deadlock occurs when a part of a program is waiting for another part to do something while the second part is waiting for the first part to do something; as a conclusion, the application stops responding.

Wish me luck at my deadlock party :)

Friday, November 9

python and large numbers

Well ... I was getting bored ten minutes ago, and I wanted to see if python supports large numbers. So, I created a method that would generate a prime number at every iteration, ran it twenty times, and I got this:

108020433769043901318839132754528513920805844617499642188964117099518224707733815
74011445899674627353379179210735712212091688247518797177364364065078362935163284
9134 [lots of digits removed as per Mrs. Anonymous request; to see the full number, run the python code, below] 4420807

So ... can anyone tell me if it's really a prime number?

Logically, it should be; This is the code:

primes = [1]

def some_prime():
candidate = 1
for i in primes:
candidate *= i
candidate += 1
primes.append(candidate)

for i in range(1, 20): some_prime()
print primes[len(primes)-1]


Thanks :)

Thursday, October 25

working with Mercurial

Working with mercurial is a strange/new experience in version management. They called it "the new step" over traditional methods, and I was skeptic.

Now, I'm inclined to agree.

The main difference between Mercurial (and, as far as I understand GIT is the same) and traditional version control systems (CVS, Perforce, VSS et all) is Mercurial's distributed nature: here, every local copy of a repository that you make, is a new repository in itself, supporting retrieving previous versions of the code, adding new changes and merging with other repositories (among other things).

This is confusing, the first couple of times you work with it, but as you go along, you start to see the benefits:
  • you no longer have to wait when getting a previous version of the code (it's a local operation)
  • you can (at any time) re-sync your changes with the master version (having a master version - or more - is completely optional; infact, I undestand that, for the linux kernel development model, there is no master version at all, just some preferred/priviledged repositories)
  • you need not make periodical backups (if - say - you loose your HDD, you can simply clone another repository - unless all the HDDs of all the team members fail at the same time :-))
  • it's under your complete control, and creating and merging repositories is as easy as typing two commands (at the moment I have three repositories on my machine, for working on different things: One for a local "master" copy, one for working, and one for testing Mercurial commands, trying various things and generally playing)


Infact, I like it enough to have switched on my home computer from cvs, to mercurial.

In short, it rocks :)

Thursday, October 11

Everything You Do Should Be Easy

I've just finished refactoring some code and then I realized how easy it is now, to extend it further; More than that, it's a pleasure to do it, starting with clean code.

That made me think: whatever I do, whatever you - or anyone else - for that matter - does, it should be easy to do. The easier it appears to you, the closer you are to mastering that domain.

I'm saying this, generalizing from programming but I think it stands true for everything else. I'm not coming here to claim I've "mastered programming" - or something ridiculous like that.

It's not about me, and it's not about programming.

When you don't have a social life (and as a programming geek, I know what that means :D), socializing with others, can be exhausting.

When you have a flat tire on your car - as another example - you find it tedious to change that tire, until you do it for a few times. Then, it becomes easy; at some point, you jump from "what should I do?!?" to "there's nothing to it".

It's not about you becoming stronger - though, there is that. It's about becoming somehow "wiser about things".

It's about going forward in such a way that all your options keep staying open and more than that, you open to even more possibilities.

If nothing else, it's a bit of food for thought.

Have a nice evening, everybody.

Wednesday, August 22

the wonder of MS Paint

Whoa! It's like ... anyone knows where I could get this graphic editing tool?

Please?

Tuesday, July 17

new favorite word

My previously favorite word no longer counts. My new favorite word is Cruft. Here's the best possible description, as seen in a comment on /.:
Cruftiness is the quality of having cruft. Cruft is the stuff that accumulates on code over time. Cruft has no odor, but it stinks. Cruft has no mass, but it weighs the code down. Cruft can't be seen, but it's ugly. Cruft cannot be young, it's always old. Cruft can't be deliberately added, it only appears when you're not looking. Cruft can't be explained to managers, except through awkward car analogies. They still won't get it because managers drive well-maintained elegant foreign cars like BMW's, which gather no cruft. Programmers understand, because their Fords and Chevys are practically built of cruft. Harley motorcycles should have cruft, but noise dissipates cruft. Cruft is mysterious.

Cruft is never present on code which hasn't had enough work. Cruft only appears on code which has been worked too long, by too many people.


(S., thanks for the email).