TextMate

November 30, 2005

BBEdit, vim, Emacs

Until recently I thought BBEdit was the best possible editor for the MacOS. I probably was influenced by my previous experiences (I used BBEdit years ago, before I switched to *nix). In fact I was amazed how simple was to do complex queries (in fact it was easier than vim from a psychological point of view), and project wise or directory based queries are as easy to do as standard queries.

Moreover in that period I needed a tool with good HTML and site management capabilities (and BBEdit has them both, and in fact for HTML projects is in my opinion still the best tool out there) and it is fully scriptable (and files can be partially generated by scripts, which is good).

These qualities made me forget about other things I looked for in an editor. That is to say: I want to use an editor that lets me code faster, helping me in manipulating text. BBEdit has extensive facilities, but most of them are really not immediate.

Basic text handling is somewhat poor (for example indenting). It’s best feature (glossaries) are not really useful for what I do most of the time. Still I liked it. It was better integrated with the mac os environment. This is the main reason because I did not use Emacs or vim: I am quite skilled in both of them, for I had to work on machines where one of them was not available. Mac ports exist, but are somehow less “native”.

Vim does open a single window per application, and if you want to have more windows you need more applications, and that is not extremely practical.

If you are interested in an Emacs version that is MacOS friendly, read this.

The key question is: it is that important to behave like a Mac application? What does it mean being a “mac application”?

You’ll be reading a more complete post on Mac philosophy on this blog later on. Right at the moment we can summarize: Apple philosophy is to keep things simple but not simplistic.

About the Apple UI there are lots of documents out there, but it’s no mystery that nor vim nor Emacs follow Apple guidelines (and that makes perfectly sense, it’s not a critic).

An application well integrated in its environment, works better. It’s easier to use, you already know shortcuts, drag n drop behaves as you expect, integration with LauncherServices is present and so on. That is the reason I preferred BBEdit to vim or Emacs (now both Emacs and vim have some integration features, but do not have them all), even if I was skilled in both and they both have features BBEdit hasn’t (and the features BBEdit has they haven’t are not so important to me, in fact I found out my use of BBEdit is really basic: I’m a lot more into TextMate).

TextMate

When I tried TextMate for the first time, I thrashed it almost immediately. It missed CVS and SVN support (and BBEdit SVN support was wonderful — unfortunately I’m not using SVN anymore). In fact I did not give it any chance. I had just bought BBEdit and was learning it’s more complex features.

I wasn’t ready for TextMate.

Text editing

Text editing is smooth. You can use all Cocoa standard shortcuts (that borrow heavily from Emacs) and Cocoa spell-checking is builtin. Fonts are smooth and beautifull.

TextMate does “smart typing”, that is to say when you type a bracket or quotes, both the opening and the closing bracket or quote is typed and the cursor is placed inside. The first times I met with this feature (and it was with some IDE’s) I hated it. Now I can’t live without and I do a lot of mistakes when typing with something that does not support it. In fact I think it makes you save a lot of typing and a lot of time.

TextMate has other smart features, for example you can edit more than one line in a time (and the key are rectangular selections of any kind)

Folding is supported, that is to say you can look at the code with the level of granularity you chose. You can collapse blocks you are not interested and expand them when you are not interested.

Another useful feature is auto-completion: it’s not as sophisticated as some C++ and Java IDEs since it’s based on typed text rather than “possible code”, but it has the advantage it applies to non-static languages too (Perl, Python, Ruby).

In fact TextMate can be used to code in C or C++, Java (even if it lacks some useful features that are necessary do to poor language design), Perl, Python, Ruby and a lot of other languages. Moreover it can be easily extended to other languages.

The most interesting thing is that you can define macros that put inserted text in more than one place. The most simple example is with HTML. Most HTML tags are kind of <tag></tag>. With TextMate you just say insert a “tag pair” (that is to say <p></p>, since p is one of the most used tags), and the editor will insert the text, selecting the first “p”. Then you can type the correct tag (for example h3, or maybe table) and the closing tag will be updated accordingly. This makes a lot less typing: and can be used in clever ways for other languages too (think about latex environments).

The last (well not really) interesting feature is macro completions. You can easily define macros and type only a few characters, then tab complete. This is great.

The beautiful thing is that TextMate can be easily extended. There is a lot of support for many languages (unfortunately not for Prolog, and that is something I miss a lot, even not enough to write my own bundle).

Organize code

TextMate has some basic project managing. It can be further extended with TmCodeBrowser.

It’s improving in this field. It can also import projects from Apple’s XCode.

To be completely sincere to manage this website I prefer BBEdit (however most “text editing” and maintenance is made with TextEdit). I miss an FTP browser (I know this is controversial and the “current” solution is to have TM interact with FTP clients, I also do this, but sometimes it is just to good to directly edit).

And remember that TextMate + BBEdit do cost less than Dreamweaver (and produce better code — of course this does not apply if you write code manually with DW, but then I think TextMate text editing facilities are just superior).


Mac Emacs (Aquamac)

November 30, 2005

Unfortunately enough this seems to be going to be a “text editor blog”, with BBEdit post, this post and almost finished post about TextMate.

In fact I already told Mac Emacs versions do seem to miss something, and this is the reason why I chose to buy TextMate (a cheap 39$ affair).

Right now I’m just trying to convince myself that TextMate was worth 39$. I mean, probably if I discovered Aquamacs earlier (the first version I tried long time ago were “new”) I would not have bought it (even if once I was a vi fan — and in fact I still am, for some things).

And not because I don’t like TextMate anymore… just because Aquamacs is free and is free software [ which is good ].

Aquamacs

You can download Aquamacs here.

I told how weird do seem Emacs key bindings on MacOS (in fact they look odd everywhere outside Emacs). Some of them are pretty standard, readline supports them by default (and so most command line applications do) and Cocoa controls also do (that is to say they work in any text editor.).

I’m talking about ctrl-a, ctrl-e, etc.

But you are never going to convince a Mac user he has to ctrl-x ctrl-s to save or ctrl-x ctrl-c to quit.

So Aquamacs uses standard Apple bindings. Command-C, Command-S, Command-Q. This way the command-key is not free. And meta has to be mapped on alt.

So if you have a not english keyboard, you may not be able to type square brackets, for example. And braces. In fact this is a serious design flaw, in my opinion.

In fact I found out the flaw was in the user (me) who did not read the wiki. Meta, Apple/Command, Option keys are fully configurable.

In my opinion if you are going to use Emacs, you expect to find Emacs key bindings. The more logical choice is to use Command for meta, and leave alt as alt, to enter braces (and on american keyboards accents.).

Still you may want (and it is my choice) to allow Command-key to be used for mac key shortcuts, but you want to pass option/alt to the OS (so that you can use [] and other characters). You do this with

(setq mac-pass-option-to-system t)

.

Should you have troubles with copying and pasting text from outside into Emacs or vice-versa, try to use

(setq x-select-enable-clipboard t)

In fact you can find lot of informations in the wiki:
Emacs wiki.

The nice thing was that you are able to use standard MacOS open and close dialogs, that is really nice.

But there is something even more beautiful features:

  • Frames are mapped to windows: when you open a file, you open it in a new window, and you can manage windows with Expose. Probably a hardcore Emacs user is not going to like this (but probably he will prefer some other more standard version).
  • Fonts: fonts are nice. It uses MacOS fonts and locales and unicode characters appear to be handled correctly (not sure for full unicode, for european >7bit characters, it works).
  • You also have transparencies. It’s just a little nifty feature.
  • Cut and paste works as you expected, so does deleting a text selection. This is probably one of the single features users that are not used to Emacs are going to hate, even if it makes perfectly sense in the Emacs way of working.
  • Last but not least, you can associate files with Emacs, so you can use it as a standard MacOS text editor to open files double clicking in the finder.

I’m really impressed by this project: in fact it’s enought Emacs you want get too confused if you are used to Emacs, but it’s also enought Mac you can use it along your other Mac applications, without having to rethink basic commands (if you don’t want to).

Right at the moment it is in my opinion the best Emacs version for the MacOS out there.


Io e Netbeans

November 20, 2005

Per una serie di casi del destino anche il sottoscritto si è ritrovato costretto ad usare Java. D’altra parte era stato ampiamente previsto, e la cosa non mi ha trovato più impreparato di un tot.

Complessivamente mi sono assuefatto all’ambiente. Il linguaggio è abbastanza “stupido”, ma va bene nella maggioranza dei casi. Certo, avendo dei templates decorosi… la mia opinione sui generics di Java non è certo delle migliori.

D’altra parte la piattaforma regge bene. La libreria continua ad essere ben fatta. E complessivamente anche Swing mi piace abbastanza. Non è troppo cervellotica da usare (mai usato Carbon?), e si comporta entro grandi linee bene ovunque (mai provato Tk?).

Proprio riguardo alle GUI si possono apprezzare alcune dei maggiori vantaggi di NetBeans 5. Il nuovo GUI editor, Matisse, è semplicemente favoloso. Sicuramente sta al pari con InterfaceBuilder dei Developer’s Tools di Apple, forse è anche migliore.

Matisse

Qui un piccolo screenshot di Matisse. Fare le GUI è davvero semplice.
Si tratta di disporre i controlli all’interno della finestra. NetBeans automaticamente mostra delle guide, per disporli “bene” in relazione fra loro.
Per esempio NetBeans tenterà di suggerire che tre campi di testo uno sopra l’altro finiranno allineati e così via.

L’utente poi potrà specificare quali controlli vorrà che si ridimensionino con la finestra e quali no, e quali vuole che siano “ancorati” ad un qualche bordo, in modo che si spostino con la finestra stessa.

Anche se la spiegazione non rende l’idea, posso garantire che il tutto ora è davvero molto semplice e comodo, niente a che fare con il famoso GridBagLayout (in pratica i Layout sono caduti in disuso, a quanto pare)

Tutte le proprietà dei controlli, codice da inserire prima o dopo per inizializzarli e la scelta del costruttore possono essere fatti direttamente da Matisse.

Refactoring

Finalmente anche NetBeans ha aggiunto le funzioni di refactoring che eravamo soliti trovare su Eclipse. Non c’è molto da aggiungere, fanno il loro dovere, punto e basta.

Altro…

Altro non ho ancora avuto il modo di provarlo. In effetti Java continua a non essere il mio ambiente naturale, per quanto mi stia sforzando di affrontarlo con mente aperta. Sono in grado di vederne i pregi, ma mi balzano anche all’occhio i difetti.

Allo stesso modo devo dire di NetBeans che è davvero un bel progetto, un IDE ben fatto di cui più lo si usa, più si scoprono utili funzioni e interessanti scorciatoie.

Purtroppo è terribilmente mastodontico. Sembra di manovrare un camion, è lento e spesso si ferma a pensare davvero troppo. A causa dei limiti di Java diventa sostanzialmente impensabile scrivere il codice semplicemente con un editor di testi, ma la tentazione è forte. TextMate è maneggevole, snello, veloce. Risponde ai miei comandi.

NetBeans ha i suoi tempi. La cosa che continua a torturarmi è… ma abbiamo davvero bisogno di una piattaforma che ha bisogno di centinaia di megabyte di IDE per sopperire alle sue mancanze? A quanto pare si, ma c’è qualcosa che mi suggerisce all’orecchio che mi hanno fregato (con questo Matisse è comunque fantastico, e mi piacerebbe averlo a disposizione anche per le mie GUI Python, Swing è una libreria abbastanza buona — specie adesso che usa il freeform layoyt ).

Attenzione!

Se usate NetBeans 5 sotto MacOS, ricordatevi di copiare il jar org-jdesktop-layout.jar dentro il classpath standard della vostra versione di Java, altrimenti non riuscirete a combinare nulla.


Symlink droplet

November 17, 2005

Although this can be easily adapted to work without MacPython’s
EasyDialogs, it would not make sense.
This is meant to be an applet used to easily create unix symlinks from
the Finder.

import os
import sys

import EasyDialogs
from EasyDialogs import Message

exec_path = sys.argv[0]
if len(sys.argv) < 2:
    Message('Drag a file onto SymlinkDroplet')
else:
    i = exec_path.find("SymlinkDroplet")
    dir_path = exec_path[:i]
    for file_ in sys.argv[1:]:
       if os.path.isfile(file_):
           linkname = os.path.basename(file_)
           os.symlink(file_, os.path.join(dir_path, linkname))


Parsing “lines”

November 16, 2005
00001: #define BUFFER_SIZE 1<<16
00002: #define ARR_SIZE 1<<16
00003: 
00004: void parse_args(char *buffer, char** args, 
00005:                 size_t args_size, size_t *nargs)
00006: {
00007:     char *buf_args[args_size]; /* You need C99 */
00008:     char **cp;
00009:     char *wbuf;
00010:     size_t i, j;
00011:     
00012:     wbuf=buffer;
00013:     buf_args[0]=buffer; 
00014:     args[0] =buffer;
00015:     
00016:     for(cp=buf_args; (*cp=strsep(&wbuf, " \n\t")) != NULL ;){
00017:         if ((*cp != '') && (++cp >= &buf_args[args_size]))
00018:             break;
00019:     }
00020:     
00021:     for (j=i=0; buf_args[i]!=NULL; i++){
00022:         if(strlen(buf_args[i])>0)
00023:             args[j++]=buf_args[i];
00024:     }
00025:     
00026:     *nargs=j;
00027:     args[j]=NULL;
00028: }

To understand how it works, I suggest to take a look at the man page of strsep. As you can see this is a lot of code to perform a very simple task.
Compare with

        argv = cmd.split()

The C++ version is a bit longer (because I split some lines) than the C version, but there is less “magic”. In fact if you read the code it is very easy to understand how it works. Moreovere there is no hassle with memory allocation.

00001: #include <iostream>
00002: #include <string>
00003: #include <vector>
00004: #include <algorithm>
00005: #include <iterator>
00006: #include <cstdlib>
00007: #include <cerrno>
00008: #include <exception>
00009: #include <unistd.h>
00010: 
00011: using std::string;
00012: 
00013: void 
00014: split_string(const std::string s, 
00015:              const std::string sep, 
00016:              std::vector<std::string> &components)
00017: {
00018:     typedef std::pair<size_t, size_t> Range;
00019:     size_t pos, old_pos;
00020:     Range trange;
00021:     std::vector<Range> ranges;
00022:     trange.first = 0;
00023:     trange.second = s.find(sep, trange.first);
00024:     if (trange.second == string::npos){
00025:         trange.second = s.length();
00026:     }        
00027:     ranges.push_back(trange);
00028:     while(trange.second < s.length()){
00029:         trange.first = trange.second + 1;
00030:         trange.second = s.find(sep, trange.first);
00031:         if (trange.second == string::npos){
00032:             trange.second = s.length();
00033:         }
00034:         ranges.push_back(trange);
00035:     }
00036:     
00037:     for (int i = 0; i < ranges.size() ; ++i){
00038:        components.push_back(s.substr(ranges[i].first, 
00039:                 ranges[i].second - ranges[i].first) );
00040:     }
00041: }
00042: 
00043: class InvalidCommandLine : public std::exception {
00044: 
00045: };

Unixify

November 16, 2005

This is a small script to convert text files from old MacOS CR based format to MacOS X Unix LS based format

This is a very simple Python script I use to convert text files with Mac line endings in text files with unix line endings. It needs some Python libraries available only on MacOS, but it should not be difficult to make a portable version (without GUI or with another GUI toolkit).
This one is meant to be packaged as an applet with BuildApplet, included in MacPython.


# Enrico Franchi 2004(c)
# unixify.py
# Version 0.5
# This is released under Python Software Foundation Licence Version 2

# Usage: pythonw unixify.py
# You may build an applet.

import re
import os
import sys
import MacOS
import EasyDialogs

extensions= [".txt", ".text", ".mac", ".unix", ".dos", 
        ".c", ".cc", ".cpp", ".cxx", ".hxx", ".h", ".hpp",
        ".htm", ".html", ".xml", ".xsl", ".sgml", ".tex"
        ".py", ".pl", ".cgi", ".m", ".mm", ".texinfo" ]

def convert(file):
    """Creates a backup copy and converts \
    fileending from everything to unix"""
    try:
        fh = open(file,"r")
    except IOError:
        EasyDialogs.Message("Could not open %s" % file)
        return
    str = fh.read()
    fh.close()

    if str.find("\r") != -1:
        oldfile = file + ".old"
        try:
            fho = open(oldfile, "w+")
            fho.write(str)
            fho.close()
            del str
        except IOError:
            EasyDialogs.Message\
                 ("Could not open %s for writing" % oldfile)
        try:
            fh = open(file,"rU")
            str = fh.read()
            fh.close()
        except IOError:
            EasyDialogs.Message("Could not open %s" % file)
            os.remove(oldfile)
            return
        try:
            fh = open(file,"w")
            fh.write(str)
            fh.close()
        except IOError:
            EasyDialogs.Message\
                 ("Could not open %s for writing" % file)
            try:
                os.remove(file)
            except:
                pass
            os.rename(oldfile, file)
            return

def loop(dir, extensions):
    """ this calls convert recursively on all text files """
    goodFileList = []
    fileList = [ os.path.normcase(f) for f in os.listdir(dir) \
                 if os.path.isfile(os.path.join(dir,f))]
    dirList = [ os.path.normcase(d) for d in os.listdir(dir) \
                if os.path.isdir(os.path.join(dir,d))]
    for f in fileList:
        # now we are evalutating wether 
        # the file is likely to be ascii
        (root, ext) = os.path.splitext(f)
        try:
            is_there = extensions.index(ext.lower())

            if is_there:
                goodFileList.append(f)
                continue
        except ValueError:
            pass
        # checks TYPE
        #(cr, ty) = MacOS.GetCreatorAndType(f)
        #if ty=="ttxt":
        #   goodFileList.append(f)
    if len(goodFileList) > 0:
        (path, name) = os.path.split(dir)
        print "\n%s: " % name
    for f in goodFileList:
        convert(f)
    for d in dirList:
        loop(os.path.join(dir,d), extensions)

if __name__ == "__main__":
    if len(sys.argv) == 1:
        args = EasyDialogs.GetArgv()
    else:
        args = sys.argv[1:]
    for file in args:
        if os.path.isfile(os.path.normcase(file)):
            convert(file)
        elif os.path.isdir(os.path.normcase(file)):
            if EasyDialogs.AskYesNoCancel\
                     ("Do you want to process all files\
                     inside the directory %s?" % file,
                    cancel=""):
                loop(file, extensions)


Python Virtual Cron

November 6, 2005

Problem

Suppose you do want perform a certain task once in a while, and suppose you don’t have cron nor nothing similar. Suppose for example your environment is a CGI script and that it keeps almost no memory.

Of course every-time you run the script you can check if you are in a specified time range, and in that case you do run your action. In my opinion there is another way to accomplish this task: you can run the action based on a probability. This grants that the action is run in average m times every n uses (where m/n is the probability), and quite easily you can define an action that is run every-time (with n==m).

This approach in my opinion is better suited for a script that is called quite often and with action that need to be performed relatively often too, so that defining fine grained time ranges in which to run the supplementary actions becomes tedious.

Think for example in some database cleaning and consolidating. It’s more “use-based” than time-based.

Singleton

I did not write this particular code, but I use it in my vcron class. It’s freely available on the web, so no problem putting it here. As far as I know the author is Michele Simionato.

class Singleton(type):
    def __init__(cls,name,bases,dic):
        super(Singleton,cls).__init__(name,bases,dic)
        cls.instance=None
    def __call__(cls,*args,**kw):
        if cls.instance is None:
            cls.instance=super(Singleton,cls).__call__(*args,**kw)
        return cls.instance

Put it in a file named “singleton.py” in the Python path or in the same directory as vcron.py.

vcron

Put the following code in a file called vcron.py. The license is a BSD based one, so you can do with it what you want, provided you give credit. I think it would be correct to give credit to Michele, too. You may check if you must on ActiveState Python Cookbook website, where I found this code. I am giving credit.

#  Copyright (c) 2004, Enrico Franchi
#  All rights reserved.
#  
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are
#  met:
#  1) Redistributions of source code must retain the above copyright
#  notice, this list of conditions and the following disclaimer.
#  2) Redistributions in binary form must reproduce the above copyright
#  notice, this list of conditions and the following disclaimer in the
#  documentation and/or other materials provided with the distribution.
#  3) Neither my name nor the names of its contributors may be used to
#  endorse or promote products derived from this software without specific
#  prior written permission.
#  
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
#  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
#  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
#  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
#  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
#  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


import singleton
from random import randint

class VirtualCron:
    """VirtualCron emulates cron.

    This is used in environments where processes are not persistent and do
    not want to have memory of what happened, still they need to perform some
    tasks once in a while.
    It implements the singleton pattern, just one instance is allowed.

    Always use named arguments. Order of parameters is not guaranteed to remain the same.
    """
    __metaclass__ = singleton.Singleton

    FUNCTION = 0
    PARAMS = 1
    CHANCE = 2
    ACTIVE = 3
    RUNONCE = 4

    def __init__(self):
        self._actions = {}
    def add_action(self, name, function, params={}, chance=1000, active=True, runonce=False):
        """Adds selected action to VirtualCron object.

name is the name you use to access a record.
function is the function that will be called
params is the dictionary **kwds that will be passed to the function.
So it will be called function(**kwds) will be called.
1/ chance is the chance the function will be executed.
active means that the function is active
If runonce is True, then after being executed the function will be deactivated.
        """
        self._actions[name]=[function, params, chance, active, runonce]
    def del_action(self, name):
        del self._actions[name]
    def set_params(self, name, chance=None, runonce=None):
        if chance is not None:
            self._actions[name][self.CHANCE] = chance
        if runonce is not None:
            self._actions[name][self.RUNONCE] = runonce
    def activate(self, name):
        self._actions[name][self.ACTIVE]=True
    def deactivate(self, name):
        self._actions[name][self.ACTIVE]=False
    def change_named_parameter(self, name, arg_name, newvalue):
        rule = self._actions[name]
        kwds = rule[self.PARAMS]
        kwds[arg_name] = newvalue
    def run(self):
        for name, rule in self._actions.items():
            function, kwds, chance, active, runonce = rule
            if active and randint(1, chance)==1:
                function(**kwds)
                if runonce==True:
                    self.deactivate(name)

# some tests

## def printFactory(arg):
##     def _():
##         print arg
##     return _

## def printFactory2(arg):
##     def _(string):
##         print arg, string
##     return _

## v = VirtualCron()
## v.add_action('one', printFactory("one"))
## v.add_action('two', printFactory2("two"), params={'string':'kowabunga'})
## v.add_action('freq', printFactory("frequentissima"), chance=100)
## v.add_action('unique', printFactory("=====UNICA!======"), runonce=True)

## for i in xrange(10000):
##     v.run()

CEOs should follow NBA and make geeks wear real clothes | The Register

November 5, 2005

Really funny


Google Paying for Firefox Installs

November 5, 2005

Google Paying for Firefox Installs: “slashkitty writes ‘Google updated their AdSense service to pay publishers for referrals. What’s interesting is that now they pay publishers $1 US for each FireFox download with the Google Toolbar installed. Is this the bump that Firefox needs to boost downloads? Will Google be able to pay the millions for all the downloads?’

(Via Slashdot.)

Google ha modificato adsense in modo che gli inserzionisti prendono 1 dollarozzo per ogni download di firefox con la google-bar.

Apparentemente sembra che sempre più i colossi si stiano coalizzando tutti contro i vari monopoli che Microsoft ha accumulato lungo gli anni per sostanziale mancanza di competitori seri (ovvero sufficientemente completi).

Fino a poco tempo fa le grandi ditte dell’informatica erano vicino a Microsoft oppure se in contrasto, separate fra loro da motivi ideologici o di mercato. Adesso sempre più sembra che si stia costituendo un fronte comune.


Sempre sui brevetti software

November 5, 2005

Qui il link un pochino piĆ¹ approfondito

Flickr Photo

No ai brevetti software!

technorati tags: ,