Friday, January 23, 2009

Encoding video sizes

Video compression usually works on square blocks of pixels. These can have sizes of 8x8 or 16x16 or other powers of 2. H264 (AVC) for example, uses macroblocks of 16x16.

So when compressing video, it helps if the frame size is such that both width and height are evenly divisible by 16 or at least by 8. This is why videos encoded for the web or for portable video players are often not exactly in a 16/9 aspect ratio. The PSP's screen, for example, is 480 x 272 even though true 16/9 would require 480 x 270. But 270 is not divisible by 16 whereas 272 is. Youtube uses 640x360, which is true 16/9 and divisible by 8. If you use other sizes, FFmpeg will print a message like

width or height not divisible by 16 (480x270), compression will suffer.

So what are the sizes which are both the right aspect ratio and nicely divisible by 16 or by 8? This little Perl script will let us know:

#!/usr/bin/perl

my $aspect_width  = 16;
my $aspect_height = 9;
my $max_height = 1200;
my @dividers = (16, 8);

for my $divider (@dividers) {

print "$aspect_width/$aspect_height with both ",
     "width and height divisible by $divider :\n\n";

# try sizes up to Full HD
for my $i (1..$max_height/$aspect_height) {
   my $h = $aspect_height * $i;
   unless ($h % $divider) {
       my $w = $aspect_width * $i;
       printf "$aspect_width/$aspect_height divisible by %2d : %4d x %4d\n",
              $divider, $w, $h;
   }
}

print "\n";
}

For 16/9, this gives, among others, numbers like

16/9 divisible by 16 :  256 x  144
16/9 divisible by 16 :  512 x  288
16/9 divisible by 16 :  768 x  432
16/9 divisible by 16 : 1024 x  576
16/9 divisible by 16 : 1280 x  720

For sizes divisible by 8, you obviously have all of the above, plus (among others):

16/9 divisible by  8 :  384 x  216
16/9 divisible by  8 :  640 x  360
16/9 divisible by  8 :  896 x  504
16/9 divisible by  8 : 1920 x 1080
16/9 divisible by  8 : 2048 x 1152

Labels: , , , , ,

Wednesday, January 16, 2008

Simple password management

To easily manage all your passwords, you don't need any freeware/shareware/crapware/malware/whateverware. If you are running Windows, all you need is 2 batch files, each containing a single line.

As a bonus, you can get some very simple security-through-obscurity by using a little known feature of the NTFS file system called "Alternate Data Streams". The security is not great, but the obscurity feels like a cool hack. And it's still better than having passwords.txt on your desktop, or Post-its on your monitor. (Of course, you can also skip the coolness and combine these handy batch files with the excellent TrueCrypt for really strong encryption at the expense of a minimum of additional hassle).

  1. Create a file containing anything (or nothing). Let's call it x, and put it in our profile folder (C:\Documents adn Settings\username\)
  2. Create a batch file (let's call it password-add.bat) with one line:
    @ECHO %* >> "%USERPROFILE%\x:passwords"
  3. Create a second batch file (for example password.bat) also with one line :
    @FIND /I "%1" < "%USERPROFILE%\x:passwords"
  4. Copy these two files to some directory in your path (like C:\Windows or C:\Windows\System32)
To add your new Google user name and password, open a Command Prompt window, and type:

password-add "Google: mystupidname@gmail.com pass: ul7ra-secr37"

To retrieve that password once you have forgotten it, type anything like

password Google
or
password stupid
or
password @gmail.com
etc.

To add some obscurity, call the batch files something else (and shorter so you don't have to type so much): like newp.bat and p.bat.

To add even more obscurity, copy some small .dll file in c:\Windows\System32 to a new name like msp32.dll, and in the batch files replace "%USERPROFILE%\x:passwords" with "c:\Windows\System32\msp32.dll".

To add real security, get TrueCrypt, and put the file on a TrueCrypt volume. (Don't forget to correct the 2 batch files).

Important: This only works on NTFS partitions. If you move your file to a FAT32 partition or send it by email or FTP, all your passwords are lost forever. If your backups are done to an external FAT32 disk, you won't have a backup either. You can move the file around as much as want, providing that it always stays on NTFS partitions. If you copy over a network, the server also needs to be Windows (not Samba).

Labels: , , , , , , ,

Monday, September 10, 2007

importing Eudora address books into Thunderbird

The solution to "Can't import empty address book".

The import of Eudora address books into Thunderbird (as of version 2.0.0.6) requires a registry entry pointing to the Eudora data folder.

Put the text below into a text file, adjust the path, save as "EudoraAB-to-TB.reg" or whatever.reg and double-click it. The import should now work, whether you have Eudora installed or not.
Windows Registry Editor Version 5.00

; Adjust C:\\Path\\To\\Folder below so that it points to your Eudora data folder
; with your nndbase.txt and nndbase.toc files and/or your Nickname sub-folder

[HKEY_CURRENT_USER\Software\Qualcomm\Eudora\CommandLine]
"Current"="Anything C:\\Path\\To\\Folder Anything"

The path is normally the path to your Eudora data directory, which contains your .mbx files, your eudora.ini, etc. However, all you really need to have there are your address book files: either nndbase.txt and nndbase.toc, or a sub-folder called "Nicknames" which would have other .txt and .toc address book files, or both the nndbase files and files in the Nicknames folder.

Without this registry entry, you get the standard Windows braindead browse for folder dialog. But whatever you select in that dialog, Thunderbird ignores it and the import just fails with a stupid and misleading message saying "Can't import empty address book". With the registry setting it works, even if the folder contains nothing but the address book files and you don't even have Eudora installed on the machine.

Update: This problem has since been reported in Mozilla bugs (bug 395808). However, the similar registry entry provided didn't work for me when I tried it because it lacked the third word after the path. You really need 3 words in that registry value. The first and the last can be anything, and the path in the middle must of course be correct.

If it still doesn't work, see also bug 368634 (Unable to import if Eudora is using '.nnt' format).

Labels: , , , ,

Thursday, August 16, 2007

Embedding documentation in shell script

A simple hack to embed Perl style POD documentation into shell scripts, so that these bash (or dash) scripts can carry their documentation with them, and updates are done at the same time the script is updated.

The trick is to have the Perl POD section in a bash "Here-Document", right after the null command (no-op) ":".

  • Start with : <<=cut
  • Write your POD-formatted man page
  • That's it. Your POD ends with =cut, which has also been defined as the end of the shell Here-doc

Your script can then be processed with all the usual Perl tools like perldoc, or perl2html, and you can even generate real man pages with pod2man.

As an example, here is the podtest.sh script :

#!/bin/dash

echo This is a plain shell script
echo Followed by POD documentation

: <<'=cut'
=pod

=head1 NAME

   podtest.sh - Example shell script with embedded POD documentation

=head1 SYNOPSIS

   : <<=cut
   =pod
   Your POD documentation
   =cut

=head1 DESCRIPTION

This is a simple way to embed POD documentation into shell scripts.
The documentation can then be shown with C<perldoc scriptname="">,
or even converted to a manual page with C<pod2man>.

=head1 SEE ALSO

L<perlpod|pod::perlpod>, L<perlpodspec|pod::perlpodspec>

=head1 LICENSE

Such a wonderful and unusual idea is certainly protected by many patents...

Or maybe not...

=head1 AUTHOR

B<I> had this great idea...

No rights Reserved

=cut

To add this podtest.sh to your man pages:

pod2man podtest.sh >/usr/local/share/man/man1/podtest.sh.1

Use pod2html podtest.sh to have HTML output.

Labels: , , , , ,

Tuesday, December 12, 2006

Postgres: allow dynamic IPs in pg_hba.conf

If you don't want to open access to PostgreSQL to any external IP, but occasionally do need access from a particular address, you can edit pg_hba.conf. I needed this often enough that I wrote a script to make the procedure easier. What i now do is:
  • Open an ssh connection to the server
  • Start the pg_open script
The script takes care of getting your current IP from the ssh connection, adding it to pg_hba.conf and telling postgres to reload pg_hba.conf

Since the end of pg_hba.conf is usually to deny all access, the line to allow your IP needs te be before that. So you need some specific text in the file to let the script know where to put your IP, and that specific text must be configured in the script.

Here is the pg_open script:
#!/usr/bin/perl

# open postgres access to current ssh client

my $conf_file = '/etc/postgresql/pg_hba.conf';
my $reload_cmd = '/etc/init.d/postgresql reload';
# the following 2 markers must exist in the file
my $mark_start = '#---- Temporary access for dynamic IP ----';
my $mark_end = '#---- End Temporary access for dynamic IP ----';

my $VERSION = 0.1;

use strict;

my ($ip, $confblock_new);

## find the IP, using the SSH_CLIENT environment variable
if ( $ENV{SSH_CLIENT} =~ /((?:\d{1,3}\.){3}\d{1,3})/ ) {
$ip = $1;
$confblock_new = "\n$mark_start\n"
. "#Allow current SSH connection (" . scalar(localtime) . " user=$ENV{USER})\n"
. join("\t", "host", "all", "all", $ip, "255.255.255.255", "trust")
. "\n$mark_end\n";
}
else {
die "Could not find ip of current SSH user (\$SSH_CLIENT=$ENV{SSH_CLIENT})\n";
}

## make a backup of the file
system("cp", $conf_file, "$conf_file.bak") == 0
or die "Could not create backup of $conf_file ($!)\n";

## Get file, and replace the relevant block
open(F, "$conf_file")
or die "Cannot read $conf_file: $!\n";
undef $/;
my $conf = <F>;
close F;

$conf =~ s/\n$mark_start\n.*?\n$mark_end\n/$confblock_new/s
or die "Could not find markers in file\n";

## Write new temp file then rename it to the right file name
open(NEW, ">$conf_file.new")
or die "Could not create new file $conf_file.new ($!)\n";
print NEW $conf
or die "Could not write new file $conf_file.new ($!)\n";
close NEW;

rename("$conf_file.new", $conf_file)
or die "Could not rename $conf_file.new to $conf_file ($!)\n";

## Finally signal postmaster to reload the new file
system($reload_cmd) == 0
or die "Could not reload postgresql ($!)\n";
The limitations are that, as it is, it will only allow one such connection at a time. Also, there should be a timeout or some cron job which removes the allowed IP after some time.

Labels:

Tuesday, July 11, 2006

Perl in Postgres tips

I recently needed to convert the type of a Postgres text field from varchar to date. There would probably have been a pure SQL solution, but since using Perl in Postgres had been so easy last time I tried, it seemed like the obvious and simple solution to use Perl again.

However, while writing the text2date function below, I lost quite some time with a few little traps of plperl. So here is my short list of tips for using PL/Perl in PostgreSQL:
  • All examples suggest using $$ as a delimiter in function definitions, but that only works in recent versions of PostgreSQL. With 7.4 - the version currently in Debian stable (Sarge) - you cannot use $$ and need single quotes instead.
  • When using single quotes, backslashes must be escaped (doubled) in the Perl code. You will get strange results if you forgot that.
  • To see the code as Postgres actually stored it:
    SELECT prosrc FROM pg_proc WHERE proname = 'YourPerlFunction';
  • warn doesn't work. Use elog(level, msg) instead. The documentation for elog says:

elog(level, msg)
Emit a log or error message. Possible levels are DEBUG, LOG, INFO, NOTICE, WARNING, and ERROR.
ERROR raises an error condition; if this is not trapped by the surrounding Perl code, the error propagates out to the calling query, causing the current transaction or subtransaction to be aborted. This is effectively the same as the Perl die command.
Finally, here is the text2date function I used:
CREATE OR REPLACE FUNCTION text2date(text) RETURNS "date" AS '
$_=shift;
my ($day, $month, $year) = /^\\s* (\\d{1,2}) [^\\d]+ (\\d{1,2}) [^\\d]+ (\\d{4}) .* /x;
if ( !($day && $month && $year) ) {
elog(WARNING, "No date found in $_\\n");
return undef;
}
elsif ($day > 31 || $month > 12 || length($year) != 4) {
elog(WARNING, "Wrong date: $year-$month-$day\\n");
return undef;
}
return "$year-$month-$day";
' LANGUAGE 'plperl' IMMUTABLE STRICT;
And this is how it was stored in Postgres:
# SELECT prosrc FROM pg_proc WHERE proname = 'text2date';

$_=shift;
my ($day, $month, $year) = /^\s* (\d{1,2}) [^\d]+ (\d{1,2}) [^\d]+ (\d{4}) .* /x;
if ( !($day && $month && $year) ) {
elog(WARNING, "No date found in $_\n");
return undef;
}
elsif ($day > 31 || $month > 12 || length($year) != 4) {
elog(WARNING, "Wrong date: $year-$month-$day\n");
return undef;
}
return "$year-$month-$day";

Obviously, this is for European style dates.

Labels:

Tuesday, July 04, 2006

Liens email dans les pages web

J'ai eu un peu de mal à retrouver ce lien, alors je suppose que ça vaut la peine de l'inclure dans un petit article de blog. (Finalement, mon attention a été détournée par de nombreux à-côtés; les liens pertinents sont dans les tout derniers paragraphes).

Bien qu'on puisse considérer que la lutte anti-spam s'apparente à un ré-arrangement des transats sur le pont du Titanic, il faut quand même encore mettre des liens email dans des pages web. Là, bien sûr, ils sont immédiatement collectés pour servir à nous inonder de "pourriels", comme disent les français.

Comment avoir des liens "mailto:" cliquables, sans que tous les robots de spam en profitent? Les trucs simples comme "user [at] example [dot] com", ou les plus compréhensibles mais très peu pratiques images montrant le texte de l'adresse ne sont pas cliquables. N'essayez même pas les vieux trucs avec les entités HTML (&amp;#64; pour "@" etc.); ça fait longtemps que ça ne marche plus. Il n'existe plus guère de robot qui s'y prenne les pieds.

Ce qui marche bien est le javascript, que les robots collecteurs d'adresse ne décodent pas; du moins pas encore, à ma connaissance. Mais la plupart de ces astuces javascript qu'on trouve sur le web utilisent un chiffrage antique (littéralement), et même pas sa version ROT13, mais un décalage de 1. Etant donné qu'une simple expression Perl comme s/(.)/chr(ord($1)-1)/eg; permet de le décoder, je doute que ça résiste longtemps. Tiens pendant que j'y suis, voilà un script Perl complet qui suffit pour extraire d'une page web les emails cryptés de cette façon (quel que soit le décalage utilisé):
use LWP::Simple;
my $url = shift or die "Usage: $0 URL\n";
my $html = get($url);

while ( $html =~ /href=['"]javascript:.*?\(['"](.*?)['"]\)/g ) {
my $crypted = $1;
foreach my $i (-25..25) {
my $try = $crypted;
$try =~ s/(.)/chr(ord($1)+$i)/eg;
if ($try =~ /@([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,6}/) {
print "$try (i=$i)\n";
}
}
}

Bref, je cherchais un peu mieux que ça.

Un certain Jim à qui on essayait de vendre un de ces scripts minables a poussé la solution javascript usuelle (autre exemple ici) à un niveau de cryptage un peu plus actuel. Avec Email Protector, il fournit une page où on peut crypter son adresse email. Il n'y a plus qu'à copier le code fourni aux bons endroits, après avoir séparé du code le lien mailto: lui-même et les fonctions javascript aux noms "obfusqués" et pittoresques. Certains trouveront cette solution exagérée, mais justement, je trouve aussi un certain charme là où d'autres ne verront que du "over-engineered".

Update: Finalement, j'utilise ça régulièrement, et mes suis même adapté le cryptage des adresses en un petit script Perl (disponible ici en .pl et en .bat), pour pouvoir l'utiliser directement à la ligne de commande et obtenir la partie à coller dans le HTML:

#mailcrypt 11 31 user@example.com
<!-- crypted email link -->
<a href="javascript:var N=341,D=43;bid('127 300 95 104 4 95 263 202 318 7 147 95 271 99 133 318',N,D)"
title="Click to send email"
class="mail"<Email</a>

Au préalable, il faut évidemment ajouter
<script type="text/javascript" src="/js/emailProtector.js"></script> dans la page, et sauver l'excellent script de Jim Tucek.

Labels: , , , , , ,

Tuesday, April 11, 2006

cannot cast type money

In PostgreSQL, there used to be a type money which is now deprecated. It is suggested to replace it with numeric(p,s), but how do you convert your existing money columns?

Someone complained that no migration path is provided, but that conversation ended up in some useless nitpicking about whether it had ever been possible in some previous version.

There is a MONEY Conversion Module patch, but that requires you to recompile PostgreSQL and will only work with some specific versions of Postgres.

Well, if you inadvertently used that stupid money type and need to convert, maybe the simple solution below will work for you. It takes advantage of the fact that you can use Perl in PostgreSQL functions.

The following steps have worked for me:

  1. Connect to your database
    #psql -U postgres my_database

  2. Add the plperl language
    CREATE TRUSTED PROCEDURAL LANGUAGE 'plperl' HANDLER plperl_call_handler;

  3. Create the function
    CREATE OR REPLACE FUNCTION money2numeric(money)
    RETURNS "numeric" AS
    '$_=shift; s/[^0-9\.\-]//g; return 1 * $_;'
    LANGUAGE 'plperl' IMMUTABLE STRICT;
    (in case you are not familiar with Perl, here is what it does: assign the argument to the $_ variable; remove any characters which are not a digit, a dot or a minus sign; return the result multiplied by 1 to ensure it is numeric)

  4. Add a numeric column
    ALTER TABLE my_table ADD COLUMN my_new_col numeric(15,2);

  5. Update your data
    UPDATE my_table SET my_new_col = money2numeric(my_old_col);

  6. Drop the old column
    ALTER TABLE my_table DROP COLUMN my_old_col;

  7. Rename the new column to the old name
    ALTER TABLE my_table RENAME COLUMN my_old_col TO my_new_col;

That's it...

Labels:

Thursday, April 06, 2006

en_CH locale

English may not be one of the official Swiss languages (yet?), but it is certainly useful in a computer system. I don't want server logs and error messages in French or German or Italian (or Romantsch?). These things are easiest to understand and get help about in English. But I don't want dates in the confusing US "MM/DD/YY" format either, or times in AM/PM. Most importantly: I want sorting to work correctly with accented letters, and I want Perl to understand accented letters for \w, the uc(), lc() functions, etc.

So I made my own en_CH locale. Tested it in Debian stable (3.1 "Sarge"), and it seems to do what I expected. If you want to try it out, the steps are below. And it is quite easy to edit language_COUNTRY files to suit your needs.
# cp /usr/share/i18n/SUPPORTED /usr/share/i18n/SUPPORTED.orig
# echo -e "en_CH ISO-8859-1\nen_CH.UTF-8 UTF-8" | sort - /usr/share/i18n/SUPPORTED.orig >/usr/share/i18n/SUPPORTED
# wget -O /usr/share/i18n/locales/en_CH http://alma.ch/linux/en_CH
# echo "en_CH ISO-8859-1" >>/etc/locale.gen
# echo "en_CH.UTF-8 UTF-8" >>/etc/locale.gen
# dpkg-reconfigure locales
You may also need to edit your /etc/environment file and/or /etc/default/locale if they have a left-over LANGUAGE= line.
At your next login, your locale should be set to en_CH, and these little tests should work:
$ echo -e "é\ne\nA\nà\nE" |sort
$ perl -Mlocale -e 'print "Uppercase accented é and à: ", uc("éà\n")'
$ echo -e "é\ne\nA\nà\nE" |perl -Mlocale -ne 'while (/(\w+)/g) {print "$1\n"}'
(Note that for Perl, you need use locale;).

Update: For Ubuntu 6.10 ("Edgy"), see also this post in this ubuntu forum thread!

Labels: , , , , , ,

Thursday, March 30, 2006

Simple multi-language web site

There are many ways to direct users of a multi lingual web site to the pages
in their language. But the quick search I made didn't return solutions which
I liked, so I did my own. I'm sure the same method has been used many times,
but it's sometimes faster to re-invent a little wheel than to go through zillions
of Google search results.

I had to work on a small site with about 20 pages, in which every language
is in it's own subdirectory (www.example.com/fr/, www.example.com/en/, etc.)
and the file names remain the same.

This simple structure made it pretty easy, and the solution makes it trivial
to add another language.

(For a lot of background about languages and a different solution involving the standard "content language negotiation", see Dan's Web Tips: Languages).

The way I choose works like this:

  • One line in every page calls a PHP script to display the language menu.
  • A cookie holds the visitor's preferred language.
  • Javascript updates the cookie when the visitor switches languages.
  • The main index page redirects visitors to the correct language, using:
    1. either the cookie set in a previous visit,
    2. or the browsers preferred language,
    3. or a pre-configured default language.
  • Finally, a few CSS rules take care of the menu's look

The redirector script

I used a little Perl script on www.example.com/index.cgi. It first looks for
a cookie with the user's preferred language. If there is no cookie (usually
because this is a first-time visit), it uses the HTTP_ACCEPT_LANGUAGE
header sent by the browser to find the best guess. If that doesn't work either,
it uses a default language. Finally, it redirects the user to the selected language,
while also setting the cookie for the next visit.

I probably could have done the index page in PHP, but I didn't know PHP at
all when I started so it was easier for me in Perl. (If someone feels like sharing
a PHP translation, you are welcome to post it here as a comment).

#!/usr/bin/perl

# Redirect to correct language version of the site.
# Use cookie if present. Otherwise, parse the HTTP_ACCEPT_LANGUAGE header

# Author: Milivoj Ivkovic "mi\x40alma.ch"

use CGI qw(:cgi);
my $VERSION = 0.3;

my %available_languages = (
fr => 'fr/',
de => 'de/',
en => 'en/',
default => 'fr/',
);
my $wanted_language = 'default';

my $q = new CGI;

# try cookie
my $cookie_lang = $q->cookie("lang");
if ( exists $available_languages{$cookie_lang} ) {
$wanted_language = $cookie_lang;
}
else { # try HTTP_ACCEPT_LANGUAGE
# Get languages from HTTP_ACCEPT_LANGUAGE, (ignore local variants like en_US, en_GB, etc.).
# Don't bother removing duplicates
my @langs = map { substr($_, 0, 2) } split(/,/, $ENV{HTTP_ACCEPT_LANGUAGE});
foreach my $l (@langs) {
if (exists $available_languages{$l}) {
$wanted_language = $l;
last;
}
}
}

# if nothing worked, we still have the default in $wanted_language which was set at the beginning

# fix URL
my $url = $q->url(-path_info=>1);

# (some servers give us a url ending with 2 "/", so we need "/+" in the
# regex below instead of just "/".
# Otherwise we may end up with "http://example.com//en/" which doesn't look nice.)
$url =~ s|/+[^/]*$|/$available_languages{$wanted_language}|;

# redirect, and also set the cookie for next time
print $q->redirect ( -uri => $url,
-cookie => cookie( -name=>"lang",
-value=>$wanted_language,
-path=>"/",
-expires=>"+1y",
),
);

The javascript setLang function

Anytime the user switches the language, this small javascript function is called
to update a cookie, so the preference is saved between sessions.

function setLang(l) {
// Called from links which switch the language.
// Sets cookie with "lang=fr" or other language code, valid for 1 year, on all the site
var expire = new Date();
expire.setTime(expire.getTime() + 3600000*24*365);
document.cookie = 'lang='+l+";expires="+expire.toGMTString()+";path=/";
}

The language menu

Since all the pages happened to be in PHP, I did the language menu in PHP,
and added an include() to every page.

<?php include("../common/lang-selector.php") ?>

The PHP lang-selector.php script

This is the PHP script which displays the language menu.

<!-- php lang selector -->
<div id="lang-selector">
<?php
$langs = array(
"fr" => "Français",
"de" => "Deutsch",
"en" => "English",
);
$self=$_SERVER['PHP_SELF'];
$pattern = "{^/[^\/]+/}i";
$links = array();
foreach ($langs as $l => $lang) {
$is_current = ! ( strpos($self, "/$l/") === false );
$url = preg_replace($pattern, "/$l/", $self);
$link = "<a href=\"$url\" class=\"lang-"
. ($is_current ? "current\">" : "other\" onclick=\"setLang('$l')\">")
. " $lang </a> ";
array_push($links, $link);
}
echo " " . implode(" | ", $links) . "\n";
?>
</div>
<!-- end php lang selector -->

The finishing touch: CSS

The look of the language menu is done with CSS. This is what I used for that
particular site:

#lang-selector {
font-size: x-small;
text-align: left;
white-space: nowrap;
padding-bottom: 1em;
color: #CCCCCC;
}

a.lang-current,
a.lang-current:visited,
a.lang-current:hover
{
color: #CCCCCC;
text-decoration: underline;
}

a.lang-other,
a.lang-other:visited
{
color: #333333;
text-decoration: none;
}

a.lang-other:hover
{
text-decoration: underline;
background: #F7EBDB;
}

@media print {
#lang-selector {
display: none;
}
}

By the way, the site for which this was first set up is http://lesouffledudesert.com/.

When we added English to the site, the steps were:

  • Copy the /fr directory to a new /en directory
  • Add "en" => "English", to lang-selector.php
  • Add en => 'en/', to index.cgi
  • And of course, translate all pages, which was the real work and where
    this little multi-language system doesn't help...

Labels: , , , , ,

Sunday, May 08, 2005

modulo 10

Calcul du chiffre-clé d'après le modulo 10, récursif

Pour le calcul du chiffre de contrôle dans des données des banques ou de la poste, un algorithme nommé "modulo 10 récursif" est parfois utilisé. La documentation des banques que j'ai vue présente un graphique démentiel et incompréhensible pour ce calcul. C'est utilisé dans des machins comme les bulletins "ESR", "BESR", "BVR", les numéros de compte etc.

Voilà une version toute simple en Perl, adaptée à partir des versions C et Basic de cette page.

sub mod10 {
# take number without control digit, return control digit
my @nums = split //, shift;
my @T = (0, 9, 4, 6, 8, 2, 7, 1, 3, 5);
my $n = 0;

for (@nums) {
$n = $T[ ($n + $_) % 10 ];
}
return (10 - $n) % 10;
}

C'était finalement tout simple. Les gens des banques doivent être des gens bizarres...

Labels:

Tuesday, February 22, 2005

Restaurer les barres par défaut d'Eudora

La gestion des différentes barres d'Eudora est parfois étrange. Si elles disparaissent ou ont des réglages bizarres, la simple ligne de Perl ci-dessous, certes un peu brutale, permet de supprimer tous les réglages qui s'y rapportent et de récupérer les réglages par défaut.

L'intérêt est qu'on ne perd pas ses autres réglages Eudora comme les comptes email, signatures, etc.

Après avoir fermé Eudora:

perl -i.bak -ne "if(/^\[.*Bar/){$inbar=1} elsif (/^\[/){$inbar=0}; print unless (/Bar/ or $inbar)" eudora.ini

Labels:

Tuesday, December 21, 2004

Perl, touch and silly hit counters

While looking at how to set a file's time stamp in Perl (Perl's `touch`), I stumbled across this in the perl faq:

I still don't get locking. I just want to increment the number in the file. How can I do this?
Didn't anyone ever tell you web-page hit counters were useless?
They don't count number of hits, they're a waste of time, and they serve only to stroke the writer's vanity. It's better to pick a random number; they're more realistic.

Anyway, this is what you can do if you can't help yourself.

[8 lines of boring and ugly code]

Here's a much better web-page hit counter:

$hits = int( (time() - 850_000_000) / rand(1_000) );

If the count doesn't impress your friends, then the code might.
:-)

Well, it did amuse me. So much indeed, that I'm actually about to add a page counter to http://alma.ch/perl.

Oh, and by the way: `touch` in Perl is utime in case that is what actually brought you here.

Update (since this little entry seems to rank strangely high for "Perl touch" searches):

As POS points out in a comment below, utime will not create the file if needed, as `touch` would. To update-or-create, try something like utime(time, time, "file") or ( open(F, ">file") && close F )

For a real `touch` clone, you probably need a little function, or even the big one from the Perl Power Tools project.

Labels:

Thursday, November 25, 2004

Perl 6 nightmare

Perl afficionados will no doubt be frightened by the cover of the new Perl 6 book:

The new Perl 6 book cover

(found on http://bleaklow.com/blog/archive/000018.html)

Labels: