diff options
author | Sébastien Dailly <sebastien@chimrod.com> | 2020-11-30 22:56:26 +0100 |
---|---|---|
committer | Sébastien Dailly <sebastien@chimrod.com> | 2020-12-03 21:35:35 +0100 |
commit | 9b77ec15e5beeff3f57f845be883416d2a68b84d (patch) | |
tree | 796f2aecfcdf5012ce611fac22b85fa481bf63de | |
parent | 1c02ae819eee2d28040804d58872ceb4c003ee1f (diff) |
New article on rst & Latex. Changed theme
77 files changed, 2561 insertions, 1256 deletions
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b9cd9d4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Flex"] + path = Flex + url = https://github.com/alexandrevicenzi/Flex.git diff --git a/Flex b/Flex new file mode 160000 +Subproject bbf47fe35473774d8a41478523cf4d3b21268e3 @@ -3,20 +3,20 @@ PELICANOPTS= BASEDIR=$(CURDIR) INPUTDIR=$(BASEDIR)/content -OUTPUTDIR=$(BASEDIR)/output +OUTPUTDIR=$(BASEDIR)/output/ CONFFILE=$(BASEDIR)/pelicanconf.py PUBLISHCONF=$(BASEDIR)/publishconf.py -FTP_HOST=localhost -FTP_USER=anonymous -FTP_TARGET_DIR=/ +#FTP_HOST=localhost +#FTP_USER=anonymous +#FTP_TARGET_DIR=/ -SSH_HOST=localhost +SSH_HOST=vps785951 SSH_PORT=22 -SSH_USER=root -SSH_TARGET_DIR=/var/www +SSH_USER=sdailly +SSH_TARGET_DIR=/srv/apache/blog-chimrod/ -DROPBOX_DIR=~/Dropbox/Public/ +#DROPBOX_DIR=~/Dropbox/Public/ help: @echo 'Makefile for a pelican Web site ' @@ -63,20 +63,20 @@ stopserver: publish: $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS) -ssh_upload: publish - scp -P $(SSH_PORT) -r $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) +#ssh_upload: publish +# scp -P $(SSH_PORT) -r $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) rsync_upload: publish rsync -e "ssh -p $(SSH_PORT)" -P -rvz --delete $(OUTPUTDIR) $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) -dropbox_upload: publish - cp -r $(OUTPUTDIR)/* $(DROPBOX_DIR) - -ftp_upload: publish - lftp ftp://$(FTP_USER)@$(FTP_HOST) -e "mirror -R $(OUTPUTDIR) $(FTP_TARGET_DIR) ; quit" - -github: publish - ghp-import $(OUTPUTDIR) - git push origin gh-pages +#dropbox_upload: publish +# cp -r $(OUTPUTDIR)/* $(DROPBOX_DIR) +# +#ftp_upload: publish +# lftp ftp://$(FTP_USER)@$(FTP_HOST) -e "mirror -R $(OUTPUTDIR) $(FTP_TARGET_DIR) ; quit" +# +#github: publish +# ghp-import $(OUTPUTDIR) +# git push origin gh-pages .PHONY: html help clean regenerate serve devserver publish ssh_upload rsync_upload dropbox_upload ftp_upload github diff --git a/content/Informatique/2009-03-11-wiimote.rst b/content/Informatique/2009-03-11-wiimote.en.rst_ index 325d4b7..3604257 100644 --- a/content/Informatique/2009-03-11-wiimote.rst +++ b/content/Informatique/2009-03-11-wiimote.en.rst_ @@ -9,6 +9,7 @@ Controling the wiimote (I) :tags: Programmation, Wiimote, Python :slug: controling-the-wiimote-i :lang: en +:translation: true Creating the plugin for wminput diff --git a/content/Informatique/2009-03-11-wiimote_fr.rst b/content/Informatique/2009-03-11-wiimote.fr.rst index 2c80584..2c80584 100644 --- a/content/Informatique/2009-03-11-wiimote_fr.rst +++ b/content/Informatique/2009-03-11-wiimote.fr.rst diff --git a/content/Informatique/2009-10-18-backup.rst b/content/Informatique/2009-10-18-backup.rst deleted file mode 100644 index 291f187..0000000 --- a/content/Informatique/2009-10-18-backup.rst +++ /dev/null @@ -1,82 +0,0 @@ -.. -*- mode: rst -*- -.. -*- coding: utf-8 -*- - -Un système de backup automatique -################################ - -:date: 2009-10-18 -:tags: Libre, Administration -:summary: |summary| - -.. |summary| replace:: - On le sait tous, il faut faire des sauvegardes de manière régulière. On le - sait également, pour que celles-ci se fassent sur le long terme, il faut - que celles-ci se fassent de manière automatique, et sur un autre support - que le PC que l'on souhaite sauvegarder. Le problème qui se pose est le - suivant : comment concilier ces deux conditions sur un PC de bureau (ne - disposant donc pas d'une série de serveur allumés en permanences et prêt à - recevoir nos sauvegardes en continu…) ? - -|summary| - -Pour répondre à tout cela, nous allons mettre en place un système de backup sur -disque dur externe, qui se lancera à chaque fois que notre disque sera monté. À -chaque fois que le disque dur sera allumé, la sauvegarde s'enclenchera. Cela ne -garantit pas, bien sûr que les sauvegardes se feront à un intervalle régulier, -mais cela garantit au moins que nous n'aurons pas à nous en soucier. Pour cela -nous allons utiliser les outils qui sont disponibles sous un environnement -Linux : rsync et hal. Cet article nous présente une base pour faire notre -sauvegarde `Une sauvegarde améliorée avec rsync -<http://informatique-et-liberte.tuxfamily.org/2009/05/10/une-sauvegarde-amelioree-avec-rsync/>`_. -Nous allons juste devoir le modifier un petit peu pour répondre à un problème -qui arrive souvent avec les périphériques USB : selon que d'autres -périphériques sont déjà montés ou non, nous ne savons pas dans quel répertoire -nous allons nous trouve. Il va donc falloir mettre en place une ligne pour -récupérer le répertoire dans lequel nous sommes. Il ne nous reste plus qu'à -trouver le moyen de l'éxécuter automatiquement pour cela nous allons utiliser -halevt. Le script est disponible ici_. - -.. _ici: {filename}/resources/backup.sh - -Comme son nom l'indique, halevt est un gestionnaire d'évènements pour hal. Hal -est un gestionnaire d'évènement matériel sous Linux; il envoie des informations -à chaque fois que des informations sont envoyées depuis les composants. Cela -permet de détecter le branchement d'un périphérique USB et de le monter sur le -bureau (et qui nous simplifie grandement la vie aujourd'hui !!!). Halevt est -un démon à l'écoute des informations qui nous sont envoyées par hal, et -d'activer des actions en conséquence : par exemple pour lancer l'antivirus sur -la clef usb, reconfigurer le mappage du clavier en fonction de la marque que -l'on branche etc. Pour notre part, nous allons nous contenter de lancer un -script (celui du backup mentionné plus haut). - -Pour commencer nous allons devoir identifier le lecteur à mettre sous -surveillance : inutile de se baser sur les noms de montage habituels (/dev/sda -par exemple) en effet en fonction des périphériques déjà branchés nous -n'allons pas obtenir la même configuration. Nous allons utiliser les point de -montage défini dans /dev/disk/by-uuid qui permet d'obtenir l'identifiant -de notre disque (et qui sera repris par la suite dans la configuration de hal -). Une fois que nous avons relevé quel est le disque concerné, il faut mettre -en place une entrée pour notre évènement dans la configuration de halevt : - -:: - - <halevt:Device match="hal.block.device & hal.block.is_volume = true & hal.volume.uuid = fd20536f-7b80-4a80-8c3d-b5bebe8fb484"> - <halevt:Property name="hal.volume.is_mounted"> - <halevt:Action value="true" exec="bash $hal.volume.mount_point$/chemin/script/backup-ssh.sh"/> - </halevt:Property> - </halevt:Device> - -Cela si halevt est exécuté avec les droits de l'utilisateur lançant le -backup. Si on le fait tourner en démon, il faut trouver une autre -solution (sur mon poste j'ai utilisé sudo, mais on peut très bien se -baser sur le sticky bit pour donner les droits au script). De même, -dans la configuration mise en place, le script se trouve sur le disque -de stockage (de manière à pouvoir le lancer à la main si le démon n'est -pas disponible), cela peut être adapté en fonction de chacun… - -Dans le cas d'une configuration multi-utilisateur, je pense qu'il est -nécessaire de passer par un script qui lance les différentes sauvegardes sous -le bon groupe de l'utilisateur à chaque fois. (Ce qui en plus permet d'éviter -le problème du sudo) mais je n'ai pas eu besoin d'aller jusque-là pour -l'instant ! À vous d'adapter ce que je vous propose en fonction de votre -configuration ! diff --git a/content/Informatique/2010-06-11-vala.rst b/content/Informatique/2010-06-11-vala.rst deleted file mode 100644 index 05aeeb7..0000000 --- a/content/Informatique/2010-06-11-vala.rst +++ /dev/null @@ -1,165 +0,0 @@ -.. -*- mode: rst -*- -.. -*- coding: utf-8 -*- - -Présentation de Vala -#################### - -:date: 2010-06-11 -:tags: Libre, Programmation - -.. default-role:: literal - -J'ai découvert une présentation du langage dans GLMF n°127 qui -l'utilisait pour se brancher sur le pare-feu. Cela ayant aiguisé ma -curiosité, j'ai cherché à en savoir davantage sur le langage. Le langage -C m'a toujours paru difficile à aborder. Non pas au niveau de sa -syntaxe, mais au sujet de l'accès à sa documentation et aux librairies -disponibles. Pour moi qui suis habitué à Java ou à Python, j'ai regardé -Vala comme un moyen de mettre un pied dans le C. Je vais essayer de -présenter ma manière de le voir, après avoir fait une petite plongée -dedans… - -Présentation -~~~~~~~~~~~~ - -Le langage Vala a été créé par les développeurs de Gnome pour qu'ils -puissent disposer d'un langage de haut niveau. Gnome ayant eu pendant -quelque temps un pied (dansant) dans mono, Vala en est très inspiré. -(Je ne connais pas mono et ne peux donc pas lancer de comparatif, je -vous renvoie donc à celui présenté par Gnome `par rapport à -C# <http://live.gnome.org/Vala/QuickIntroForCSharpProgrammers>`_, et -pour `Java <http://live.gnome.org/Vala/ValaForJavaProgrammers>`_). Le -langage est ensuite compilé en C, et GCC est utilisé pour la compilation -d'un exécutable. On est donc dans un langage intermédiaire, qui reste -très proche du C comme nous allons le voir par la suite. - -Analyse -~~~~~~~ - -Langage objet haut niveau --------------------------- - -Vala est tout d'abord un langage haut niveau : -on y retrouve des *interface* (pour pouvoir moduler le code facilement), des -*delegate* (qui permettent de définir un type de fonction pour un callback), -une gestion *événementielle* (gérer le déclenchement de plusieurs méthodes dans -le code par un appel unique) … On dispose donc d'un langage objet assez riche -pour éviter d'avoir à passer du temps sur des détails et se concentrer sur le -programme et son déroulement. Il est pensé objet et l'on retrouve vite ses -marques, tout comme l'on sent qu'il est assez facile d'« abstraire » le code -pour faire une application qui n'en reste pas un simple script… Je n'ai pas -envie de détailler la syntaxe du langage et tout ce qu'il intègre car vous -pourrez trouver tout cela en ligne. Je vous renvoie au `tutoriel -<http://live.gnome.org/Vala/Tutorial>`_ qui explique la structure du langage en -détail, qui présentent des exemples détaillés. - -La GLib -------- - -Le langage est basé sur -la GLib, la librairie standard utilisé dans les applications GTK. La plupart -des types primitifs (`int` …) sont donc en réalités des types issus de cette -librairie (`gint` …). On accède ainsi à l'ensemble des méthodes de la GLib, -diffusées sous formes de classes objets et bien documentées (la `valadoc -<http://valadoc.org/index.html>`_). Cela rejoint l'histoire de Vala puisque La -GLib est tout utilisée par les développeurs Gnome (on constate d'ailleurs que -la plupart des composants Gnome ont une entrée dans cette Valadoc). Il ne faut -cependant pas croire que l'on dispose avec cette bibliothèque standard d'un -ensemble de routines aussi riche que dans le package standard d'un Java ou d'un -Python : la GLib est avant tout destinée à des développeurs C, et beaucoup des -méthodes standard du C ne sont pas disponibles. Pour parer cela, Vala propose -une classe POSIX[LIEN] proposant les méthodes standard du C, mais celle-ci -n'est pas exhaustive et il nous arrive souvent de tomber sur une fonction qui -ne nous est pas accessible via le langage haut niveau qu'est Vala. - -Les Bindings ------------- - -Pour répondre à cela, propose la possibilité de mettre en place un binding vers -une libraire C de manière native. (Normal me direz-vous, le code en sortie de -Vala est du C !) On peut donc très facilement utiliser n'importe quelle -libraire existante. Vala étant un langage objet, il devient donc possible -d'utiliser les librairies standard au sein d'un code objet de haut niveau. Cela -ne demande que quelques lignes, demandant au minimum le fichier dans lequel se -trouve la définition de la méthode, et sa signature. Exemple du binding -définissant la méthode `execl` (issue du fichier -`/usr/share/vala/vapi/posix.vapi`) : - -:: - - [CCode (cheader_filename = "unistd.h")] - public int execl (string path, params string[] arg); - -et voici la signature de la méthode C correspondante : - -:: - - extern int execl (__const char *__path, __const char *__arg, ...) __THROW __nonnull ((1)); - -Les types primitifs de la GLib étant basés sur les types primitifs C -correspondants, il n'y a pas de problème de cast dans la plupart de cas. -Toutefois, Vala utilise des `char\*` pour définir ses string : il est parfois -nécessaire de mettre en place faire un cast quand une méthode nous un `char[]` : -même dans le code haut niveau, le C n'est jamais loin… C'est souvent -frustrant de devoir mettre en place un binding pour une fonction disponible en -standard dans la libc. Je pense qu'il manque la possibilité d'inclure -directement du code C dans le code vala, tout comme il est possible directement -de l'assembleur dans du C. Cela permettrait un confort dans l'utilisation qui -n'est pas disponible actuellement. À noter qu'un outil permet de mettre en -place ces bindings pour les composants basés sur `GObjet` (Gnome toujours…). De -plus de nombreux bindings sont disponibles pour les composants Gnome. -Attention, cela n'empêchera pas d'avoir à installer les headers C -correspondants ! - -Les profils ------------- - -Il est possible d'utiliser des profils de compilation. Cette option encore -expérimentale a été mise en place pour répondre à des réclamations de la part -des utilisateurs qui voulaient programmer en Vala sans avoir de dépendances -envers la GLib. Ainsi, il est possible d'appeler le compilateur avec la syntaxe -suivante : - -.. code-block:: console - - $ valac --profile posix - -ce qui a pour conséquence de réduire le jeu de bibliothèque par défaut à -celles disponibles dans la classe `POSIX`. Cela permet de limiter les -dépendances du programmes, mais beaucoup de types standard de Vala -deviennent de ce fait indisponible; on sent bien que le langage n'a pas -été pensé pour ça. - -Conclusion -~~~~~~~~~~ - -Pour résumer, je dirais que Vala correspond à ce qu'il annonce : un -langage haut niveau pour faciliter la création d'applications Gnome. Dès -que l'on cherche à sortir de ce cadre, on se retrouve confronté à des -limitations (qui ne sont bien sûr pas insurmontable) : - -- Pour ceux qui ne cherche pas à travailler sur du code Gnome, - embarquement de bibliothèques qui ne nous intéressent pas forcément. - (Bien que sur ce point, la GLib est plutôt standard sur les PCs - ayant X d'installés) -- Pour ceux qui cherchent un langage de haut niveau, le C est encore - trop présent entre les lignes pour pouvoir l'oublier complètement. - D'un autre côté, l'intégration du code C n'est pas toujours évidente - puisqu'elle oblige à déclarer des bindings pour des fonctions - standards. -- Mais surtout, le fait que l'ensemble des fonctions de la libc ne - soient pas encore disponibles oblige à mettre le doigt dans les - bindings régulièrement. - -Le langage est bien sûr encore amené à évoluer, et les points que je cite ici -ne sont pas pour autant rédhibitoires. D'un point de vue totalement subjectif -Je trouve le langage simple mais sans trop de saveur. Ça reste un langage objet -qui n'est bien sûr pas révolutionnaire; on sent qu'il ne fait que reprendre les -concepts des langages qui l'ont inspiré, mais le produit est cohérent, et -d'après les benchs génère un code plutôt optimisé (en rapport avec le temps que -l'on pourrait passer à produire le même code en C). Après, je ne prends pas le -même plaisir à programmer en Vala qu'en python (j'avais bien dit que j'étais -subjectif ^^). Cela dit, pour moi, qui ne suis pas un développeur Gnome, je -suis plus intéressé par la possibilité d'avoir un langage haut niveau qui reste -très proche du C, avec les performances qui vont avec; si le prix pour cela est -une dépendance vers la GLib, ça reste un coût sommes toutes assez faible. diff --git a/content/Informatique/2013-04-20-gueule1.rst b/content/Informatique/2013-04-20-gueule1.rst deleted file mode 100644 index 6fa3f04..0000000 --- a/content/Informatique/2013-04-20-gueule1.rst +++ /dev/null @@ -1,65 +0,0 @@ -.. -*- mode: rst -*- -.. -*- coding: utf-8 -*- - -Essai raté d'un plugin gedit -############################ - -:date: 20/04/2013 -:tags: Programmation, Libre, Humeur, gtk -:logo: /images/gedit.png - -.. default-role:: literal - -En relisant mes articles, j'ai décidé d'utiliser languageTool_ pour vérifier la -grammaire avant de les publier. Puis en regardant l'outil, j'ai voulu voir s'il -existait un plugin gedit permettant de l'intégrer dans l'édition d'un texte -(j'utilise vim, je sais que ma compagne utilise gedit pour écrire ses textes). - -Titivullus_ est disponible sur sourceforge, et est facile à installer (même si -l'auteur n'a pas compris comment fonctionnait SVN), par contre l'utilisation -n'est pas des plus pratiques : il faut charger la console d'erreur de gedit -(qui ne peut pas être réduite), et le plugin n'est pas configurable. - -.. _languageTool: http://www.languagetool.org/ -.. _Titivullus: http://sourceforge.net/projects/titivullus/ - -Heureusement, c'est du python ! -=============================== - -Le code n'est pas compliqué à comprendre (il n'est pas parfait, mais à au moins -le mérite d'être là), et j'ai commencé à le modifier : première étape, appeler -languagetool en ligne de commande plutôt que de lancer le serveur web. -Deuxième étape, surligner dans gedit la ligne en erreur pour éviter d'avoir à -passer par la console pour savoir ce qui ne va pas ; on se plonge dans -l'édition de `plugin sous gedit`_, et là, c'est le drame ! - -.. _`plugin sous gedit`: https://live.gnome.org/Gedit/PythonPluginHowTo - -Devant du code C -================ - -Le binding python est assez limité, il faut remonter dans l'API C pour trouver -ce que l'on cherche, et là : - -* erreur 404 dans l'API (par exemple les liens de l'« Object Hierarchy » de - GtkSourceView_) -* Des messages d'erreurs abscons dûs à l'introspection (par exemple en cas de - mauvais objet passé en paramètre) -* Des dépendances de plus en plus importantes à chaque nouvelle étape (`gedit`, - `gtk`, puis pango_\ …) - -Plus j'avance, et plus j'ai l'impression que le chemin qui reste à parcourir -est grand ! Qu'il est loin le temps où je m'étais penché sur Vala_ pour -découvrir ! J'ai finalement laissé tomber, en ayant l'impression d'avoir perdu -mon temps : mettre en place un plugin sous gedit est définitivement trop -compliqué pour pouvoir être réalisé facilement, sans se plonger dans le code -GTK et le connaître sur le bout des doigts. - -Dommage, pour une fois que j'avais envie de me plonger dans le code et -d'avancer… Je comprends que le greffon Titivullus soit resté à l'état de proff -of concept et que personne ne l'ai repris : cette petite aventure m'a fait -passer l'envie de me plonger dans du dev gnome ! - -.. _GtkSourceView: https://developer.gnome.org/gtksourceview/stable/GtkSourceView.html -.. _pango: https://developer.gnome.org/pango/stable/pango-Text-Attributes.html -.. _Vala: {filename}../Informatique/2010-06-11-vala.rst diff --git a/content/Informatique/2013-05-08-fcron2cron_en.rst b/content/Informatique/2013-05-08-fcron2cron_en.rst deleted file mode 100644 index 0e51388..0000000 --- a/content/Informatique/2013-05-08-fcron2cron_en.rst +++ /dev/null @@ -1,64 +0,0 @@ -.. -*- mode: rst -*- -.. -*- coding: utf-8 -*- - -================== -From fcron to cron -================== - -:date: 2013-05-08 -:tags: Libre, Administration -:summary: |summary| -:logo: /images/time/time_75.jpg -:lang: en -:slug: fcron-to-cron -:translation: true -:status: draft - -.. default-role:: literal - - -.. figure:: {filename}/images/time/time.jpg - :figwidth: 150 - :figclass: floatleft - :alt: Pavement - - Image : `Toni Verdú Carbó`_ (creativecommons_) - -.. _Toni Verdú Carbó: http://www.flickr.com/photos/tonivc/2283676770/in/photostream/ -.. _creativecommons: http://creativecommons.org/licenses/by-nc-nd/2.0/deed.fr - -|summary| - -.. |summary| replace:: - Debian Wheezy is now out, and this new realease removed some packages, - including fcron_. - Il s'agit du - gestionnaire de tâche planifiées que j'utilisais jusqu'alors. Ce petit - guide a pour vocation de vous expliquer comment migrer la configuration. - -.. _fcron: http://fcron.free.fr/ - -Fcron avait l'avantage de pouvoir gérer l'arrêt de la machine, et lancer les -tâches en attente lors du redémarrage. Quand on s'auto-héberge, cela permet de -prendre en charge les coupures inopinées, et se passer du couple cron/anacron. -Debian a fait le choix de préférer ce dernier couple plutôt que fcron, voici -donc un petit guide pour passer de l'un a l'autre. - -Migration ? -=========== - -Pour commencer, pas de panique, le paquet est supprimé des dépôts, mais reste -parfaitement fonctionnel sur votre machine. Vous n'avez donc pas besoin de vous -précipiter, tout va continuer de tourner tout seul comme auparavant. Vous -pouvez même choisir de ne pas migrer et continuer à l'utiliser. Toutefois, -si des failles de sécurités sont trouvées sur le programme, aucune mise à jour -ne sera effectuée : c'est à vous de vous tenir informé du développement de -l'application et de faire l'installation. - -Voici un programme python qui se charge de faire la migration d'un fcrontab -vers un crontab. Il ne fait que générer les fichiers de configuration, et -n'enregistre rien sur le système. Le script va lire les fcrontab enregistrées -dans le répertoire `/var/spool/fcron/` et génère en sortie les crontab -correspondantes pour chacun de ces utilisateurs. - -Les instructions spécifiques sont converties, dans la mesure du possible. diff --git a/content/Informatique/2013-05-09-fcron2cron.rst b/content/Informatique/2013-05-09-fcron2cron.rst deleted file mode 100644 index f7652dc..0000000 --- a/content/Informatique/2013-05-09-fcron2cron.rst +++ /dev/null @@ -1,124 +0,0 @@ -.. -*- mode: rst -*- -.. -*- coding: utf-8 -*- - -=============== -De fcron à cron -=============== - -:date: 2013-05-09 -:tags: Libre, Python, Administration -:summary: |summary| -:logo: /images/time/time_75.jpg -:lang: fr -:slug: fcron-to-cron - -.. default-role:: literal - - -.. figure:: {filename}/images/time/time.jpg - :figwidth: 150 - :figclass: floatleft - :alt: Pavement - - Image : `Toni Verdú Carbó`_ (creativecommons_) - -.. _Toni Verdú Carbó: http://www.flickr.com/photos/tonivc/2283676770/in/photostream/ -.. _creativecommons: http://creativecommons.org/licenses/by-nc-nd/2.0/deed.fr - -|summary| - -.. |summary| replace:: - Debian Wheezy est sortie, et cette nouvelle version a fait un peu de ménage - dans les paquets disponibles. Parmi ceux-ci, fcron_ a disparu. Il s'agit du - gestionnaire de tâche planifiées que j'utilisais jusqu'alors. Ce petit - guide a pour vocation de vous expliquer comment migrer la configuration. - -.. _fcron: http://fcron.free.fr/ - -Fcron avait l'avantage de pouvoir gérer l'arrêt de la machine, et lancer les -tâches en attente lors du redémarrage. Quand on s'auto-héberge, cela permet de -prendre en charge les coupures inopinées, et se passer du couple cron/anacron. -Debian a fait le choix de préférer ce dernier couple plutôt que fcron, voici -donc un petit guide pour passer de l'un a l'autre. - -Migration ? -=========== - -Pour commencer, pas de panique, le paquet est supprimé des dépôts, mais reste -parfaitement fonctionnel sur votre machine. Vous n'avez donc pas besoin de vous -précipiter, tout va continuer de tourner tout seul comme auparavant. Vous -pouvez même choisir de ne pas migrer et continuer à l'utiliser. Toutefois, -si des failles de sécurités sont trouvées sur le programme, aucune mise à jour -ne sera effectuée : c'est à vous de vous tenir informé du développement de -l'application et de faire l'installation. - -La transformation -================= - -Voici un `script python`_ qui se charge de faire la migration d'un fcrontab -vers un crontab. Il ne fait que générer les fichiers de configuration, et -n'enregistre rien sur le système. Le script va lire les fcrontab enregistrées -dans le répertoire `/var/spool/fcron/` et génère en sortie les crontab -correspondantes pour chacun de ces utilisateurs. - -.. _script python: {filename}/resources/fcron2cron.py - -Usage -~~~~~ - -Le programme doit être lancé en root pour pouvoir lire les fichiers présents -dans le répertoire `/var/spool/fcron/` ainsi que les fcrontabs des différents -utilisateurs du système: - -.. code-block:: console - - $ mkdir crontabs - $ cd crontabs - $ sudo python ~/fcron2cron.py - -Il y a beaucoup moins de paramètres chez cron que fcron, il est toutefois -possible de convertir quelques instructions : - -Les options -~~~~~~~~~~~ - -Les seules options possibles dans cron sont celles qui concernent le mail : - -`mail(bool)` devient `MAILTO=""` si bool est à false `mailto(string)` devient -`MAILTO="string"` - -Les autres options sont ignorées\ [#]_. - - -Les directives -~~~~~~~~~~~~~~ - -Les directives `hourly`, `daily`\ [#]_, `weekly`, `monthly` sont transformées -en leur équivalent dans cron, ainsi que leur équivalent `mid-*ly`. Les -directives `mins`, `hours`, `days`, `mons`, `dow` ne sont pas prises en compte -et sont ignorées dans la ligne générée. - -L'édition -========= - -Le programme ne fait pas l'insertion de la crontab dans le système, ainsi, dans -le cas ou une telle crontab existe déjà, elle ne sera pas écrasée. C'est à -l'adminstrateur de mettre le paramétrage à jour. Une fois le script lancé, des -fichiers `UTILISATEUR.crontab` vont être générés. - -Si l'utilisateur n'a pas de crontab correspondante, il est possible de le -charger directement avec la commande suivante : - -.. code-block:: console - - # crontab -u ${USER} ${USER}.crontab - -Si l'utilisateur possède déjà une crontab, il va falloir éditer le fichier pour -le faire correspondre. Maintenant qu'il ne reste plus que ça à faire, il est -temps de se remonter les manches et y aller à la main ! Une fois que les -modifications sont faites, vous pouvez désinstaller fcron de votre système. - -.. admonition:: notes : - - .. [#] Y compris les options en début de commande (ligne débutant par `&`) - .. [#] `nightly` est traité de la même manière diff --git a/content/Informatique/2013-05-23-ocamlfind_en.rst b/content/Informatique/2013-05-23-ocamlfind_en.rst_ index a725a39..6b812f7 100644 --- a/content/Informatique/2013-05-23-ocamlfind_en.rst +++ b/content/Informatique/2013-05-23-ocamlfind_en.rst_ @@ -1,63 +1,63 @@ -.. -*- rst -*-
-.. -*- coding: utf-8 -*-
-
-============================
-A local repository for ocaml
-============================
-
-:date: 2013-05-23
-:tags: ocaml
-:summary: |summary|
-:logo: /images/ocaml/camel.jpg
-:slug: un-depot-local-pour-ocaml
-:lang: en
-
-.. figure:: {filename}/images/ocaml/camel_medium.jpg
- :figwidth: 150
- :figclass: floatleft
- :alt: Pavement
-
- Image : `@Doug88888`_ (creativecommons_)
-
-.. _@Doug88888: http://www.flickr.com/photos/doug88888/3458057235/
-.. _creativecommons: http://creativecommons.org/licenses/by-nc-sa/2.0/deed.fr
-
-|summary|
-
-
-.. |summary| replace::
- Today, ocaml is obsolete in the debian packages : the most of the
- librairies are still linked with Ocaml3 althought Ocaml4 is out since
- almost a year. When we need to develop with Ocaml4, we have to recompile
- all the dependencies, but this implies to install them for the whole
- system. But we can use a local repository with some findlib configuration.
-
-Findlib is now a required tool for an Ocaml project compilation. Each
-application uses now findlib for searching the dependencies, and the linking
-phase.
-
-We first need to create a local configuration in wich we write where to find
-the packages :
-
-
-.. code-block:: bash
-
- destdir="/home/chimrod/ocaml/packages/"
- path="/home/chimrod/ocaml/packages/:/usr/local/lib/ocaml/4.00.1:/usr/lib/ocaml:/usr/lib/ocaml/METAS"
-
-:destdir: is the path where we want to install the newly compiled packages.
-:path: is the path list where to find the packages.
-
-Of course, you need to replace `/home/chimrod/` by your own installation path.
-It must be an absolute path.
-
-The path to this file can be saved with the shell environnement ; we just need
-to reference it in the `.bashrc` file :
-
-.. code-block:: bash
-
- export OCAMLFIND_CONF=/home/chimrod/ocaml/ocamlfind.conf
-
-That's now : each new package will now be installed in your packages directory,
-and the compilation will also look for this path for the required
-dependencies : you do not have to install the packages as root anymore.
+.. -*- rst -*- +.. -*- coding: utf-8 -*- + +============================ +A local repository for ocaml +============================ + +:date: 2013-05-23 +:tags: ocaml +:summary: |summary| +:logo: /images/ocaml/camel.jpg +:slug: un-depot-local-pour-ocaml +:lang: en + +.. figure:: {filename}/images/ocaml/camel_medium.jpg + :figwidth: 150 + :figclass: floatleft + :alt: Pavement + + Image : `@Doug88888`_ (creativecommons_) + +.. _@Doug88888: http://www.flickr.com/photos/doug88888/3458057235/ +.. _creativecommons: http://creativecommons.org/licenses/by-nc-sa/2.0/deed.fr + +|summary| + + +.. |summary| replace:: + Today, ocaml is obsolete in the debian packages : the most of the + librairies are still linked with Ocaml3 althought Ocaml4 is out since + almost a year. When we need to develop with Ocaml4, we have to recompile + all the dependencies, but this implies to install them for the whole + system. But we can use a local repository with some findlib configuration. + +Findlib is now a required tool for an Ocaml project compilation. Each +application uses now findlib for searching the dependencies, and the linking +phase. + +We first need to create a local configuration in wich we write where to find +the packages : + + +.. code-block:: bash + + destdir="/home/chimrod/ocaml/packages/" + path="/home/chimrod/ocaml/packages/:/usr/local/lib/ocaml/4.00.1:/usr/lib/ocaml:/usr/lib/ocaml/METAS" + +:destdir: is the path where we want to install the newly compiled packages. +:path: is the path list where to find the packages. + +Of course, you need to replace `/home/chimrod/` by your own installation path. +It must be an absolute path. + +The path to this file can be saved with the shell environnement ; we just need +to reference it in the `.bashrc` file : + +.. code-block:: bash + + export OCAMLFIND_CONF=/home/chimrod/ocaml/ocamlfind.conf + +That's now : each new package will now be installed in your packages directory, +and the compilation will also look for this path for the required +dependencies : you do not have to install the packages as root anymore. diff --git a/content/Informatique/2013-06-02-roi_con.rst b/content/Informatique/2013-06-02-roi_con.rst deleted file mode 100644 index 3d2c3e5..0000000 --- a/content/Informatique/2013-06-02-roi_con.rst +++ /dev/null @@ -1,128 +0,0 @@ -.. -*- mode: rst -*- -.. -*- coding: utf-8 -*- - -===================================== -Internet, la guerre des médiacultures -===================================== - -:date: 2013-06-02 -:tags: Libre, Infowar -:summary: Les entreprises pourront toujours tenter de brider internet, la - créativité des internautes sera toujours plus forte, plus réactive. -:logo: /images/roi/roi_75.jpeg -:lang: fr - -.. figure:: {filename}/images/roi/roi_150.jpeg - :class: floatright - :alt: Un roi playmobil - -Une petite histoire -=================== - -Il était une fois, dans un royaume lointain, un roi qui n'était pas aimé de son -peuple. Ce dernier chantait une chanson dans tout le royaume dont le refrain -était le suivant : - - Longue vie au roi bouffon, le roi très con qu'est très très con ! - -Le roi lassé d'entendre cette chanson, convoqua ses ministres pour agir. Il -fut décidé que le plus sage était d'interdire tout simplement à ces sujets de -chanter cette chanson, et le texte fut mis à l'index. Toute personne surprise -en train de chanter cette chanson serait immédiatement envoyée aux galères. - -Les troubadours et chansonniers furent obligés de s'y plier. Mais dans les -tavernes, les champs et les chaumières, la population continuait de fredonner. - -Cela rendit le roi furieux, qui demanda aux gardes de patrouiller à la -recherches des chanteurs et de les mettre aux fers immédiatement, mais cela ne -changea rien, les gens continuaient, non plus à la chanter, mais à siffler la -chanson, sous le nez des gardes qui ne pouvaient rien faire puisque le texte -n'était pas prononcé ! Mais personne n'était dupe, et le roi continuait de -tourner en bourrique. - -Le roi, qui se faisait vieux, dépensa son énergie dans ce combat, les finances -de son royaume, et sa santé commença à se dégrader. - -Le business Internet -==================== - -Peut-on censurer ce qui circule sur internet ? Oui bien sûr, tout contenu qui y -est publié est sous la responsabilité d'une personne, morale ou physique. Après -longtemps s'être demandé qui devait être cette personne responsable -(l'hébergeur, le fournisseur d'accès), la LCEN a mis un peu d'ordre dans tout -ça. - -Aujourd'hui, il n'est pas possible de diffamer, calomnier, porter préjudice -parce que l'on utilise le web pour clamer son fiel, et les avocats se font un -plaisir d'envoyer des courriers de mise en demeure aux contrevenants — et c'est -très bien comme ça. - -Par contre, les entreprises savent que le droit à l'oubli n'existe pas sur le -web, et ont bien compris qu'une critique bien acerbe en première page de google -porte préjudice à l'image de marque. Des budgets sont dépensés pour que la -marque soit correctement représentée sur le net ; voyons par exemple `comment -nespresso transforme wikipédia en une brochure publicitaire`_ - -Que ce passe-t-il quand le business rencontre la loi ? Généralement rien de -très bon… Et c'est pareil ici : les lettres de mise en demeure servent de -menaces, et l'actualité nous en a fourni `un exemple très récemment`_. - -Stratégie et tactiques -====================== - -Heureusement, les internautes ne sont pas dupes, et face aux stratégies des -entreprises pour asseoir leur image, disposent via le web d'un incroyable outil -pour réponse. Non, internet ne sert pas qu'à regarder des chats qui jouent du -piano ! Michel de Certeau, dans `L'invention du quotidien`_, fait la différence -entre `stratégie` et `tactiques`. Là où la stratégie est planifiée, la tactique -*fait du coup par coup. Elle profite des « occasions » et en dépend. […] Il lui -faut utiliser, vigilante, les failles que les conjonctures particulières -ouvrent dans la surveillance du pouvoir propriétaire. Elle y braconne. Elle y -crée des surprises. Il lui est possible d'êre là où l'on ne l'attend pas. Elle -est ruse.* - -Les combats qui se jouent aujourd'hui sur internet donnent un sens très -actuels à ces mots. Les internautes ne pourront jamais faire face à l'armada -d'avocats prêts à dégainer au moindre signe d'ironie sur les forums. Mais ces -avocats sont démunis face à un `effet streisand`_, qu'ils ne peuvent pas -contrôler. Le jeu de pouvoir entre les internautes et les régulateurs du web -n'est pas fini, et les internautes savent mobiliser leurs ressources dès -qu'ils sentent que l'on porte atteinte à leur réseau. Cela m'impressionne à -chaque fois. - -La loi est respectée, par contre le combat est perdu pour celui qui cherchait à -faire plier un site, à donner à Internet la forme qu'il souhaitait : la réalité -ne se laisse pas contrôler si simplement… L'information restera libre ! - -.. _comment nespresso transforme wikipédia en une brochure publicitaire: http://thierry-klein.speechi.net/2009/12/01/comment-nespresso-transforme-wikipedia-en-brochure-publicitaire/ - -.. _un exemple très récemment: https://linuxfr.org/news/premiere-mise-en-demeure-pour-l-association-linuxfr -.. _L'invention du quotidien: https://fr.wikipedia.org/wiki/Michel_de_Certeau#L.27analyse_du_.C2.AB_braconnage_culturel_.C2.BB -.. _effet streisand: https://fr.wikipedia.org/wiki/Effet_Streisand - -La fin de l'histoire -==================== - -Après la mort du roi, le prince prit la couronne. Lors de la cérémonie, la -foule cria : - - Le roi est mort, vive le roi ! - -Et quelqu'un ajouta : - - Vive le roi bouffon, le roi très con qu'est très très con ! - -Refrain qui fut repris sur toute la place par la population. C'est alors que le -roi paru au balcon du palais, et il se mit à chanter : - - Longue vie au roi bouffon, le roi très con qu'est très très con ! - -La foule chanta avec le roi, mais rapidement le malaise s'installa, et bientôt -plus personne ne chantait. En chantant cette chanson, le roi venait de lui -retirer son aspect contestataire : comment peut-on chanter cette chanson contre -le roi, si le roi lui-même se met à la chanter ? Par cette action, il venait de -résoudre la crise qui se tramait dans le royaume. Les gens rentrèrent chez eux -et l'on n'entendit plus jamais ce refrain. - -Mais à part dans les contes de fées, existe-t-il des rois capable d'aller au -balcon pour chanter haut et fort les critiques de la foules ? diff --git a/content/Informatique/2013-07-24-projet_libre.rst b/content/Informatique/2013-07-24-projet_libre.rst deleted file mode 100644 index 50d8f2a..0000000 --- a/content/Informatique/2013-07-24-projet_libre.rst +++ /dev/null @@ -1,83 +0,0 @@ -.. -*- mode: rst -*- -.. -*- coding: utf-8 -*- - -========================== -Les conflits dans le libre -========================== - -:date: 2013-07-24 -:tags: ocaml, Libre -:summary: |summary| - -|summary| - -.. |summary| replace:: - Mener un projet libre n'est pas simple ; cela demande du temps, beaucoup - d'investissement, et le retour est rarement suffisament encourageant pour - tenir sur cette motivation. Il y a en ce moment des tensions au sein des - développeurs Ocaml par rapport à deux projets concurents. - -Le langage Ocaml essaie de gagner un nouveau dynamisme depuis quelques années. -Parmi les moteurs de ce dynamisme, un nouveau gestionnaire de paquets, opam_, -est censé apporter une simplification dans la gestion des dépendances, et -l'installation des librairies nécessaires à un projet. Sauf qu'il existe déjà -un gestionnaire de paquets dans Ocaml : godi_, qui existe depuis quelques -années, et qui a réussi à créer une grande bibliothèque de librairies -installable. - -.. _opam: http://opam.ocamlpro.com/ -.. _godi: http://godi.camlcity.org/godi/index.html - -Le créateur Gerd Stolpmann a laissé un message sur la mailing-list récemment, -indiquant qu'il `arreterait de maintenir godi`_, en partie à cause de la -concurrence d'opam, et principalement à cause de la campagne de dénigrement de -godi de la part des mainteneurs d'opam. Cela est en train de déchaîner les -réponses et d'agiter les principaux acteurs d'Ocaml qui essaient de trouver une -solution pour sortir du conflit. - -.. _arreterait de maintenir godi: https://sympa.inria.fr/sympa/arc/caml-list/2013-07/msg00199.html - -Les reproches -============= - -Gerd Stolpmann reproche à ocamlpro (l'équipe de développement d'opam) de ne pas -l'avoir contacté avant de se lancer dans le développement d'une solution -concurrente, et d'`avoir été évincé`_ après avoir construit et maintenu un -écosystème viable. D'un autre côté, ocamlpro annonce que opam `répondait à une -demande`_ d'un nouveau produit par un client, et qu'il était plus simple de -repartir sur une nouvelle base. - -Par ailleurs, la communauté reprend Gerd Stolpmann en lui annonçant qu'il n'y a -pas eu de dénigrement. Il y a bien eu toute une campagne d'enthousiasme liée à -l'arrivée d'un nouveau produit, mais qui ne s'est pas accompagnée pour autant -d'un dénigrement de godi et de la solution existante. - -.. _avoir été évincé: https://sympa.inria.fr/sympa/arc/caml-list/2013-07/msg00245.html -.. _répondait à une demande: https://sympa.inria.fr/sympa/arc/caml-list/2013-07/msg00240.html - -Rien de nouveau sous le soleil -============================== - -Ce genre de débat n'est pas nouveau, les conflits le noyau linux sont souvent -remonté et dérivent parfois vers des mails cinglants s'échangeant sur les -mailing-list. Le conflit est même plutôt positif, voire nécessaire (la seule -manière de gérer un projet sans conflit est d'imposer un dictateur). D'une -manière plus générale, `Georg Simmel`_ nous explique que le conflit est -positif, car il correspond à une prise de conscience de l'existence de l'autre. - -.. _Georg Simmel: https://fr.wikipedia.org/wiki/Georg_Simmel - -Pour ma part, je trouve dommage que l'esprit de création puisse être gâché à -cause de querelles de ce genre. Bien sûr, tout le monde est libre de commencer -un nouveau projet sans se référer à ceux existants, bien sûr tout le monde peut -forker ou même repartir à zéro un nouveau projet sans demander la permission, -mais quand on a affaire à une communauté compétente (et les développeurs Ocaml -le sont), je trouve dommage de diviser les compétences d'une communauté et -pousser quelqu'un à l'aigreur pour des raisons aussi futile que ce qui se passe -en ce moment. - -C'est pourtant le revers de la médaille du `bazar, les construction de -cathédrales`_ n'ont pas autant de péripéties… D'aucuns diront que ça fait -partie de la vie des projets libre. - -.. _bazar, les construction de cathédrales: https://fr.wikipedia.org/wiki/La_cath%C3%A9drale_et_le_bazar diff --git a/content/Informatique/2014-02-09-ocaml_gtk.rst b/content/Informatique/2014-02-09-ocaml_gtk.rst deleted file mode 100644 index 09f1aee..0000000 --- a/content/Informatique/2014-02-09-ocaml_gtk.rst +++ /dev/null @@ -1,206 +0,0 @@ -.. -*- rst -*-
-.. -*- coding: utf-8 -*-
-
-=======================================
-Trois manière d'utiliser Gtk avec OCaml
-=======================================
-
-:date: 2014-02-09
-:tags: ocaml, gtk
-:summary: |summary|
-:logo: /images/ocaml/camel_75.jpg
-
-.. default-role:: literal
-
-.. figure:: {filename}/images/ocaml/camel_2.jpg
- :figwidth: 150
- :figclass: floatleft
- :alt: Pavement
-
- Image : `Kevin Botto`_ (creativecommons_)
-
-.. _Kevin Botto: http://www.flickr.com/photos/kevinbotto/3251157974/
-.. _creativecommons: http://creativecommons.org/licenses/by-nd/2.0/deed.fr
-
-|summary|
-
-
-.. |summary| replace::
- Créer des interfaces Gtk pour OCaml peut se réveler compliquer. On trouve
- beaucoup d'explications un peu confuses sur le net, et n'est pas facile de
- trouver comment faire.
- Je vous propose ici trois manières différentes en utilisants les différents
- outils disponibles.
-
-Il est possible d'utiliser la bibliothèque Gtk à l'aide de la librairie
-lablgtk_, qui fourni un binding pour l'ensemble de la bibliothèque. L'avantage
-est de pouvoir fonctionner sur toutes les plateformes où gtk est disponible, et
-de bénéficier d'une interface évoluée et agréable pour écrire son application.
-
-L'inconvénient réside dans la bibliothèque gtk : api très compliquée,
-documentation confuse, bref on se retrouve rapidement face à des problèmes là
-où l'on pensait pouvoir avancer sans difficulté. De plus, on est aujourd'hui
-passé à gtk3 alors que `lablgtk` utilise toujours l'api de gtk2. Cela ne pose pas
-de problème dans la compilation (la compatibilité est assurée), mais peut poser
-problème lors de l'utilisation d'outils tels que `glade` (voir plus loin).
-
-.. _lablgtk: http://lablgtk.forge.ocamlcore.org/
-
-Tout construire à la main
-=========================
-
-C'est la première solution, qui demande de connaître Gtk : tous les objets sont
-construits à la main, et le code décrit la manière de faire. L'avantage est que
-le code ne dépend d'aucune ressource externes contrairement aux deux suivantes.
-De plus on contrôle complètement la création de l'interface, on peut donc
-choisir de paramétrer l'interface au lieu d'avoir une interface unique. Un
-tutoriel complet est disponible sur le site d'`OCaml.org`_, je ne vais pas
-le reprendre ici et vous encourage à le suivre.
-
-.. _ocaml.org: http://ocaml.org/learn/tutorials/introduction_to_gtk.html
-
-L'exemple est donné dans la console interactive. Si l'on souhaite le compiler
-dans un module, il faut initialiser Gtk avant de lancer l'affichage :
-
-.. code-block:: ocaml
-
- GtkMain.Main.init ();
-
-Pour compiler un module, il est nécessaire de faire référence au package
-`lablgtk2`. Voici un exemple pour compiler un fichier `hello.ml` :
-
-.. code-block:: console
-
- $ ocamlfind ocamlc -package lablgtk2 -I . -c hello.ml
-
-Maintenant il ne reste plus qu'à se plonger dans la documentation de gtk pour
-construire son interface !
-
-Utiliser Glade
-==============
-
-Glade est une interface graphique permettant de construire son application en
-plaçant les contrôles de manière visuelle. Elle génère un fichier XML qui
-décrit l'interface et sera chargé par l'application pour construire
-l'interface. Cela permet de gagner du temps et d'éviter d'écrire le code
-nécessaire pour construire son interface, on se concentre sur les actions
-à exécuter lorsque l'utilisateur interagit.
-
-.. image:: {filename}/images/glade.jpg
- :class: center
- :alt: Utilisation de glade.
-
-
-Les XMLs générés par glade sont destinés à être utilisés avec gtk3. Or,
-`lablgtk` utilise encore gtk2, il est donc nécessaire d'utiliser une conversion
-pour pouvoir les charger par la suite. Voici une petite règle `make` qui se
-charge de faire le travail :
-
-.. code-block:: make
-
- %.glade2: %.glade
- cat $< | sed 's/interface/glade-interface/' | sed 's/object/widget/' | sed 's/GtkBox/GtkVBox/' > $@
-
-Maintenant qu'on dispose d'un fichier au format glade2, on peut le charger dans
-OCaml.
-
-Attention, lors de la compilation, il est nécessaire d'utiliser `libglade` pour
-construire l'application, celle-ci est disponible dans la librairie
-`lablgtk2.glade`. Voici donc un exemple de commande pour compiler un fichier
-`hello.ml` :
-
-.. code-block:: console
-
- $ ocamlfind ocamlc -package lablgtk2.glade -I . -c hello.ml
-
-.. _gtkbuilder: https://developer.gnome.org/gtk3/3.4/GtkBuilder.html
-
-Charger le fichier xml
-----------------------
-
-Il s'agit de la solution la plus dynamique : on référence le fichier xml dans
-le code, et l'interface se construit toute seule. Cette solution est présentée
-sur le site de `developpez.com`_. L'exemple donné est toujours valide, il ne
-faut pas oublier d'initialiser Gtk avec la commande suivante avant de lancer la
-construction de l'interface :
-
-.. code-block:: ocaml
-
- GtkMain.Main.init ();
-
-L'inconvénient de cette méthode (du moins pour un développeur OCaml) est que
-l'on est obligé de convertir de manière dynamique tous les objets présents dans
-le XML. Il n'est pas possible de savoir au moment de la compilation si le code
-que l'on va exécuter est bien valide.
-
-En effet, les objets chargés depuis le XML nous sont retournés sous la forme
-widget gtk, qu'il faut convertir en Bouton, Menu via les méthodes appropriées
-(`GtkWindow.Window.cast` par exemple). On n'est donc pas à l'abri d'avoir une
-erreur lors du fonctionnement du programme, alors que celui-ci a pu compiler
-sans problème. Je pense que lorsqu'on cherche à développer en OCaml, ce genre
-de problème peut être rédhibitoire.
-
-.. _developpez.com: http://blog.developpez.com/damien-guichard/p7748/programmation-fonctionnelle/hello_developpez_avec_libglade_xml_1
-
-De plus, rien ne garantie que le fichier XML ne va pas évoluer de manière
-incompatible du code ; les deux étant distincts.
-
-
-Utiliser lablgladecc2
----------------------
-
-Heureusement, la librairie `lablgtk2` nous fourni un petit utilitaire nommé
-`lablgladecc2` qui va convertir un fichier xml glade2 en un fichier OCaml. On
-dispose donc d'un chargement dynamique du fichier xml, mais en gardant un code
-cohérent, qui détectera les erreurs dès la compilation. Il s'agit en quelque
-sorte d'un moyen de combiner les deux solutions précédentes.
-
-On va ajouter une règle au makefile pour générer notre fichier OCaml :
-
-.. code-block:: make
-
- %.ml: %.glade2
- lablgladecc2 -embed $< > $@
-
-Le fichier généré se compose d'une classe reprenant les différents composants
-de notre interface, il ne nous reste plus qu'à réaliser les connexions, ainsi,
-à partir d'un fichier glade nommé *gui* composé d'une fenêtre `window1`, d'un
-bouton `button` et d'une entrée de menu, on peut créer le code suivant :
-
-.. code-block:: ocaml
-
- let gladecc () =
- let window = new Gui.window1 () in
-
- window#button#connect#clicked (fun () -> prerr_endline "Ouch!");
- window#window1#connect#destroy GMain.quit;
- window#imagemenuitem5#connect#activate GMain.quit;
-
- window#toplevel#show ()
-
- let () =
- GtkMain.Main.init ();
- gladecc ();
- GMain.Main.main ()
-
-
-l'objet `toplevel` est créé par `lablgladecc2` et correspond à la fenêtre
-principale de notre objet.
-
-Dans cette chaîne de compilation, le fichier xml est écrit dans le programme
-OCaml (il s'agit de la signification de l'option `-embed`), ainsi, le fichier
-XML n'a pas besoin de figurer parmi les ressources de l'application.
-
-Conclusion
-==========
-
-Trois manière de faire qui répondent à trois besoin différents, entre le tout
-codé et le tout dynamique, il est possible de créer des interfaces graphiques
-en utilisant les capacités du langage caml sur l'inférence de type et le
-contrôle de l'ensemble de l'application.
-
-Pour ma part, je préfère la dernière solution, qui permet de conserver la
-simplicité de `glade` combiné avec la force du langage OCaml. J'ai écrit cet
-article suite à pas mal d'errance sur le net pour trouver les informations
-nécessaires, j'espère que la documentation va évoluer par la suite et permettre
-de faire ce genre de choses plus facilement…
diff --git a/content/Informatique/2015-10-03-moderncv.rst b/content/Informatique/2015-10-03-moderncv.rst index 939d100..0e534d9 100644 --- a/content/Informatique/2015-10-03-moderncv.rst +++ b/content/Informatique/2015-10-03-moderncv.rst @@ -9,6 +9,14 @@ Se faire un CV en rst :tags: reStructuredText, Libre :summary: |summary| +.. admonition:: Article obsolète + :class: warning + + Je propose désormais une solution complète pour se faire son modèle de CV en + rst : `encore des cvs`_ ! + +.. _encore des cvs: {filename}2020-12-03-encore_des_cv.rst + |summary| .. |summary| replace:: @@ -17,6 +25,7 @@ Se faire un CV en rst fois-ci je vous propose d'écrire votre CV en rst, et donner le rendu à latex via moderncv pour obtenir un rendu qui sort du lot. + Moderncv ======== diff --git a/content/Informatique/2020-12-03-encore_des_cv.rst b/content/Informatique/2020-12-03-encore_des_cv.rst new file mode 100755 index 0000000..e950fee --- /dev/null +++ b/content/Informatique/2020-12-03-encore_des_cv.rst @@ -0,0 +1,183 @@ +.. -*- mode: rst -*- +.. -*- coding: utf-8 -*- + +===================== +Encore des cvs en rst +===================== + +:date: 2020-12-03 +:tags: latex, restructuredText +:summary: |summary| + + +.. default-role:: literal + +|summary| + +.. |summary| replace:: + + J'avais déjà présenté comment modifier_ le format rst pour produire un cv avec + la classe moderncv de latex. Cette fois je propose de se créer soi-même son + modèle tout en gardant le format RST. + +.. _modifier: {filename}2015-10-03-moderncv.rst + + +L'idée est de conserver le standard du format rst et de modifier les fichiers +latex pour les adapter en fonction du besoin. Cela signifie que le même fichier +source peut être utilisé avec les différents exemples sans changements (ou +presque). + +Le résultat +=========== + +Commençons directement par le final, voilà le résultat : + +.. image:: {static}../images/cv/model5.png + :width: 50% + :class: floatleft + +le code est disponible sur mon `dépôt git`_ si vous souhaitez le cloner et +récupérer les sources. + +.. _dépôt git: http://git.chimrod.com/latex_template.git/ + +Les directives RST +================== + +La sidebar +---------- + +.. code-block:: rst + + .. sidebar:: Titre + + Texte à renseigner dans le bandeau + +Permet de créer du texte dans la barre latérale. Le texte peut être une liste, +une image ou tout autre élément standard. Il faut juste faire attention à la +largeur qui oblige à rester concis… + + +.. admonition:: Sans titre ? + :class: hint + + rst oblige à déclarer un titre dans la directive, ce qui n'est pas forcément + ce que l'on souhaite. Par contre le titre peut être vide ! Il suffit + d'échapper une espace : + + + .. code-block:: rst + + .. sidebar:: \ + + .. image:: mon profil.jpg + :width: 100% + +Les listes +---------- + +Avec la directive `class`, il est possible d'appliquer un style qui sera +présent jusqu'à la fin du paragraphe. Ce style est associé à une commande Latex +qui permet l'appliquer le style en question. Ainsi, avec la classe `checklist`, +toutes les puces des listes sont remplacées par une coche : + +.. code-block:: rst + + .. class:: checklist + + * 1er élément + * Second élément + +La commande latex suivante est utilisée : + +.. code-block:: latex + + \newenvironment{DUCLASSchecklist}{\renewcommand{\labelitemi}{\faCheck}}{} + +Il est facile de modifier la commande pour charger d'autres types de marques, +voir le fichier `10_lists.tex`_ pour la liste complète. + +.. _10_lists.tex: http://git.chimrod.com/latex_template.git/tree/common/10_lists.tex + +Il est possible de modifier le style de liste en cours de route, mais il faut +faire attention à appeler la commande **avant** la ligne que l'on souhaite +modifier. C'est pourquoi l'on retrouve l'exemple suivant dans lequel on annonce +les icônes avant la ligne suivante : + +.. code-block:: rst + + .. sidebar:: Informations + + * 75001 Paris |phoneicon| + * 01 23 45 67 89 |mailicon| + * email@example |noicon| + +`|phoneicon|` est en fait une substitution de texte qui insère une commande +latex à la place (et donc ne substitue rien du tout :)) + +Les barres de niveau +==================== + +Autre point assez amusant, il est possible de faire des graphiques à partir de +données dans le fichier. Les niveaux représentés sous forme d'étoiles ou de +barres sont construits dans latex à partir des lignes suivantes : + +.. code-block:: rst + + .. role:: level + .. role:: levelbox + .. role:: star + + :star:`4.5` + :levelbox:`5` + :level:`3` + +Il s'agit de rôles personnalisés, qui sont interprétés comme des commandes +latex prenant un paramètre : la valeur du niveau. Celui-ci est ensuite +représenté sous forme d'étoile colorées, ou barre de niveau. On peut imaginer +toutes les fantaisies possibles… + +Autres éléments +=============== + +Il n'y en a pas d'autres. Tout le reste est ensuite standard et suit les +directives rst. (La directive `admonition`_ permet également des +personnalisations assez avancées, mais je ne l'ai pas appliqué ici). + +.. _admonition: https://docutils.sourceforge.io/docs/ref/rst/directives.html#admonitions + +L'organisation +============== + +Chaque répertoire contient un modèle différent, et tous les modèles suivent le +même schéma : + +:: + + | + |\ Makefile + |\ fichier.rst (le fichier source à traiter) + \ resources (le répertoire contenant les modèles latex) + | + |\ 00_colors.tex (la définition des couleurs) + |\ 00_length.tex (les longueurs pour les marges ou autres paramètres) + |\ 00_preamble.tex (le fichier contenant les paramètres principaux) + |\ 10_fonts.tex (les polices utilisées dans le document) + |\ 10_title_style.tex (la personnalisation des titres) + \ autres directives latex + +Tous les fichiers qui sont présents dans le répertoire `resources` sont +automatiquement chargés, et les modules présents dans le fichier `modules` sont +également chargés. + +Pour la simplification du dépôt, des fichiers communs à tous mes styles sont +présents dans le répertoire `common` + +Et voilà +======== + +J'espère que ces petits exemples vous donneront des idées, et qu'ils vous +permettront de construire vos propres modèles. L'idée est vraiment de laisser +toute la mécanique latex en arrière-plan, et se simplifier au maximum +l'écriture ! diff --git a/content/Perso/poker.rst b/content/Perso/poker.rst deleted file mode 100644 index 44a011b..0000000 --- a/content/Perso/poker.rst +++ /dev/null @@ -1,221 +0,0 @@ -.. -*- rst -*-
-.. -*- coding: utf-8 -*-
-
-Évaluation des mains au poker
-#############################
-
-:date: 2013-04-19
-:tags: ocaml, Programmation, Jeux
-
-J'ai entrepris il y a quelques temps de mettre en place un moteur d'évaluation
-des mains de poker. Je n'ai pas trouvé beaucoup de codes ou librairies
-permettant ça, à l'exception de pokersource_ qui est assez utilée dans des
-projets libres.
-
-Cette librairie permet d'évaluer le pourcentage de chance pour chaque joueur de
-gagner le pot, en fonction du type de jeu, du board, ou des cartes brulées.
-Elle est fiable et assez rapide si l'on en croit les critiques_.
-
-Par contre, dès qu'il s'agit d'évaluer des groupes de mains, aucune
-optimisation n'est réalisée, et c'est à la charge du développeur de s'en
-occuper. J'ai donc commencé une petite librairie en Ocaml qui se charge de
-faire les calculs, en réalisant les optimisations nécessaires pour accélerer le
-calcul.
-
-Je ne vais pas présenter ici le code, mais plutôt comment sont réalisées les
-différentes optimisations mises en places par la librairie.
-
-.. _pokersource: http://svn.gna.org/svn/pokersource/
-.. _critiques: http://www.codingthewheel.com/archives/a-pokersource-poker-eval-primer
-
-Couleurs de libertés
-=====================
-
-Présentation
-------------
-
-Il s'agit de la première optimisation et s'inspire des degrés de libertés l'on
-peut retrouver dans les équations. On part du principe que évaluer les
-probabilité entre |Ah|\ |As| contre |Ks|\ |Kc| est équivalent a
-|Ac|\ |Ad| contre |Kd|\ |Kh| c'est à dire que les couleurs
-peuvent être substituées les unes aux autres sans changer les probabilités.
-
-Tant qu'une couleur n'a pas été « posée », elle peut être substituée par
-n'importe quelle autre. Cela simplifier les calculs répétitifs, par exemple :
-
-|Ah|\ |As| contre |Ks|\ |Kc|
-
-|Ah|\ |As| contre |Ks|\ |Kh|
-
-|Ah|\ |As| contre |Ks|\ |Kd|
-
-|Ah|\ |As| contre |Kc|\ |Kh|
-
-|Ah|\ |As| contre |Kc|\ |Kd|
-
-|Ah|\ |As| contre |Kh|\ |Kd|
-
-va pouvoir être simplifié en
-
-1 * |Ah|\ |As| contre |Ks|\ |Kh| (les deux mains partagent les couleurs)
-
-4 * |Ah|\ |As| contre |Ks|\ |Kc| (les deux mains partagent une couleur)
-
-1 * |Ah|\ |As| contre |Kc|\ |Kd| (les deux mains ne partagent pas de couleur)
-
-Selon le nombre de joueurs disutant la confrontation, le nombre de calculs à
-faire peut déjà être diminué par 2 ou plus : il est possible d'appliquer
-récursivement cette opération pour chacun des joueurs, en fonction des couleurs
-qui ont été utilisées précédemment.
-
-Implémentation
---------------
-
-Il « suffit » de tenir une table de correspondance entre les couleurs réelles,
-et les couleurs utilisées dans le calcul. Puis, de regrouper les mains du
-joueur :
-
-1) Au début, la table est vide : aucune couleur n'est fixée, donc deux mains
- ayant des valeurs identiques (par exemple |Ah|\ |As| et |Ac|\ |Ad|\ ) vont se
- retrouver regroupée sous la même main, qui sera comptabilisée deux fois.
-
-2) Ensuite, pour chaque main ainsi générée, on va regrouper les mains du joueur
- suivant. Sauf que cette fois-ci la table de correspondance n'est plus vide :
- elle a déjà été rempli lors de la phase 1; certaines mains ne pourront donc
- pas être regroupées entre elles.
-
-3) On répète la phase 2 pour chaque joueur, à chaque fois le nombre de couleurs
- libre est réduit. Quand on arrive au dernier joueur, on lance le calcul,
- auquel on applique le facteur de multiplication, calculé en fonction du
- nombre de mains qui ont étés regroupées pour l'ensemble des joueurs.
-
-Calcul combinatoires
-====================
-
-Cette première opération permet déjà de réduire les calculs, mais ça n'est pas
-suffisant. Quand on essaie de calculer les probabilité, il arrive souvent que
-les adversaires aient des mains en communs, parmi leur possibité de jeu.
-
-Imaginons les possibité suivantes :
-
-* joueur 1 : {AA, KK, AK}
-* joueur 2 : {AA, KK}
-* joueur 3 : {KK, AK}
-
-évaluer les probabilité de chaque joueur va conduire a des répétitions lors
-évaluations :
-
-* (AA, **KK**, **AK**) et (AA, **AK**, **KK**)
-* (**AK**, AA, **KK**) et (**KK**, AA, **AK**)
-* …
-
-Quand on a calculé le premier arrangement, on peut déduire les résulats du
-second sans faire les calculs, il suffit d'éffectuer une permutation pour
-obtenir, mutatis mutandis, le résulat attendu.
-
-Partitions
-----------
-
-La première étape est de réaliser une partition des mains possibles du joueur.
-Pour chaque joueur, il faut commencer par créer des groupes de mains tel que :
-
-* chaque groupe pris pair à pair ne contient aucune carte en commun
-* il est possible de reconstituer l'ensemble des mains des joueurs par une
- combinaison de ces groupes.
-
-On va se servir des `opérations de base`_ pour découper un ensemble_ pair à
-pair, en prenant à chaque fois l'élément unique que l'on applique ensuite à
-l'ensemble suivant.
-
-.. code-block:: ocaml
-
- let explode =
-
- let rec partition clean current = function
- | [] -> current, clean
- | hd::tl ->
- if S.disjoint current hd || S.equal current hd then
- partition (hd::clean) current tl
- else
- let s1::s2 = split current hd
- in List.unique ~eq:S.equal (partition s2 s1 ((tl @ clean)))
-
- and process_list checked = function
- | [] -> checked
- | hd :: tl -> (
- match partition [] hd tl with
- | cleaned, [] -> cleaned::checked
- | cleaned, tl' -> process_list (cleaned::checked) tl'
- )
-
- in process_list []
- %> List.unique ~eq:S.equal
-
-.. _ensemble: https://fr.wikipedia.org/wiki/Ensemble
-.. _`opérations de base`: https://fr.wikipedia.org/wiki/Alg%C3%A8bre_des_parties_d%27un_ensemble
-
-Énumérer
---------
-
-Ensuite, nous allons énumérer l'ensemble des combinaisons possible de ces
-groupes. Il existe quelques algorithmes_ permettant de générer la liste des
-combinaisons données pour un rang donné.
-
-Toutefois, nous travaillons ici sur des `combinaisons avec répétitions`_ (une
-même partition peut êre présente plusieurs fois), ce qui change le nombre de
-combinaisons disponibles.
-
-Pour chacune de ces combinaison, il faut maintenant tester toutes les
-permutations possibles, et, pour chacune d'elle, vérifier si cette permutation
-peut être présente ou non dans la confrontation. Si l'une d'entre elle est
-possible, alors on peut lancer le calcul, et garder le résultat au cas où une
-autre permutation de la même combinaison pourrait correspondre.
-
-.. _algorithmes: http://jean-paul.davalan.pagesperso-orange.fr/mots/comb/comb/combalgo.html
-.. _combinaisons avec répétitions: http://fr.wikipedia.org/wiki/Combinaison_avec_r%C3%A9p%C3%A9tition
-
-Assembler
----------
-
-Pour terminer, il faut pouvoir assembler les résulats. Il suffit de garder le
-nombre de mains évaluées, et le nombre de mains gagnées pour chaque joueur,
-cela peut se faire au fil de l'eau, en additionnant chaque nouveau résulat au
-résultat final. La probabilité de gagner se fait en calculant le rapport.
-
-Le code
-=======
-
-Il est disponible sur mon dépôt git_\ . Je ne vois pas d'autres optimisations
-possibles pour l'instant (sauf à partir sur de la parallélisation de code, ce
-qui est faisable, mais l'on n'est plus dans l'algorithme), mais suit preneur de
-toute nouvelle idée !
-
-.. _git: http://git.chimrod.com/?p=poker-evaluation.git;a=summary
-
-
-.. |As| image:: {filename}../images/poker/as.jpeg
- :width: 30
-
-.. |Ad| image:: {filename}../images/poker/ad.jpeg
- :width: 30
-
-.. |Ac| image:: {filename}../images/poker/ac.jpeg
- :width: 30
-
-.. |Ah| image:: {filename}../images/poker/ah.jpeg
- :width: 30
-
-.. |Ks| image:: {filename}../images/poker/ks.jpeg
- :width: 30
-
-.. |Kc| image:: {filename}../images/poker/kc.jpeg
- :width: 30
-
-.. |Kd| image:: {filename}../images/poker/kd.jpeg
- :width: 30
-
-.. |Kh| image:: {filename}../images/poker/kh.jpeg
- :width: 30
-
-.. |nbsp| unicode:: 0xA0
- :trim:
diff --git a/content/images/cv/model5.png b/content/images/cv/model5.png Binary files differnew file mode 100755 index 0000000..c80eb6f --- /dev/null +++ b/content/images/cv/model5.png diff --git a/content/images/poker/ac.jpeg b/content/images/poker/ac.jpeg Binary files differdeleted file mode 100644 index 0b26895..0000000 --- a/content/images/poker/ac.jpeg +++ /dev/null diff --git a/content/images/poker/ad.jpeg b/content/images/poker/ad.jpeg Binary files differdeleted file mode 100644 index 62efda2..0000000 --- a/content/images/poker/ad.jpeg +++ /dev/null diff --git a/content/images/poker/ah.jpeg b/content/images/poker/ah.jpeg Binary files differdeleted file mode 100644 index d158981..0000000 --- a/content/images/poker/ah.jpeg +++ /dev/null diff --git a/content/images/poker/as.jpeg b/content/images/poker/as.jpeg Binary files differdeleted file mode 100644 index 9a6765e..0000000 --- a/content/images/poker/as.jpeg +++ /dev/null diff --git a/content/images/poker/kc.jpeg b/content/images/poker/kc.jpeg Binary files differdeleted file mode 100644 index 6cc19a3..0000000 --- a/content/images/poker/kc.jpeg +++ /dev/null diff --git a/content/images/poker/kd.jpeg b/content/images/poker/kd.jpeg Binary files differdeleted file mode 100644 index b86fc41..0000000 --- a/content/images/poker/kd.jpeg +++ /dev/null diff --git a/content/images/poker/kh.jpeg b/content/images/poker/kh.jpeg Binary files differdeleted file mode 100644 index 58b95a4..0000000 --- a/content/images/poker/kh.jpeg +++ /dev/null diff --git a/content/images/poker/ks.jpeg b/content/images/poker/ks.jpeg Binary files differdeleted file mode 100644 index ca6e804..0000000 --- a/content/images/poker/ks.jpeg +++ /dev/null diff --git a/content/images/profile.png b/content/images/profile.png Binary files differnew file mode 100755 index 0000000..0353275 --- /dev/null +++ b/content/images/profile.png diff --git a/content/images/roi/roi_150.jpeg b/content/images/roi/roi_150.jpeg Binary files differdeleted file mode 100644 index 33b129a..0000000 --- a/content/images/roi/roi_150.jpeg +++ /dev/null diff --git a/content/images/roi/roi_75.jpeg b/content/images/roi/roi_75.jpeg Binary files differdeleted file mode 100644 index cd38cec..0000000 --- a/content/images/roi/roi_75.jpeg +++ /dev/null diff --git a/extras/custom.css b/extras/custom.css new file mode 100755 index 0000000..b7423c4 --- /dev/null +++ b/extras/custom.css @@ -0,0 +1,189 @@ +body { + color:#2e3440; +} + +main nav { + border-bottom: 1px solid #d8dee9; +} +hr { + background-color: #d8dee9; +} + +a.btn, +.tag-cloud a, +section#isso-thread section.auth-section p.post-action input { + background-color: #4c566a; + color:#fff; +} +a.btn:hover, +.tag-cloud a:hover { + background-color:#5e81ac; + color:#fff; +} + +a { + color: #5e81ac; +} +a:hover { + color: #5e81ac; + text-decoration:underline +} + +aside { + background-color:#2e3440; +} + +aside, +aside a { + color:#eceff4 +} +aside a:hover { + color:#d8dee9; + text-decoration:none; +} + +main article :not(pre)>code { + font-size:.8em; + white-space:nowrap; + color:#c25; + padding:1px 3px; + background-color:#f7f7f9; + border:1px solid #e1e1e8; + border-radius:3px +} +main footer p { + margin:2px; + text-align:center; + padding:0 40px; + color:#999; + font-size:11px +} +div.related-posts { + margin:15px 0; + padding-bottom:20px; + border-top:1px solid #eee; + border-bottom:1px solid #eee +} +ul.social a.sc-facebook { + background-color:#3e5b98 +} +ul.social a.sc-google { + background-color:#d93e2d +} +ul.social a.sc-instagram { + background-color:#125688 +} +ul.social a.sc-keybase { + background-color:#4c8dff +} +ul.social a.sc-pinterest { + background-color:#c92619 +} +ul.social a.sc-linkedin { + background-color:#3371b7 +} +ul.social a.sc-medium { + background-color:#00ab6b +} +ul.social a.sc-rss { + background-color:#f26109 +} +ul.social a.sc-stack-overflow { + background-color:#f90 +} +ul.social a.sc-tumblr { + background-color:#36465d +} +ul.social a.sc-twitch { + background-color:#a970ff +} +ul.social a.sc-twitter { + background-color:#4da7de +} +ul.social a.sc-youtube { + background-color:#e02a20 +} +ul.social a.sc-github, +ul.social a.sc-github-alt { + background-color:#111010 +} +ul.social a.sc-envelope { + background-color:#578ad6 +} +ul.social a.sc-reddit { + background-color:#ff4500 +} +ul.social a.sc-soundcloud { + background-color:#f50 +} +ul.social a.sc-gitlab { + background-color:#fca326 +} +ul.social a.sc-xing { + background-color:#007575 +} +ul.social a.sc-bitbucket { + background-color:#0747a6 +} +ul.social a.sc-mastodon { + background-color:#3088d4 +} +ul.social a.sc-diaspora { + color:#000; + background-color:#d5d5d5 +} +ul.social a.sc-flickr { + background-color:#ff0084 +} +ul.social a.sc-lastfm { + background-color:#d92323 +} +div.admonition div, +div.admonition p, +div.admonition pre { + margin-bottom:0 +} +div.admonition { + color:#242121; + background-color:#ccc +} +div.admonition.attention { + color:#856404; + background-color:#fff3cd +} +div.admonition.caution { + color:#856404; + background-color:#fff3cd +} +div.admonition.danger { + color:#721c24; + background-color:#f8d7da +} +div.admonition.error { + color:#721c24; + background-color:#f8d7da +} +div.admonition.hint { + color:#004085; + background-color:#cce5ff +} +div.admonition.important { + color:#155724; + background-color:#d4edda +} +div.admonition.note { + color:#2e3440; + background-color:#a3be8c +} +div.admonition.tip { + color:#004085; + background-color:#cce5ff +} +div.admonition.warning { + color:#856404; + background-color:#fff3cd +} +div.figure { + font-size:90%; + color:#6c757d +} diff --git a/pelicanconf.py b/pelicanconf.py index 9e4ca35..744f347 100755 --- a/pelicanconf.py +++ b/pelicanconf.py @@ -34,33 +34,55 @@ ARTICLE_SAVE_AS = u'{date:%Y}/{date:%m}/{slug}/index.html' STATIC_SAVE_AS=u'{path}' -THEME = 'theme' +THEME= 'Flex' +PYGMENTS_STYLE = "monokai" +SITELOGO= '/images/profile.png' +SITETITLE= 'Chimrod' +DEFAULT_LANG = "fr_FR" +CUSTOM_CSS='custom.css' +#SITESUBTITLE= 'Un site d\'informatique' +MAIN_MENU=True +LINKS= ( ("git","http://git.chimrod.com") + , ) +HOME_HIDE_TAGS=False +I18N_TEMPLATES_LANG = "en" +DEFAULT_LANG = "fr_FR" +LOCALE="fr_FR" + TYPOGRIFY = False TEMPLATE_PAGES = {'../extras/404.html': '404.html'} -STATIC_PATHS = [ - 'images', - 'resources', - '../extras/robots.txt', - '../extras/htaccess', - ] +STATIC_PATHS = \ + ( 'images' + , 'resources' + , '../extras/robots.txt' + , '../extras/htaccess' + , '../extras/custom.css' + ) -EXTRA_PATH_METADATA = { - '../extras/robots.txt': {'path': 'robots.txt'}, - '../extras/htaccess': {'path': '.htaccess'}, - } +EXTRA_PATH_METADATA = \ + { '../extras/robots.txt': \ + {'path': 'robots.txt'} + , '../extras/htaccess': \ + {'path': '.htaccess'} + , '../extras/custom.css': \ + {'path': 'custom.css'}} PLUGIN_PATHS = ['plugins'] -PLUGINS = ['related_posts', 'typogrify'] + +PLUGINS = \ + ( 'related_posts' + , 'typogrify' + , 'i18n_subsites' ) RELATED_POSTS_IGNORE_TAGS = ["Libre"] SUMMARY_MAX_LENGTH=100 -DOCUTILS_SETTINGS={ - "footnote_references":'superscript', - 'strip_comments': 'True' - } +DOCUTILS_SETTINGS = \ + { "footnote_references":'superscript' + , 'strip_comments': 'True' } +JINJA_ENVIRONMENT = {'extensions': ['jinja2.ext.i18n']} diff --git a/plugins/i18n_subsites/README.rst b/plugins/i18n_subsites/README.rst new file mode 100644 index 0000000..340109b --- /dev/null +++ b/plugins/i18n_subsites/README.rst @@ -0,0 +1,165 @@ +======================= + I18N Sub-sites Plugin +======================= + +This plugin extends the translations functionality by creating +internationalized sub-sites for the default site. + +This plugin is designed for Pelican 3.4 and later. + +What it does +============ + +1. When the content of the main site is being generated, the settings + are saved and the generation stops when content is ready to be + written. While reading source files and generating content objects, + the output queue is modified in certain ways: + + - translations that will appear as native in a different (sub-)site + will be removed + - untranslated articles will be transformed to drafts if + ``I18N_UNTRANSLATED_ARTICLES`` is ``'hide'`` (default), removed if + ``'remove'`` or kept as they are if ``'keep'``. + - untranslated pages will be transformed into hidden pages if + ``I18N_UNTRANSLATED_PAGES`` is ``'hide'`` (default), removed if + ``'remove'`` or kept as they are if ``'keep'``.'' + - additional content manipulation similar to articles and pages can + be specified for custom generators in the ``I18N_GENERATOR_INFO`` + setting. + +2. For each language specified in the ``I18N_SUBSITES`` dictionary the + settings overrides are applied to the settings from the main site + and a new sub-site is generated in the same way as with the main + site until content is ready to be written. +3. When all (sub-)sites are waiting for content writing, all removed + contents, translations and static files are interlinked across the + (sub-)sites. +4. Finally, all the output is written. + +Setting it up +============= + +For each extra used language code, a language-specific settings overrides +dictionary must be given (but can be empty) in the ``I18N_SUBSITES`` dictionary + +.. code-block:: python + + PLUGINS = ['i18n_subsites', ...] + + # mapping: language_code -> settings_overrides_dict + I18N_SUBSITES = { + 'cz': { + 'SITENAME': 'Hezkej blog', + } + } + +You must also have the following in your pelican configuration + +.. code-block:: python + JINJA_ENVIRONMENT = { + 'extensions': ['jinja2.ext.i18n'], + } + + + +Default and special overrides +----------------------------- +The settings overrides may contain arbitrary settings, however, there +are some that are handled in a special way: + +``SITEURL`` + Any overrides to this setting should ensure that there is some level + of hierarchy between all (sub-)sites, because Pelican makes all URLs + relative to ``SITEURL`` and the plugin can only cross-link between + the sites using this hierarchy. For instance, with the main site + ``http://example.com`` a sub-site ``http://example.com/de`` will + work, but ``http://de.example.com`` will not. If not overridden, the + language code (the language identifier used in the ``lang`` + metadata) is appended to the main ``SITEURL`` for each sub-site. +``OUTPUT_PATH``, ``CACHE_PATH`` + If not overridden, the language code is appended as with ``SITEURL``. + Separate cache paths are required as parser results depend on the locale. +``STATIC_PATHS``, ``THEME_STATIC_PATHS`` + If not overridden, they are set to ``[]`` and all links to static + files are cross-linked to the main site. +``THEME``, ``THEME_STATIC_DIR`` + If overridden, the logic with ``THEME_STATIC_PATHS`` does not apply. +``DEFAULT_LANG`` + This should not be overridden as the plugin changes it to the + language code of each sub-site to change what is perceived as translations. + +Localizing templates +-------------------- + +Most importantly, this plugin can use localized templates for each +sub-site. There are two approaches to having the templates localized: + +- You can set a different ``THEME`` override for each language in + ``I18N_SUBSITES``, e.g. by making a copy of a theme ``my_theme`` to + ``my_theme_lang`` and then editing the templates in the new + localized theme. This approach means you don't have to deal with + gettext ``*.po`` files, but it is harder to maintain over time. +- You use only one theme and localize the templates using the + `jinja2.ext.i18n Jinja2 extension + <http://jinja.pocoo.org/docs/templates/#i18n>`_. For a kickstart + read this `guide <./localizing_using_jinja2.rst>`_. + +Additional context variables +............................ + +It may be convenient to add language buttons to your theme in addition +to the translation links of articles and pages. These buttons could, +for example, point to the ``SITEURL`` of each (sub-)site. For this +reason the plugin adds these variables to the template context: + +``main_lang`` + The language of the main site — the original ``DEFAULT_LANG`` +``main_siteurl`` + The ``SITEURL`` of the main site — the original ``SITEURL`` +``lang_siteurls`` + An ordered dictionary, mapping all used languages to their + ``SITEURL``. The ``main_lang`` is the first key with ``main_siteurl`` + as the value. This dictionary is useful for implementing global + language buttons that show the language of the currently viewed + (sub-)site too. +``extra_siteurls`` + An ordered dictionary, subset of ``lang_siteurls``, the current + ``DEFAULT_LANG`` of the rendered (sub-)site is not included, so for + each (sub-)site ``set(extra_siteurls) == set(lang_siteurls) - + set([DEFAULT_LANG])``. This dictionary is useful for implementing + global language buttons that do not show the current language. +``relpath_to_site`` + A function that returns a relative path from the first (sub-)site to + the second (sub-)site where the (sub-)sites are identified by the + language codes given as two arguments. + +If you don't like the default ordering of the ordered dictionaries, +use a Jinja2 filter to alter the ordering. + +All the siteurls above are always absolute even in the case of +``RELATIVE_URLS == True`` (it would be to complicated to replicate the +Pelican internals for local siteurls), so you may rather use something +like ``{{ SITEURL }}/{{ relpath_to_site(DEFAULT_LANG, main_lang }}`` +to link to the main site. + +This short `howto <./implementing_language_buttons.rst>`_ shows two +example implementations of language buttons. + +Usage notes +=========== +- It is **mandatory** to specify ``lang`` metadata for each article + and page as ``DEFAULT_LANG`` is later changed for each sub-site, so + content without ``lang`` metadata would be rendered in every + (sub-)site. +- As with the original translations functionality, ``slug`` metadata + is used to group translations. It is therefore often convenient to + compensate for this by overriding the content URL (which defaults to + slug) using the ``url`` and ``save_as`` metadata. You could also + give articles e.g. ``name`` metadata and use it in ``ARTICLE_URL = + '{name}.html'``. + +Development +=========== + +- A demo and a test site is in the ``gh-pages`` branch and can be seen + at http://smartass101.github.io/pelican-plugins/ diff --git a/plugins/i18n_subsites/__init__.py b/plugins/i18n_subsites/__init__.py new file mode 100644 index 0000000..7dfbde0 --- /dev/null +++ b/plugins/i18n_subsites/__init__.py @@ -0,0 +1 @@ +from .i18n_subsites import * diff --git a/plugins/i18n_subsites/i18n_subsites.py b/plugins/i18n_subsites/i18n_subsites.py new file mode 100644 index 0000000..dc27799 --- /dev/null +++ b/plugins/i18n_subsites/i18n_subsites.py @@ -0,0 +1,462 @@ +"""i18n_subsites plugin creates i18n-ized subsites of the default site + +This plugin is designed for Pelican 3.4 and later +""" + + +import os +import six +import logging +import posixpath + +from copy import copy +from itertools import chain +from operator import attrgetter +try: + from collections.abc import OrderedDict +except ImportError: + from collections import OrderedDict +from contextlib import contextmanager +from six.moves.urllib.parse import urlparse + +import gettext +import locale + +from pelican import signals +from pelican.generators import ArticlesGenerator, PagesGenerator +from pelican.settings import configure_settings +try: + from pelican.contents import Draft +except ImportError: + from pelican.contents import Article as Draft + + +# Global vars +_MAIN_SETTINGS = None # settings dict of the main Pelican instance +_MAIN_LANG = None # lang of the main Pelican instance +_MAIN_SITEURL = None # siteurl of the main Pelican instance +_MAIN_STATIC_FILES = None # list of Static instances the main Pelican instance +_SUBSITE_QUEUE = {} # map: lang -> settings overrides +_SITE_DB = OrderedDict() # OrderedDict: lang -> siteurl +_SITES_RELPATH_DB = {} # map: (lang, base_lang) -> relpath +# map: generator -> list of removed contents that need interlinking +_GENERATOR_DB = {} +_NATIVE_CONTENT_URL_DB = {} # map: source_path -> content in its native lang +_LOGGER = logging.getLogger(__name__) + + +@contextmanager +def temporary_locale(temp_locale=None): + '''Enable code to run in a context with a temporary locale + + Resets the locale back when exiting context. + Can set a temporary locale if provided + ''' + orig_locale = locale.setlocale(locale.LC_ALL) + if temp_locale is not None: + locale.setlocale(locale.LC_ALL, temp_locale) + yield + locale.setlocale(locale.LC_ALL, orig_locale) + + +def initialize_dbs(settings): + '''Initialize internal DBs using the Pelican settings dict + + This clears the DBs for e.g. autoreload mode to work + ''' + global _MAIN_SETTINGS, _MAIN_SITEURL, _MAIN_LANG, _SUBSITE_QUEUE + _MAIN_SETTINGS = settings + _MAIN_LANG = settings['DEFAULT_LANG'] + _MAIN_SITEURL = settings['SITEURL'] + _SUBSITE_QUEUE = settings.get('I18N_SUBSITES', {}).copy() + prepare_site_db_and_overrides() + # clear databases in case of autoreload mode + _SITES_RELPATH_DB.clear() + _NATIVE_CONTENT_URL_DB.clear() + _GENERATOR_DB.clear() + + +def prepare_site_db_and_overrides(): + '''Prepare overrides and create _SITE_DB + + _SITE_DB.keys() need to be ready for filter_translations + ''' + _SITE_DB.clear() + _SITE_DB[_MAIN_LANG] = _MAIN_SITEURL + # make sure it works for both root-relative and absolute + main_siteurl = '/' if _MAIN_SITEURL == '' else _MAIN_SITEURL + for lang, overrides in _SUBSITE_QUEUE.items(): + if 'SITEURL' not in overrides: + overrides['SITEURL'] = posixpath.join(main_siteurl, lang) + _SITE_DB[lang] = overrides['SITEURL'] + # default subsite hierarchy + if 'OUTPUT_PATH' not in overrides: + overrides['OUTPUT_PATH'] = os.path.join( + _MAIN_SETTINGS['OUTPUT_PATH'], lang) + if 'CACHE_PATH' not in overrides: + overrides['CACHE_PATH'] = os.path.join( + _MAIN_SETTINGS['CACHE_PATH'], lang) + if 'STATIC_PATHS' not in overrides: + overrides['STATIC_PATHS'] = [] + if ('THEME' not in overrides and 'THEME_STATIC_DIR' not in overrides and + 'THEME_STATIC_PATHS' not in overrides): + relpath = relpath_to_site(lang, _MAIN_LANG) + overrides['THEME_STATIC_DIR'] = posixpath.join( + relpath, _MAIN_SETTINGS['THEME_STATIC_DIR']) + overrides['THEME_STATIC_PATHS'] = [] + # to change what is perceived as translations + overrides['DEFAULT_LANG'] = lang + + +def subscribe_filter_to_signals(settings): + '''Subscribe content filter to requested signals''' + for sig in settings.get('I18N_FILTER_SIGNALS', []): + sig.connect(filter_contents_translations) + + +def initialize_plugin(pelican_obj): + '''Initialize plugin variables and Pelican settings''' + if _MAIN_SETTINGS is None: + initialize_dbs(pelican_obj.settings) + subscribe_filter_to_signals(pelican_obj.settings) + + +def get_site_path(url): + '''Get the path component of an url, excludes siteurl + + also normalizes '' to '/' for relpath to work, + otherwise it could be interpreted as a relative filesystem path + ''' + path = urlparse(url).path + if path == '': + path = '/' + return path + + +def relpath_to_site(lang, target_lang): + '''Get relative path from siteurl of lang to siteurl of base_lang + + the output is cached in _SITES_RELPATH_DB + ''' + path = _SITES_RELPATH_DB.get((lang, target_lang), None) + if path is None: + siteurl = _SITE_DB.get(lang, _MAIN_SITEURL) + target_siteurl = _SITE_DB.get(target_lang, _MAIN_SITEURL) + path = posixpath.relpath(get_site_path(target_siteurl), + get_site_path(siteurl)) + _SITES_RELPATH_DB[(lang, target_lang)] = path + return path + + +def save_generator(generator): + '''Save the generator for later use + + initialize the removed content list + ''' + _GENERATOR_DB[generator] = [] + + +def article2draft(article): + '''Transform an Article to Draft''' + draft = Draft(article._content, article.metadata, article.settings, + article.source_path, article._context) + draft.status = 'draft' + return draft + + +def page2hidden_page(page): + '''Transform a Page to a hidden Page''' + page.status = 'hidden' + return page + + +class GeneratorInspector(object): + '''Inspector of generator instances''' + + generators_info = { + ArticlesGenerator: { + 'translations_lists': ['translations', 'drafts_translations'], + 'contents_lists': [('articles', 'drafts')], + 'hiding_func': article2draft, + 'policy': 'I18N_UNTRANSLATED_ARTICLES', + }, + PagesGenerator: { + 'translations_lists': ['translations', 'hidden_translations'], + 'contents_lists': [('pages', 'hidden_pages')], + 'hiding_func': page2hidden_page, + 'policy': 'I18N_UNTRANSLATED_PAGES', + }, + } + + def __init__(self, generator): + '''Identify the best known class of the generator instance + + The class ''' + self.generator = generator + self.generators_info.update(generator.settings.get( + 'I18N_GENERATORS_INFO', {})) + for cls in generator.__class__.__mro__: + if cls in self.generators_info: + self.info = self.generators_info[cls] + break + else: + self.info = {} + + def translations_lists(self): + '''Iterator over lists of content translations''' + return (getattr(self.generator, name) for name in + self.info.get('translations_lists', [])) + + def contents_list_pairs(self): + '''Iterator over pairs of normal and hidden contents''' + return (tuple(getattr(self.generator, name) for name in names) + for names in self.info.get('contents_lists', [])) + + def hiding_function(self): + '''Function for transforming content to a hidden version''' + hiding_func = self.info.get('hiding_func', lambda x: x) + return hiding_func + + def untranslated_policy(self, default): + '''Get the policy for untranslated content''' + return self.generator.settings.get(self.info.get('policy', None), + default) + + def all_contents(self): + '''Iterator over all contents''' + translations_iterator = chain(*self.translations_lists()) + return chain(translations_iterator, + *(pair[i] for pair in self.contents_list_pairs() + for i in (0, 1))) + + +def filter_contents_translations(generator): + '''Filter the content and translations lists of a generator + + Filters out + 1) translations which will be generated in a different site + 2) content that is not in the language of the currently + generated site but in that of a different site, content in a + language which has no site is generated always. The filtering + method bay be modified by the respective untranslated policy + ''' + inspector = GeneratorInspector(generator) + current_lang = generator.settings['DEFAULT_LANG'] + langs_with_sites = _SITE_DB.keys() + removed_contents = _GENERATOR_DB[generator] + + for translations in inspector.translations_lists(): + for translation in translations[:]: # copy to be able to remove + if translation.lang in langs_with_sites: + translations.remove(translation) + removed_contents.append(translation) + + hiding_func = inspector.hiding_function() + untrans_policy = inspector.untranslated_policy(default='hide') + for (contents, other_contents) in inspector.contents_list_pairs(): + for content in other_contents: # save any hidden native content first + if content.lang == current_lang: # in native lang + # save the native URL attr formatted in the current locale + _NATIVE_CONTENT_URL_DB[content.source_path] = content.url + for content in contents[:]: # copy for removing in loop + if content.lang == current_lang: # in native lang + # save the native URL attr formatted in the current locale + _NATIVE_CONTENT_URL_DB[content.source_path] = content.url + elif content.lang in langs_with_sites and untrans_policy != 'keep': + contents.remove(content) + if untrans_policy == 'hide': + other_contents.append(hiding_func(content)) + elif untrans_policy == 'remove': + removed_contents.append(content) + + +def install_templates_translations(generator): + '''Install gettext translations in the jinja2.Environment + + Only if the 'jinja2.ext.i18n' jinja2 extension is enabled + the translations for the current DEFAULT_LANG are installed. + ''' + if 'JINJA_ENVIRONMENT' in generator.settings: # pelican 3.7+ + jinja_extensions = generator.settings['JINJA_ENVIRONMENT'].get( + 'extensions', []) + else: + jinja_extensions = generator.settings['JINJA_EXTENSIONS'] + + if 'jinja2.ext.i18n' in jinja_extensions: + domain = generator.settings.get('I18N_GETTEXT_DOMAIN', 'messages') + localedir = generator.settings.get('I18N_GETTEXT_LOCALEDIR') + if localedir is None: + localedir = os.path.join(generator.theme, 'translations') + current_lang = generator.settings['DEFAULT_LANG'] + if current_lang == generator.settings.get('I18N_TEMPLATES_LANG', + _MAIN_LANG): + translations = gettext.NullTranslations() + else: + langs = [current_lang] + try: + translations = gettext.translation(domain, localedir, langs) + except (IOError, OSError): + _LOGGER.error(( + "Cannot find translations for language '{}' in '{}' with " + "domain '{}'. Installing NullTranslations.").format( + langs[0], localedir, domain)) + translations = gettext.NullTranslations() + newstyle = generator.settings.get('I18N_GETTEXT_NEWSTYLE', True) + generator.env.install_gettext_translations(translations, newstyle) + + +def add_variables_to_context(generator): + '''Adds useful iterable variables to template context''' + context = generator.context # minimize attr lookup + context['relpath_to_site'] = relpath_to_site + context['main_siteurl'] = _MAIN_SITEURL + context['main_lang'] = _MAIN_LANG + context['lang_siteurls'] = _SITE_DB + current_lang = generator.settings['DEFAULT_LANG'] + extra_siteurls = _SITE_DB.copy() + extra_siteurls.pop(current_lang) + context['extra_siteurls'] = extra_siteurls + + +def interlink_translations(content): + '''Link content to translations in their main language + + so the URL (including localized month names) of the different subsites + will be honored + ''' + lang = content.lang + # sort translations by lang + content.translations.sort(key=attrgetter('lang')) + for translation in content.translations: + relpath = relpath_to_site(lang, translation.lang) + url = _NATIVE_CONTENT_URL_DB[translation.source_path] + translation.override_url = posixpath.join(relpath, url) + + +def interlink_translated_content(generator): + '''Make translations link to the native locations + + for generators that may contain translated content + ''' + inspector = GeneratorInspector(generator) + for content in inspector.all_contents(): + interlink_translations(content) + + +def interlink_removed_content(generator): + '''For all contents removed from generation queue update interlinks + + link to the native location + ''' + current_lang = generator.settings['DEFAULT_LANG'] + for content in _GENERATOR_DB[generator]: + url = _NATIVE_CONTENT_URL_DB[content.source_path] + relpath = relpath_to_site(current_lang, content.lang) + content.override_url = posixpath.join(relpath, url) + + +def interlink_static_files(generator): + '''Add links to static files in the main site if necessary''' + if generator.settings['STATIC_PATHS'] != []: + return # customized STATIC_PATHS + try: # minimize attr lookup + static_content = generator.context['static_content'] + except KeyError: + static_content = generator.context['filenames'] + relpath = relpath_to_site(generator.settings['DEFAULT_LANG'], _MAIN_LANG) + for staticfile in _MAIN_STATIC_FILES: + if staticfile.get_relative_source_path() not in static_content: + staticfile = copy(staticfile) # prevent override in main site + staticfile.override_url = posixpath.join(relpath, staticfile.url) + try: + generator.add_source_path(staticfile, static=True) + except TypeError: + generator.add_source_path(staticfile) + + +def save_main_static_files(static_generator): + '''Save the static files generated for the main site''' + global _MAIN_STATIC_FILES + # test just for current lang as settings change in autoreload mode + if static_generator.settings['DEFAULT_LANG'] == _MAIN_LANG: + _MAIN_STATIC_FILES = static_generator.staticfiles + + +def update_generators(): + '''Update the context of all generators + + Ads useful variables and translations into the template context + and interlink translations + ''' + for generator in _GENERATOR_DB.keys(): + install_templates_translations(generator) + add_variables_to_context(generator) + interlink_static_files(generator) + interlink_removed_content(generator) + interlink_translated_content(generator) + + +def get_pelican_cls(settings): + '''Get the Pelican class requested in settings''' + cls = settings['PELICAN_CLASS'] + if isinstance(cls, six.string_types): + module, cls_name = cls.rsplit('.', 1) + module = __import__(module) + cls = getattr(module, cls_name) + return cls + + +def create_next_subsite(pelican_obj): + '''Create the next subsite using the lang-specific config + + If there are no more subsites in the generation queue, update all + the generators (interlink translations and removed content, add + variables and translations to template context). Otherwise get the + language and overrides for next the subsite in the queue and apply + overrides. Then generate the subsite using a PELICAN_CLASS + instance and its run method. Finally, restore the previous locale. + ''' + global _MAIN_SETTINGS + if len(_SUBSITE_QUEUE) == 0: + _LOGGER.debug( + 'i18n: Updating cross-site links and context of all generators.') + update_generators() + _MAIN_SETTINGS = None # to initialize next time + else: + with temporary_locale(): + settings = _MAIN_SETTINGS.copy() + lang, overrides = _SUBSITE_QUEUE.popitem() + settings.update(overrides) + settings = configure_settings(settings) # to set LOCALE, etc. + cls = get_pelican_cls(settings) + + new_pelican_obj = cls(settings) + _LOGGER.debug(("Generating i18n subsite for language '{}' " + "using class {}").format(lang, cls)) + new_pelican_obj.run() + + +# map: signal name -> function name +_SIGNAL_HANDLERS_DB = { + 'get_generators': initialize_plugin, + 'article_generator_pretaxonomy': filter_contents_translations, + 'page_generator_finalized': filter_contents_translations, + 'get_writer': create_next_subsite, + 'static_generator_finalized': save_main_static_files, + 'generator_init': save_generator, +} + + +def register(): + '''Register the plugin only if required signals are available''' + for sig_name in _SIGNAL_HANDLERS_DB.keys(): + if not hasattr(signals, sig_name): + _LOGGER.error(( + 'The i18n_subsites plugin requires the {} ' + 'signal available for sure in Pelican 3.4.0 and later, ' + 'plugin will not be used.').format(sig_name)) + return + + for sig_name, handler in _SIGNAL_HANDLERS_DB.items(): + sig = getattr(signals, sig_name) + sig.connect(handler) diff --git a/plugins/i18n_subsites/implementing_language_buttons.rst b/plugins/i18n_subsites/implementing_language_buttons.rst new file mode 100644 index 0000000..55b7bf3 --- /dev/null +++ b/plugins/i18n_subsites/implementing_language_buttons.rst @@ -0,0 +1,128 @@ +----------------------------- +Implementing language buttons +----------------------------- + +Each article with translations has translations links, but that's the +only way to switch between language subsites. + +For this reason it is convenient to add language buttons to the top +menu bar to make it simple to switch between the language subsites on +all pages. + +Example designs +--------------- + +Language buttons showing other available languages +.................................................. + +The ``extra_siteurls`` dictionary is a mapping of all other (not the +``DEFAULT_LANG`` of the current (sub-)site) languages to the +``SITEURL`` of the respective (sub-)sites + +.. code-block:: jinja + + <!-- SNIP --> + <nav><ul> + {% if extra_siteurls %} + {% for lang, url in extra_siteurls.items() %} + <li><a href="{{ url }}/">{{ lang }}</a></li> + {% endfor %} + <!-- separator --> + <li style="background-color: white; padding: 5px;"> </li> + {% endif %} + {% for title, link in MENUITEMS %} + <!-- SNIP --> + +Notice the slash ``/`` after ``{{ url }}``, this makes sure it works +with local development when ``SITEURL == ''``. + +Language buttons showing all available languages, current is active +................................................................... + +The ``lang_subsites`` dictionary is a mapping of all languages to the +``SITEURL`` of the respective (sub-)sites. This template sets the +language of the current (sub-)site as active. + +.. code-block:: jinja + + <!-- SNIP --> + <nav><ul> + {% if lang_siteurls %} + {% for lang, url in lang_siteurls.items() %} + <li{% if lang == DEFAULT_LANG %} class="active"{% endif %}><a href="{{ url }}/">{{ lang }}</a></li> + {% endfor %} + <!-- separator --> + <li style="background-color: white; padding: 5px;"> </li> + {% endif %} + {% for title, link in MENUITEMS %} + <!-- SNIP --> + + +Tips and tricks +--------------- + +Showing more than language codes +................................ + +If you want the language buttons to show e.g. the names of the +languages or flags [#flags]_, not just the language codes, you can use +a jinja filter to translate the language codes + + +.. code-block:: python + + languages_lookup = { + 'en': 'English', + 'cz': 'Čeština', + } + + def lookup_lang_name(lang_code): + return languages_lookup[lang_code] + + JINJA_FILTERS = { + ... + 'lookup_lang_name': lookup_lang_name, + } + +And then the link content becomes + +.. code-block:: jinja + + <!-- SNIP --> + {% for lang, siteurl in lang_siteurls.items() %} + <li{% if lang == DEFAULT_LANG %} class="active"{% endif %}><a href="{{ siteurl }}/">{{ lang | lookup_lang_name }}</a></li> + {% endfor %} + <!-- SNIP --> + + +Changing the order of language buttons +...................................... + +Because ``lang_siteurls`` and ``extra_siteurls`` are instances of +``OrderedDict`` with ``main_lang`` being always the first key, you can +change the order through a jinja filter. + +.. code-block:: python + + def my_ordered_items(ordered_dict): + items = list(ordered_dict.items()) + # swap first and last using tuple unpacking + items[0], items[-1] = items[-1], items[0] + return items + + JINJA_FILTERS = { + ... + 'my_ordered_items': my_ordered_items, + } + +And then the ``for`` loop line in the template becomes + +.. code-block:: jinja + + <!-- SNIP --> + {% for lang, siteurl in lang_siteurls | my_ordered_items %} + <!-- SNIP --> + + +.. [#flags] Although it may look nice, `w3 discourages it + <http://www.w3.org/TR/i18n-html-tech-lang/#ri20040808.173208643>`_. diff --git a/plugins/i18n_subsites/localizing_using_jinja2.rst b/plugins/i18n_subsites/localizing_using_jinja2.rst new file mode 100644 index 0000000..a28bedd --- /dev/null +++ b/plugins/i18n_subsites/localizing_using_jinja2.rst @@ -0,0 +1,202 @@ +----------------------------- +Localizing themes with Jinja2 +----------------------------- + +1. Localize templates +--------------------- + +To enable the |ext| extension in your templates, you must add it to +``JINJA_ENVIRONMENT`` in your Pelican configuration + +.. code-block:: python + + JINJA_ENVIRONMENT = { + 'extensions': ['jinja2.ext.i18n', ...] + } + +Then follow the `Jinja2 templating documentation for the I18N plugin +<http://jinja.pocoo.org/docs/templates/#i18n>`_ to make your templates +localizable. This usually means surrounding strings with the ``{% +trans %}`` directive or using ``gettext()`` in expressions + +.. code-block:: jinja + + {% trans %}translatable content{% endtrans %} + {{ gettext('a translatable string') }} + +For pluralization support, etc. consult the documentation. + +To enable `newstyle gettext calls +<http://jinja.pocoo.org/docs/extensions/#newstyle-gettext>`_ the +``I18N_GETTEXT_NEWSTYLE`` config variable must be set to ``True`` +(default). + +.. |ext| replace:: ``jinja2.ext.i18n`` + +2. Specify translations location +-------------------------------- + +The |ext| extension uses the `Python gettext library +<http://docs.python.org/library/gettext.html>`_ for translating +strings. + +In your Pelican config you can give the path in which to look for +translations in the ``I18N_GETTEXT_LOCALEDIR`` variable. If not given, +it is assumed to be the ``translations`` subfolder in the top folder +of the theme specified by ``THEME``. + +The domain of the translations (the name of each translation file is +``domain.mo``) is controlled by the ``I18N_GETTEXT_DOMAIN`` config +variable (defaults to ``messages``). + +Example +....... + +With the following in your Pelican settings file + +.. code-block:: python + + I18N_GETTEXT_LOCALEDIR = 'some/path/' + I18N_GETTEXT_DOMAIN = 'my_domain' + +the translation for language 'cz' will be expected to be in +``some/path/cz/LC_MESSAGES/my_domain.mo`` + + +3. Extract translatable strings and translate them +-------------------------------------------------- + +There are many ways to extract translatable strings and create +``gettext`` compatible translations. You can create the ``*.po`` and +``*.mo`` message catalog files yourself, or you can use some helper +tool as described in `the Python gettext library tutorial +<http://docs.python.org/library/gettext.html#internationalizing-your-programs-and-modules>`_. + +You of course don't need to provide a translation for the language in +which the templates are written which is assumed to be the original +``DEFAULT_LANG``. This can be overridden in the +``I18N_TEMPLATES_LANG`` variable. + +Recommended tool: babel +....................... + +`Babel <http://babel.pocoo.org/>`_ makes it easy to extract +translatable strings from the localized Jinja2 templates and assists +with creating translations as documented in this `Jinja2-Babel +tutorial +<http://pythonhosted.org/Flask-Babel/#translating-applications>`_ +[#flask]_ on which the following is based. + +1. Add babel mapping +~~~~~~~~~~~~~~~~~~~~ + +Let's assume that you are localizing a theme in ``themes/my_theme/`` +and that you use the default settings, i.e. the default domain +``messages`` and will put the translations in the ``translations`` +subdirectory of the theme directory as +``themes/my_theme/translations/``. + +It is up to you where to store babel mappings and translation files +templates (``*.pot``), but a convenient place is to put them in +``themes/my_theme/`` and work in that directory. From now on let's +assume that it will be our current working directory (CWD). + +To tell babel to extract translatable strings from the templates +create a mapping file ``babel.cfg`` with the following line + +.. code-block:: cfg + + [jinja2: templates/**.html] + + +2. Extract translatable strings from templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Run the following command to create a ``messages.pot`` message catalog +template file from extracted translatable strings + +.. code-block:: bash + + pybabel extract --mapping babel.cfg --output messages.pot ./ + + +3. Initialize message catalogs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to translate the template to language ``lang``, run the +following command to create a message catalog +``translations/lang/LC_MESSAGES/messages.po`` using the template +``messages.pot`` + +.. code-block:: bash + + pybabel init --input-file messages.pot --output-dir translations/ --locale lang --domain messages + +babel expects ``lang`` to be a valid locale identifier, so if e.g. you +are translating for language ``cz`` but the corresponding locale is +``cs``, you have to use the locale identifier. Nevertheless, the +gettext infrastructure should later correctly find the locale for a +given language. + +4. Fill the message catalogs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The message catalog files format is quite intuitive, it is fully +documented in the `GNU gettext manual +<http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files>`_. Essentially, +you fill in the ``msgstr`` strings + + +.. code-block:: po + + msgid "just a simple string" + msgstr "jenom jednoduchý řetězec" + + msgid "" + "some multiline string" + "looks like this" + msgstr "" + "nějaký více řádkový řetězec" + "vypadá takto" + +You might also want to remove ``#,fuzzy`` flags once the translation +is complete and reviewed to show that it can be compiled. + +5. Compile the message catalogs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The message catalogs must be compiled into binary format using this +command + +.. code-block:: bash + + pybabel compile --directory translations/ --domain messages + +This command might complain about "fuzzy" translations, which means +you should review the translations and once done, remove the fuzzy +flag line. + +(6.) Update the catalogs when templates change +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you add any translatable patterns into your templates, you have to +update your message catalogs too. First you extract a new message +catalog template as described in the 2. step. Then you run the +following command [#pybabel_error]_ + +.. code-block:: bash + + pybabel update --input-file messages.pot --output-dir translations/ --domain messages + +This will merge the new patterns with the old ones. Once you review +and fill them, you have to recompile them as described in the 5. step. + +.. [#flask] Although the tutorial is focused on Flask-based web + applications, the linked translation tutorial is not + Flask-specific. +.. [#pybabel_error] If you get an error ``TypeError: must be str, not + bytes`` with Python 3.3, it is likely you are + suffering from this `bug + <https://github.com/mitsuhiko/flask-babel/issues/43>`_. + Until the fix is released, you can use babel with + Python 2.7. diff --git a/plugins/i18n_subsites/test_data/content/images/img.png b/plugins/i18n_subsites/test_data/content/images/img.png new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/i18n_subsites/test_data/content/images/img.png diff --git a/plugins/i18n_subsites/test_data/content/pages/hidden-page-cz.rst b/plugins/i18n_subsites/test_data/content/pages/hidden-page-cz.rst new file mode 100644 index 0000000..c282faa --- /dev/null +++ b/plugins/i18n_subsites/test_data/content/pages/hidden-page-cz.rst @@ -0,0 +1,7 @@ +404 stránka +=========== +:slug: 404 +:lang: cz +:status: hidden + +Jednoduchá 404 stránka. diff --git a/plugins/i18n_subsites/test_data/content/pages/hidden-page-de.rst b/plugins/i18n_subsites/test_data/content/pages/hidden-page-de.rst new file mode 100644 index 0000000..d8410a1 --- /dev/null +++ b/plugins/i18n_subsites/test_data/content/pages/hidden-page-de.rst @@ -0,0 +1,7 @@ +Eine 404 Seite +============== +:slug: 404 +:lang: de +:status: hidden + +Eine einfache 404 Seite. diff --git a/plugins/i18n_subsites/test_data/content/pages/hidden-page-en.rst b/plugins/i18n_subsites/test_data/content/pages/hidden-page-en.rst new file mode 100644 index 0000000..74a97d7 --- /dev/null +++ b/plugins/i18n_subsites/test_data/content/pages/hidden-page-en.rst @@ -0,0 +1,7 @@ +A 404 page +========== +:slug: 404 +:lang: en +:status: hidden + +A simple 404 page. diff --git a/plugins/i18n_subsites/test_data/content/pages/untranslated-page.rst b/plugins/i18n_subsites/test_data/content/pages/untranslated-page.rst new file mode 100644 index 0000000..ae4c2b8 --- /dev/null +++ b/plugins/i18n_subsites/test_data/content/pages/untranslated-page.rst @@ -0,0 +1,5 @@ +Untranslated page +================= +:lang: en + +This page has no translation. diff --git a/plugins/i18n_subsites/test_data/content/translated_article-cz.rst b/plugins/i18n_subsites/test_data/content/translated_article-cz.rst new file mode 100644 index 0000000..555a69d --- /dev/null +++ b/plugins/i18n_subsites/test_data/content/translated_article-cz.rst @@ -0,0 +1,8 @@ +Přeložený článek +================ +:slug: translated-article +:lang: cz +:date: 2014-09-15 + +Jednoduchý článek s překlady. +Zde je odkaz na `nějaký obrázek <{filename}/images/img.png>`_. diff --git a/plugins/i18n_subsites/test_data/content/translated_article-de.rst b/plugins/i18n_subsites/test_data/content/translated_article-de.rst new file mode 100644 index 0000000..01bf565 --- /dev/null +++ b/plugins/i18n_subsites/test_data/content/translated_article-de.rst @@ -0,0 +1,8 @@ +Ein übersetzter Artikel +======================= +:slug: translated-article +:lang: de +:date: 2014-09-14 + +Ein einfacher Artikel mit einer Übersetzung. +Hier ist ein Link zur `einigem Bild <{filename}/images/img.png>`_. diff --git a/plugins/i18n_subsites/test_data/content/translated_article-en.rst b/plugins/i18n_subsites/test_data/content/translated_article-en.rst new file mode 100644 index 0000000..d7f5dad --- /dev/null +++ b/plugins/i18n_subsites/test_data/content/translated_article-en.rst @@ -0,0 +1,8 @@ +A translated article +==================== +:slug: translated-article +:lang: en +:date: 2014-09-13 + +A simple article with a translation. +Here is a link to `some image <{filename}/images/img.png>`_. diff --git a/plugins/i18n_subsites/test_data/content/untranslated_article-en.rst b/plugins/i18n_subsites/test_data/content/untranslated_article-en.rst new file mode 100644 index 0000000..867ae5d --- /dev/null +++ b/plugins/i18n_subsites/test_data/content/untranslated_article-en.rst @@ -0,0 +1,9 @@ +An untranslated article +======================= +:date: 2014-07-14 +:lang: en + +An article without a translation. +Here is a link to an `untranslated page`_ + +.. _`untranslated page`: {filename}/pages/untranslated-page.rst diff --git a/plugins/i18n_subsites/test_data/localized_theme/babel.cfg b/plugins/i18n_subsites/test_data/localized_theme/babel.cfg new file mode 100644 index 0000000..4406732 --- /dev/null +++ b/plugins/i18n_subsites/test_data/localized_theme/babel.cfg @@ -0,0 +1,2 @@ +[jinja2: templates/**.html] + diff --git a/plugins/i18n_subsites/test_data/localized_theme/messages.pot b/plugins/i18n_subsites/test_data/localized_theme/messages.pot new file mode 100644 index 0000000..578917f --- /dev/null +++ b/plugins/i18n_subsites/test_data/localized_theme/messages.pot @@ -0,0 +1,23 @@ +# Translations template for PROJECT. +# Copyright (C) 2014 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR <EMAIL@ADDRESS>, 2014. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2014-07-13 12:25+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 1.3\n" + +#: templates/base.html:3 +msgid "Welcome to our" +msgstr "" + diff --git a/plugins/i18n_subsites/test_data/localized_theme/static/style.css b/plugins/i18n_subsites/test_data/localized_theme/static/style.css new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/i18n_subsites/test_data/localized_theme/static/style.css diff --git a/plugins/i18n_subsites/test_data/localized_theme/templates/base.html b/plugins/i18n_subsites/test_data/localized_theme/templates/base.html new file mode 100644 index 0000000..a24eb1d --- /dev/null +++ b/plugins/i18n_subsites/test_data/localized_theme/templates/base.html @@ -0,0 +1,7 @@ +{% extends "!simple/base.html" %} + +{% block title %}{% trans %}Welcome to our{% endtrans %} {{ SITENAME }}{% endblock %} +{% block head %} +{{ super() }} +<link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/style.css" /> +{% endblock %} diff --git a/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.mo b/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.mo Binary files differnew file mode 100644 index 0000000..2390102 --- /dev/null +++ b/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.mo diff --git a/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.po b/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.po new file mode 100644 index 0000000..2eb4efb --- /dev/null +++ b/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.po @@ -0,0 +1,23 @@ +# German translations for PROJECT. +# Copyright (C) 2014 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR <EMAIL@ADDRESS>, 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2014-07-13 12:25+0200\n" +"PO-Revision-Date: 2014-07-13 12:26+0200\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: de <LL@li.org>\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 1.3\n" + +#: templates/base.html:3 +msgid "Welcome to our" +msgstr "Willkommen Sie zur unserer" + diff --git a/plugins/i18n_subsites/test_data/output/an-untranslated-article.html b/plugins/i18n_subsites/test_data/output/an-untranslated-article.html new file mode 100644 index 0000000..f2f6494 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/an-untranslated-article.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>Testing site - An untranslated article</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testing site Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/theme/style.css" /> + + + + + +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/">Testing site</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li><a href="http://example.com/test/pages/untranslated-page.html">Untranslated page</a></li> + <li class="active"><a href="http://example.com/test/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> +<section id="content" class="body"> + <header> + <h2 class="entry-title"> + <a href="http://example.com/test/an-untranslated-article.html" rel="bookmark" + title="Permalink to An untranslated article">An untranslated article</a></h2> + + </header> + <footer class="post-info"> + <time class="published" datetime="2014-07-14T00:00:00+00:00"> + Mon 14 July 2014 + </time> + <address class="vcard author"> + By <a class="url fn" href="http://example.com/test/author/the-tester.html">The Tester</a> + </address> + <div class="category"> + Category: <a href="http://example.com/test/category/misc.html">misc</a> + </div> + </footer><!-- /.post-info --> + <div class="entry-content"> + <p>An article without a translation. +Here is a link to an <a class="reference external" href="http://example.com/test/pages/untranslated-page.html">untranslated page</a></p> + + </div><!-- /.entry-content --> +</section> + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/cz/an-untranslated-article-en.html b/plugins/i18n_subsites/test_data/output/cz/an-untranslated-article-en.html new file mode 100644 index 0000000..4673ee5 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/cz/an-untranslated-article-en.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>Testovací stránka - An untranslated article</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testovací stránka Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/cz/../theme/style.css" /> + + + + + +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/cz/">Testovací stránka</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li class="active"><a href="http://example.com/test/cz/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> +<section id="content" class="body"> + <header> + <h2 class="entry-title"> + <a href="http://example.com/test/cz/an-untranslated-article-en.html" rel="bookmark" + title="Permalink to An untranslated article">An untranslated article</a></h2> + + </header> + <footer class="post-info"> + <time class="published" datetime="2014-07-14T00:00:00+00:00"> + Mon 14 July 2014 + </time> + <address class="vcard author"> + By <a class="url fn" href="http://example.com/test/cz/author/test-testovic.html">Test Testovič</a> + </address> + <div class="category"> + Category: <a href="http://example.com/test/cz/category/misc.html">misc</a> + </div> + </footer><!-- /.post-info --> + <div class="entry-content"> + <p>An article without a translation. +Here is a link to an <a class="reference external" href="http://example.com/test/pages/untranslated-page.html">untranslated page</a></p> + + </div><!-- /.entry-content --> +</section> + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/cz/feeds_all.atom.xml b/plugins/i18n_subsites/test_data/output/cz/feeds_all.atom.xml new file mode 100644 index 0000000..7415e1f --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/cz/feeds_all.atom.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<feed xmlns="http://www.w3.org/2005/Atom"><title>Testovací stránka</title><link href="http://example.com/test/cz/" rel="alternate"></link><link href="http://example.com/test/feeds_all.atom.xml" rel="self"></link><id>http://example.com/test/cz/</id><updated>2014-09-15T00:00:00+00:00</updated><entry><title>Přeložený článek</title><link href="http://example.com/test/cz/translated-article.html" rel="alternate"></link><published>2014-09-15T00:00:00+00:00</published><updated>2014-09-15T00:00:00+00:00</updated><author><name>Test Testovič</name></author><id>tag:example.com,2014-09-15:/test/cz/translated-article.html</id><content type="html"><p>Jednoduchý článek s překlady. +Zde je odkaz na <a class="reference external" href="http://example.com/test/images/img.png">nějaký obrázek</a>.</p> +</content><category term="misc"></category></entry><entry><title>Ein übersetzter Artikel</title><link href="http://example.com/test/de/translated-article.html" rel="alternate"></link><published>2014-09-14T00:00:00+00:00</published><updated>2014-09-14T00:00:00+00:00</updated><author><name>Test Testovič</name></author><id>tag:example.com,2014-09-14:/test/de/translated-article.html</id><content type="html"><p>Ein einfacher Artikel mit einer Übersetzung. +Hier ist ein Link zur <a class="reference external" href="http://example.com/test/images/img.png">einigem Bild</a>.</p> +</content><category term="misc"></category></entry><entry><title>A translated article</title><link href="http://example.com/test/translated-article.html" rel="alternate"></link><published>2014-09-13T00:00:00+00:00</published><updated>2014-09-13T00:00:00+00:00</updated><author><name>Test Testovič</name></author><id>tag:example.com,2014-09-13:/test/translated-article.html</id><content type="html"><p>A simple article with a translation. +Here is a link to <a class="reference external" href="http://example.com/test/images/img.png">some image</a>.</p> +</content><category term="misc"></category></entry><entry><title>An untranslated article</title><link href="http://example.com/test/cz/an-untranslated-article-en.html" rel="alternate"></link><published>2014-07-14T00:00:00+00:00</published><updated>2014-07-14T00:00:00+00:00</updated><author><name>Test Testovič</name></author><id>tag:example.com,2014-07-14:/test/cz/an-untranslated-article-en.html</id><content type="html"><p>An article without a translation. +Here is a link to an <a class="reference external" href="http://example.com/test/pages/untranslated-page.html">untranslated page</a></p> +</content><category term="misc"></category></entry></feed>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/cz/index.html b/plugins/i18n_subsites/test_data/output/cz/index.html new file mode 100644 index 0000000..c5fca32 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/cz/index.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="cz"> +<head> + <title>Welcome to our Testovací stránka</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testovací stránka Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/cz/../theme/style.css" /> +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/cz/">Testovací stránka</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li><a href="http://example.com/test/cz/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> +<section id="content"> +<h2>All articles</h2> + +<ol id="post-list"> + <li><article class="hentry"> + <header> <h2 class="entry-title"><a href="http://example.com/test/cz/translated-article.html" rel="bookmark" title="Permalink to Přeložený článek">Přeložený článek</a></h2> </header> + <footer class="post-info"> + <time class="published" datetime="2014-09-15T00:00:00+00:00"> Mon 15 September 2014 </time> + <address class="vcard author">By + <a class="url fn" href="http://example.com/test/cz/author/test-testovic.html">Test Testovič</a> + </address> + </footer><!-- /.post-info --> + <div class="entry-content"> <p>Jednoduchý článek s překlady. +Zde je odkaz na <a class="reference external" href="http://example.com/test/images/img.png">nějaký obrázek</a>.</p> + </div><!-- /.entry-content --> + </article></li> + <li><article class="hentry"> + <header> <h2 class="entry-title"><a href="http://example.com/test/cz/an-untranslated-article-en.html" rel="bookmark" title="Permalink to An untranslated article">An untranslated article</a></h2> </header> + <footer class="post-info"> + <time class="published" datetime="2014-07-14T00:00:00+00:00"> Mon 14 July 2014 </time> + <address class="vcard author">By + <a class="url fn" href="http://example.com/test/cz/author/test-testovic.html">Test Testovič</a> + </address> + </footer><!-- /.post-info --> + <div class="entry-content"> <p>An article without a translation. +Here is a link to an <a class="reference external" href="http://example.com/test/pages/untranslated-page.html">untranslated page</a></p> + </div><!-- /.entry-content --> + </article></li> +</ol><!-- /#posts-list --> +</section><!-- /#content --> + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/cz/pages/404.html b/plugins/i18n_subsites/test_data/output/cz/pages/404.html new file mode 100644 index 0000000..884203b --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/cz/pages/404.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html lang="cz"> +<head> + <title>Testovací stránka - 404 stránka</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testovací stránka Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/cz/../theme/style.css" /> + + + <link rel="alternate" hreflang="de" href="http://example.com/test/cz/../de/pages/404.html"> + <link rel="alternate" hreflang="en" href="http://example.com/test/cz/../pages/404.html"> + +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/cz/">Testovací stránka</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li><a href="http://example.com/test/cz/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> + <h1>404 stránka</h1> + Translations: +<a href="http://example.com/test/cz/../de/pages/404.html" hreflang="de">de</a> +<a href="http://example.com/test/cz/../pages/404.html" hreflang="en">en</a> + + + <p>Jednoduchá 404 stránka.</p> + + + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html> diff --git a/plugins/i18n_subsites/test_data/output/cz/translated-article.html b/plugins/i18n_subsites/test_data/output/cz/translated-article.html new file mode 100644 index 0000000..5a5e545 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/cz/translated-article.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<html lang="cz"> +<head> + <title>Testovací stránka - Přeložený článek</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testovací stránka Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/cz/../theme/style.css" /> + + + <link rel="alternate" hreflang="de" href="http://example.com/test/cz/../de/translated-article.html"> + <link rel="alternate" hreflang="en" href="http://example.com/test/cz/../translated-article.html"> + + + + +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/cz/">Testovací stránka</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li class="active"><a href="http://example.com/test/cz/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> +<section id="content" class="body"> + <header> + <h2 class="entry-title"> + <a href="http://example.com/test/cz/translated-article.html" rel="bookmark" + title="Permalink to Přeložený článek">Přeložený článek</a></h2> + Translations: +<a href="http://example.com/test/cz/../de/translated-article.html" hreflang="de">de</a> +<a href="http://example.com/test/cz/../translated-article.html" hreflang="en">en</a> + + </header> + <footer class="post-info"> + <time class="published" datetime="2014-09-15T00:00:00+00:00"> + Mon 15 September 2014 + </time> + <address class="vcard author"> + By <a class="url fn" href="http://example.com/test/cz/author/test-testovic.html">Test Testovič</a> + </address> + <div class="category"> + Category: <a href="http://example.com/test/cz/category/misc.html">misc</a> + </div> + </footer><!-- /.post-info --> + <div class="entry-content"> + <p>Jednoduchý článek s překlady. +Zde je odkaz na <a class="reference external" href="http://example.com/test/images/img.png">nějaký obrázek</a>.</p> + + </div><!-- /.entry-content --> +</section> + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/de/drafts/an-untranslated-article-en.html b/plugins/i18n_subsites/test_data/output/de/drafts/an-untranslated-article-en.html new file mode 100644 index 0000000..f01dc31 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/de/drafts/an-untranslated-article-en.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>Testseite - An untranslated article</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testseite Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/de/../theme/style.css" /> + + + + + +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/de/">Testseite</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li class="active"><a href="http://example.com/test/de/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> +<section id="content" class="body"> + <header> + <h2 class="entry-title"> + <a href="http://example.com/test/de/drafts/an-untranslated-article-en.html" rel="bookmark" + title="Permalink to An untranslated article">An untranslated article</a></h2> + + </header> + <footer class="post-info"> + <time class="published" datetime="2014-07-14T00:00:00+00:00"> + Mo 14 Juli 2014 + </time> + <address class="vcard author"> + By <a class="url fn" href="http://example.com/test/de/author/der-tester.html">Der Tester</a> + </address> + <div class="category"> + Category: <a href="http://example.com/test/de/category/misc.html">misc</a> + </div> + </footer><!-- /.post-info --> + <div class="entry-content"> + <p>An article without a translation. +Here is a link to an <a class="reference external" href="http://example.com/test/de/pages/untranslated-page-en.html">untranslated page</a></p> + + </div><!-- /.entry-content --> +</section> + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/de/feeds_all.atom.xml b/plugins/i18n_subsites/test_data/output/de/feeds_all.atom.xml new file mode 100644 index 0000000..8d845fe --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/de/feeds_all.atom.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<feed xmlns="http://www.w3.org/2005/Atom"><title>Testseite</title><link href="http://example.com/test/de/" rel="alternate"></link><link href="http://example.com/test/feeds_all.atom.xml" rel="self"></link><id>http://example.com/test/de/</id><updated>2014-09-15T00:00:00+00:00</updated><entry><title>Přeložený článek</title><link href="http://example.com/test/cz/translated-article.html" rel="alternate"></link><published>2014-09-15T00:00:00+00:00</published><updated>2014-09-15T00:00:00+00:00</updated><author><name>Der Tester</name></author><id>tag:example.com,2014-09-15:/test/cz/translated-article.html</id><content type="html"><p>Jednoduchý článek s překlady. +Zde je odkaz na <a class="reference external" href="http://example.com/test/images/img.png">nějaký obrázek</a>.</p> +</content><category term="misc"></category></entry><entry><title>Ein übersetzter Artikel</title><link href="http://example.com/test/de/translated-article.html" rel="alternate"></link><published>2014-09-14T00:00:00+00:00</published><updated>2014-09-14T00:00:00+00:00</updated><author><name>Der Tester</name></author><id>tag:example.com,2014-09-14:/test/de/translated-article.html</id><content type="html"><p>Ein einfacher Artikel mit einer Übersetzung. +Hier ist ein Link zur <a class="reference external" href="http://example.com/test/images/img.png">einigem Bild</a>.</p> +</content><category term="misc"></category></entry><entry><title>A translated article</title><link href="http://example.com/test/translated-article.html" rel="alternate"></link><published>2014-09-13T00:00:00+00:00</published><updated>2014-09-13T00:00:00+00:00</updated><author><name>Der Tester</name></author><id>tag:example.com,2014-09-13:/test/translated-article.html</id><content type="html"><p>A simple article with a translation. +Here is a link to <a class="reference external" href="http://example.com/test/images/img.png">some image</a>.</p> +</content><category term="misc"></category></entry></feed>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/de/index.html b/plugins/i18n_subsites/test_data/output/de/index.html new file mode 100644 index 0000000..fc43ae1 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/de/index.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html lang="de"> +<head> + <title>Willkommen Sie zur unserer Testseite</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testseite Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/de/../theme/style.css" /> +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/de/">Testseite</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li><a href="http://example.com/test/de/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> +<section id="content"> +<h2>All articles</h2> + +<ol id="post-list"> + <li><article class="hentry"> + <header> <h2 class="entry-title"><a href="http://example.com/test/de/translated-article.html" rel="bookmark" title="Permalink to Ein übersetzter Artikel">Ein übersetzter Artikel</a></h2> </header> + <footer class="post-info"> + <time class="published" datetime="2014-09-14T00:00:00+00:00"> So 14 September 2014 </time> + <address class="vcard author">By + <a class="url fn" href="http://example.com/test/de/author/der-tester.html">Der Tester</a> + </address> + </footer><!-- /.post-info --> + <div class="entry-content"> <p>Ein einfacher Artikel mit einer Übersetzung. +Hier ist ein Link zur <a class="reference external" href="http://example.com/test/images/img.png">einigem Bild</a>.</p> + </div><!-- /.entry-content --> + </article></li> +</ol><!-- /#posts-list --> +</section><!-- /#content --> + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/de/pages/404.html b/plugins/i18n_subsites/test_data/output/de/pages/404.html new file mode 100644 index 0000000..6425660 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/de/pages/404.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html lang="de"> +<head> + <title>Testseite - Eine 404 Seite</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testseite Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/de/../theme/style.css" /> + + + <link rel="alternate" hreflang="cz" href="http://example.com/test/de/../cz/pages/404.html"> + <link rel="alternate" hreflang="en" href="http://example.com/test/de/../pages/404.html"> + +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/de/">Testseite</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li><a href="http://example.com/test/de/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> + <h1>Eine 404 Seite</h1> + Translations: +<a href="http://example.com/test/de/../cz/pages/404.html" hreflang="cz">cz</a> +<a href="http://example.com/test/de/../pages/404.html" hreflang="en">en</a> + + + <p>Eine einfache 404 Seite.</p> + + + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/de/pages/untranslated-page-en.html b/plugins/i18n_subsites/test_data/output/de/pages/untranslated-page-en.html new file mode 100644 index 0000000..2df5ff9 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/de/pages/untranslated-page-en.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>Testseite - Untranslated page</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testseite Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/de/../theme/style.css" /> + + +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/de/">Testseite</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li><a href="http://example.com/test/de/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> + <h1>Untranslated page</h1> + + + <p>This page has no translation.</p> + + + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/de/translated-article.html b/plugins/i18n_subsites/test_data/output/de/translated-article.html new file mode 100644 index 0000000..9393b95 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/de/translated-article.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<html lang="de"> +<head> + <title>Testseite - Ein übersetzter Artikel</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testseite Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/de/../theme/style.css" /> + + + <link rel="alternate" hreflang="cz" href="http://example.com/test/de/../cz/translated-article.html"> + <link rel="alternate" hreflang="en" href="http://example.com/test/de/../translated-article.html"> + + + + +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/de/">Testseite</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li class="active"><a href="http://example.com/test/de/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> +<section id="content" class="body"> + <header> + <h2 class="entry-title"> + <a href="http://example.com/test/de/translated-article.html" rel="bookmark" + title="Permalink to Ein übersetzter Artikel">Ein übersetzter Artikel</a></h2> + Translations: +<a href="http://example.com/test/de/../cz/translated-article.html" hreflang="cz">cz</a> +<a href="http://example.com/test/de/../translated-article.html" hreflang="en">en</a> + + </header> + <footer class="post-info"> + <time class="published" datetime="2014-09-14T00:00:00+00:00"> + So 14 September 2014 + </time> + <address class="vcard author"> + By <a class="url fn" href="http://example.com/test/de/author/der-tester.html">Der Tester</a> + </address> + <div class="category"> + Category: <a href="http://example.com/test/de/category/misc.html">misc</a> + </div> + </footer><!-- /.post-info --> + <div class="entry-content"> + <p>Ein einfacher Artikel mit einer Übersetzung. +Hier ist ein Link zur <a class="reference external" href="http://example.com/test/images/img.png">einigem Bild</a>.</p> + + </div><!-- /.entry-content --> +</section> + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/feeds_all.atom.xml b/plugins/i18n_subsites/test_data/output/feeds_all.atom.xml new file mode 100644 index 0000000..c6c0908 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/feeds_all.atom.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<feed xmlns="http://www.w3.org/2005/Atom"><title>Testing site</title><link href="http://example.com/test/" rel="alternate"></link><link href="http://example.com/test/feeds_all.atom.xml" rel="self"></link><id>http://example.com/test/</id><updated>2014-09-15T00:00:00+00:00</updated><entry><title>Přeložený článek</title><link href="http://example.com/test/cz/translated-article.html" rel="alternate"></link><published>2014-09-15T00:00:00+00:00</published><updated>2014-09-15T00:00:00+00:00</updated><author><name>The Tester</name></author><id>tag:example.com,2014-09-15:/test/cz/translated-article.html</id><content type="html"><p>Jednoduchý článek s překlady. +Zde je odkaz na <a class="reference external" href="http://example.com/test/images/img.png">nějaký obrázek</a>.</p> +</content><category term="misc"></category></entry><entry><title>Ein übersetzter Artikel</title><link href="http://example.com/test/de/translated-article.html" rel="alternate"></link><published>2014-09-14T00:00:00+00:00</published><updated>2014-09-14T00:00:00+00:00</updated><author><name>The Tester</name></author><id>tag:example.com,2014-09-14:/test/de/translated-article.html</id><content type="html"><p>Ein einfacher Artikel mit einer Übersetzung. +Hier ist ein Link zur <a class="reference external" href="http://example.com/test/images/img.png">einigem Bild</a>.</p> +</content><category term="misc"></category></entry><entry><title>A translated article</title><link href="http://example.com/test/translated-article.html" rel="alternate"></link><published>2014-09-13T00:00:00+00:00</published><updated>2014-09-13T00:00:00+00:00</updated><author><name>The Tester</name></author><id>tag:example.com,2014-09-13:/test/translated-article.html</id><content type="html"><p>A simple article with a translation. +Here is a link to <a class="reference external" href="http://example.com/test/images/img.png">some image</a>.</p> +</content><category term="misc"></category></entry><entry><title>An untranslated article</title><link href="http://example.com/test/an-untranslated-article.html" rel="alternate"></link><published>2014-07-14T00:00:00+00:00</published><updated>2014-07-14T00:00:00+00:00</updated><author><name>The Tester</name></author><id>tag:example.com,2014-07-14:/test/an-untranslated-article.html</id><content type="html"><p>An article without a translation. +Here is a link to an <a class="reference external" href="http://example.com/test/pages/untranslated-page.html">untranslated page</a></p> +</content><category term="misc"></category></entry></feed>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/images/img.png b/plugins/i18n_subsites/test_data/output/images/img.png new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/images/img.png diff --git a/plugins/i18n_subsites/test_data/output/index.html b/plugins/i18n_subsites/test_data/output/index.html new file mode 100644 index 0000000..c455687 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/index.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>Welcome to our Testing site</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testing site Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/theme/style.css" /> +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/">Testing site</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li><a href="http://example.com/test/pages/untranslated-page.html">Untranslated page</a></li> + <li><a href="http://example.com/test/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> +<section id="content"> +<h2>All articles</h2> + +<ol id="post-list"> + <li><article class="hentry"> + <header> <h2 class="entry-title"><a href="http://example.com/test/translated-article.html" rel="bookmark" title="Permalink to A translated article">A translated article</a></h2> </header> + <footer class="post-info"> + <time class="published" datetime="2014-09-13T00:00:00+00:00"> Sat 13 September 2014 </time> + <address class="vcard author">By + <a class="url fn" href="http://example.com/test/author/the-tester.html">The Tester</a> + </address> + </footer><!-- /.post-info --> + <div class="entry-content"> <p>A simple article with a translation. +Here is a link to <a class="reference external" href="http://example.com/test/images/img.png">some image</a>.</p> + </div><!-- /.entry-content --> + </article></li> + <li><article class="hentry"> + <header> <h2 class="entry-title"><a href="http://example.com/test/an-untranslated-article.html" rel="bookmark" title="Permalink to An untranslated article">An untranslated article</a></h2> </header> + <footer class="post-info"> + <time class="published" datetime="2014-07-14T00:00:00+00:00"> Mon 14 July 2014 </time> + <address class="vcard author">By + <a class="url fn" href="http://example.com/test/author/the-tester.html">The Tester</a> + </address> + </footer><!-- /.post-info --> + <div class="entry-content"> <p>An article without a translation. +Here is a link to an <a class="reference external" href="http://example.com/test/pages/untranslated-page.html">untranslated page</a></p> + </div><!-- /.entry-content --> + </article></li> +</ol><!-- /#posts-list --> +</section><!-- /#content --> + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/pages/404.html b/plugins/i18n_subsites/test_data/output/pages/404.html new file mode 100644 index 0000000..4695f3c --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/pages/404.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>Testing site - A 404 page</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testing site Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/theme/style.css" /> + + + <link rel="alternate" hreflang="cz" href="http://example.com/test/cz/pages/404.html"> + <link rel="alternate" hreflang="de" href="http://example.com/test/de/pages/404.html"> + +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/">Testing site</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li><a href="http://example.com/test/pages/untranslated-page.html">Untranslated page</a></li> + <li><a href="http://example.com/test/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> + <h1>A 404 page</h1> + Translations: +<a href="http://example.com/test/cz/pages/404.html" hreflang="cz">cz</a> +<a href="http://example.com/test/de/pages/404.html" hreflang="de">de</a> + + + <p>A simple 404 page.</p> + + + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/pages/untranslated-page.html b/plugins/i18n_subsites/test_data/output/pages/untranslated-page.html new file mode 100644 index 0000000..3ff02df --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/pages/untranslated-page.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>Testing site - Untranslated page</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testing site Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/theme/style.css" /> + + +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/">Testing site</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li class="active"><a href="http://example.com/test/pages/untranslated-page.html">Untranslated page</a></li> + <li><a href="http://example.com/test/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> + <h1>Untranslated page</h1> + + + <p>This page has no translation.</p> + + + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/output/theme/style.css b/plugins/i18n_subsites/test_data/output/theme/style.css new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/theme/style.css diff --git a/plugins/i18n_subsites/test_data/output/translated-article.html b/plugins/i18n_subsites/test_data/output/translated-article.html new file mode 100644 index 0000000..45be1bb --- /dev/null +++ b/plugins/i18n_subsites/test_data/output/translated-article.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>Testing site - A translated article</title> + <meta charset="utf-8" /> + <meta name="generator" content="Pelican" /> + <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testing site Full Atom Feed" /> + +<link rel="stylesheet" href="http://example.com/test/theme/style.css" /> + + + <link rel="alternate" hreflang="cz" href="http://example.com/test/cz/translated-article.html"> + <link rel="alternate" hreflang="de" href="http://example.com/test/de/translated-article.html"> + + + + +</head> + +<body id="index" class="home"> + <header id="banner" class="body"> + <h1><a href="http://example.com/test/">Testing site</a></h1> + </header><!-- /#banner --> + <nav id="menu"><ul> + <li><a href="http://example.com/test/pages/untranslated-page.html">Untranslated page</a></li> + <li class="active"><a href="http://example.com/test/category/misc.html">misc</a></li> + </ul></nav><!-- /#menu --> +<section id="content" class="body"> + <header> + <h2 class="entry-title"> + <a href="http://example.com/test/translated-article.html" rel="bookmark" + title="Permalink to A translated article">A translated article</a></h2> + Translations: +<a href="http://example.com/test/cz/translated-article.html" hreflang="cz">cz</a> +<a href="http://example.com/test/de/translated-article.html" hreflang="de">de</a> + + </header> + <footer class="post-info"> + <time class="published" datetime="2014-09-13T00:00:00+00:00"> + Sat 13 September 2014 + </time> + <address class="vcard author"> + By <a class="url fn" href="http://example.com/test/author/the-tester.html">The Tester</a> + </address> + <div class="category"> + Category: <a href="http://example.com/test/category/misc.html">misc</a> + </div> + </footer><!-- /.post-info --> + <div class="entry-content"> + <p>A simple article with a translation. +Here is a link to <a class="reference external" href="http://example.com/test/images/img.png">some image</a>.</p> + + </div><!-- /.entry-content --> +</section> + <footer id="contentinfo" class="body"> + <address id="about" class="vcard body"> + Proudly powered by <a href="https://getpelican.com/">Pelican</a>, + which takes great advantage of <a href="https://www.python.org/">Python</a>. + </address><!-- /#about --> + </footer><!-- /#contentinfo --> +</body> +</html>
\ No newline at end of file diff --git a/plugins/i18n_subsites/test_data/pelicanconf.py b/plugins/i18n_subsites/test_data/pelicanconf.py new file mode 100644 index 0000000..55018f2 --- /dev/null +++ b/plugins/i18n_subsites/test_data/pelicanconf.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- # +from __future__ import unicode_literals + +AUTHOR = 'The Tester' +SITENAME = 'Testing site' +SITEURL = 'http://example.com/test' + +# to make the test suite portable +TIMEZONE = 'UTC' + +DEFAULT_LANG = 'en' +LOCALE = 'en_US.UTF-8' + +# Generate only one feed +FEED_ALL_ATOM = 'feeds_all.atom.xml' +CATEGORY_FEED_ATOM = None +TRANSLATION_FEED_ATOM = None +AUTHOR_FEED_ATOM = None +AUTHOR_FEED_RSS = None + +# Disable unnecessary pages +CATEGORY_SAVE_AS = '' +TAG_SAVE_AS = '' +AUTHOR_SAVE_AS = '' +ARCHIVES_SAVE_AS = '' +AUTHORS_SAVE_AS = '' +CATEGORIES_SAVE_AS = '' +TAGS_SAVE_AS = '' + +PLUGIN_PATHS = ['../../'] +PLUGINS = ['i18n_subsites'] + +THEME = 'localized_theme' +JINJA_ENVIRONMENT = {'extensions': ['jinja2.ext.i18n']} + +from blinker import signal +tmpsig = signal('tmpsig') +I18N_FILTER_SIGNALS = [tmpsig] + +I18N_SUBSITES = { + 'de': { + 'SITENAME': 'Testseite', + 'AUTHOR': 'Der Tester', + 'LOCALE': 'de_DE.UTF-8', + }, + 'cz': { + 'SITENAME': 'Testovací stránka', + 'AUTHOR': 'Test Testovič', + 'I18N_UNTRANSLATED_PAGES': 'remove', + 'I18N_UNTRANSLATED_ARTICLES': 'keep', + }, + } diff --git a/plugins/i18n_subsites/test_i18n_subsites.py b/plugins/i18n_subsites/test_i18n_subsites.py new file mode 100644 index 0000000..83d0cb9 --- /dev/null +++ b/plugins/i18n_subsites/test_i18n_subsites.py @@ -0,0 +1,139 @@ +'''Unit tests for the i18n_subsites plugin''' + +import os +import locale +import unittest +import subprocess +from tempfile import mkdtemp +from shutil import rmtree + +from . import i18n_subsites as i18ns +from pelican import Pelican +from pelican.tests.support import get_settings +from pelican.settings import read_settings + + +class TestTemporaryLocale(unittest.TestCase): + '''Test the temporary locale context manager''' + + def test_locale_restored(self): + '''Test that the locale is restored after exiting context''' + orig_locale = locale.setlocale(locale.LC_ALL) + with i18ns.temporary_locale(): + locale.setlocale(locale.LC_ALL, 'C') + self.assertEqual(locale.setlocale(locale.LC_ALL), 'C') + self.assertEqual(locale.setlocale(locale.LC_ALL), orig_locale) + + def test_temp_locale_set(self): + '''Test that the temporary locale is set''' + with i18ns.temporary_locale('C'): + self.assertEqual(locale.setlocale(locale.LC_ALL), 'C') + + +class TestSettingsManipulation(unittest.TestCase): + '''Test operations on settings dict''' + + def setUp(self): + '''Prepare default settings''' + self.settings = get_settings() + + def test_get_pelican_cls_class(self): + '''Test that we get class given as an object''' + self.settings['PELICAN_CLASS'] = object + cls = i18ns.get_pelican_cls(self.settings) + self.assertIs(cls, object) + + def test_get_pelican_cls_str(self): + '''Test that we get correct class given by string''' + cls = i18ns.get_pelican_cls(self.settings) + self.assertIs(cls, Pelican) + + +class TestSitesRelpath(unittest.TestCase): + '''Test relative path between sites generation''' + + def setUp(self): + '''Generate some sample siteurls''' + self.siteurl = 'http://example.com' + i18ns._SITE_DB['en'] = self.siteurl + i18ns._SITE_DB['de'] = self.siteurl + '/de' + + def tearDown(self): + '''Remove sites from db''' + i18ns._SITE_DB.clear() + + def test_get_site_path(self): + '''Test getting the path within a site''' + self.assertEqual(i18ns.get_site_path(self.siteurl), '/') + self.assertEqual(i18ns.get_site_path(self.siteurl + '/de'), '/de') + + def test_relpath_to_site(self): + '''Test getting relative paths between sites''' + self.assertEqual(i18ns.relpath_to_site('en', 'de'), 'de') + self.assertEqual(i18ns.relpath_to_site('de', 'en'), '..') + + +class TestRegistration(unittest.TestCase): + '''Test plugin registration''' + + def test_return_on_missing_signal(self): + '''Test return on missing required signal''' + i18ns._SIGNAL_HANDLERS_DB['tmp_sig'] = None + i18ns.register() + self.assertNotIn(id(i18ns.save_generator), + i18ns.signals.generator_init.receivers) + + def test_registration(self): + '''Test registration of all signal handlers''' + i18ns.register() + for sig_name, handler in i18ns._SIGNAL_HANDLERS_DB.items(): + sig = getattr(i18ns.signals, sig_name) + self.assertIn(id(handler), sig.receivers) + # clean up + sig.disconnect(handler) + + +class TestFullRun(unittest.TestCase): + '''Test running Pelican with the Plugin''' + + def setUp(self): + '''Create temporary output and cache folders''' + self.temp_path = mkdtemp(prefix='pelicantests.') + self.temp_cache = mkdtemp(prefix='pelican_cache.') + + def tearDown(self): + '''Remove output and cache folders''' + rmtree(self.temp_path) + rmtree(self.temp_cache) + + def test_sites_generation(self): + '''Test generation of sites with the plugin + + Compare with recorded output via ``git diff``. + To generate output for comparison run the command + ``pelican -o test_data/output -s test_data/pelicanconf.py \ + test_data/content`` + Remember to remove the output/ folder before that. + ''' + base_path = os.path.dirname(os.path.abspath(__file__)) + base_path = os.path.join(base_path, 'test_data') + content_path = os.path.join(base_path, 'content') + output_path = os.path.join(base_path, 'output') + settings_path = os.path.join(base_path, 'pelicanconf.py') + settings = read_settings(path=settings_path, override={ + 'PATH': content_path, + 'OUTPUT_PATH': self.temp_path, + 'CACHE_PATH': self.temp_cache, + 'PLUGINS': [i18ns], + } + ) + pelican = Pelican(settings) + pelican.run() + + # compare output + out, err = subprocess.Popen( + ['git', 'diff', '--no-ext-diff', '--exit-code', '-w', output_path, + self.temp_path], env={'PAGER': ''}, + stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + self.assertFalse(out, 'non-empty `diff` stdout:\n{}'.format(out)) + self.assertFalse(err, 'non-empty `diff` stderr:\n{}'.format(err)) diff --git a/plugins/related_posts/Readme.rst b/plugins/related_posts/Readme.rst new file mode 100644 index 0000000..0edf4bd --- /dev/null +++ b/plugins/related_posts/Readme.rst @@ -0,0 +1,38 @@ +Related posts +------------- + +**NOTE:** `This plugin has been moved to its own repository <https://github.com/pelican-plugins/related-posts>`_. Please file any issues/PRs there. Once all plugins have been migrated to the `new Pelican Plugins organization <https://github.com/pelican-plugins>`_, this monolithic repository will be archived. + +------------------------------------------------------------------------------- + +This plugin adds the ``related_posts`` variable to the article's context. +By default, up to 5 articles are listed. You can customize this value by +defining ``RELATED_POSTS_MAX`` in your settings file:: + + RELATED_POSTS_MAX = 10 + +You can then use the ``article.related_posts`` variable in your templates. +For example:: + + {% if article.related_posts %} + <ul> + {% for related_post in article.related_posts %} + <li><a href="{{ SITEURL }}/{{ related_post.url }}">{{ related_post.title }}</a></li> + {% endfor %} + </ul> + {% endif %} + + +Your related posts should share a common tag. You can also use ``related_posts:`` in your post's meta data. +The 'related_posts:' meta data works together with your existing slugs:: + + related_posts: slug1, slug2, slug3, ... slugN + +``N`` represents the ``RELATED_POSTS_MAX``. + +Additionally, you can specify:: + + RELATED_POSTS_SKIP_SAME_CATEGORY = True + +in your settings file. With this setting, ``article.related_posts`` will +contain only related posts from categories other than the original article's. diff --git a/plugins/related_posts/__init__.py b/plugins/related_posts/__init__.py index c78861e..057540e 100644 --- a/plugins/related_posts/__init__.py +++ b/plugins/related_posts/__init__.py @@ -1 +1 @@ -from .related_posts import *
+from .related_posts import * diff --git a/plugins/related_posts/related_posts.py b/plugins/related_posts/related_posts.py index a0cfa15..fbb2426 100755..100644 --- a/plugins/related_posts/related_posts.py +++ b/plugins/related_posts/related_posts.py @@ -14,15 +14,14 @@ def add_related_posts(generator): # get the max number of entries from settings # or fall back to default (5) numentries = generator.settings.get('RELATED_POSTS_MAX', 5) - ignore_tags = generator.settings.get('RELATED_POSTS_IGNORE_TAGS', []) # Skip all posts in the same category as the article skipcategory = generator.settings.get('RELATED_POSTS_SKIP_SAME_CATEGORY', False) - for article in generator.articles: + for article in chain(generator.articles, generator.drafts): # set priority in case of forced related posts if hasattr(article,'related_posts'): - # split slugs + # split slugs related_posts = article.related_posts.split(',') - posts = [] + posts = [] # get related articles for slug in related_posts: i = 0 @@ -41,7 +40,7 @@ def add_related_posts(generator): continue # score = number of common tags - related = chain(*(generator.tags[tag] for tag in article.tags if tag.name not in ignore_tags)) + related = chain(*(generator.tags[tag] for tag in article.tags)) if skipcategory: related = (other for other in related if other.category != article.category) @@ -50,7 +49,7 @@ def add_related_posts(generator): # remove itself scores.pop(article, None) - article.related_posts = [other for other, count + article.related_posts = [other for other, count in scores.most_common(numentries)] def register(): diff --git a/publish.sh b/publish.sh deleted file mode 100755 index 358f77c..0000000 --- a/publish.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -source ~/.virtualenvs/pelican4.1/bin/activate -cd ~/pelican/ -make publish - -find output -name '*.html' -print0 | xargs -0 grep "{filename}" -if [ $? -eq 0 ]; then - echo Lien cassé dans les fichiers listés ci dessus. - exit 1 -fi - -rsync -ra --del -e ssh output/ goku:/home/www/blog-chimrod/ -rsync -ra --del -e ssh output/ vps785951:/srv/apache/blog-chimrod/ diff --git a/readme.rst b/readme.rst new file mode 100755 index 0000000..06913c7 --- /dev/null +++ b/readme.rst @@ -0,0 +1,23 @@ + +Ce dépôt contient le blog hébergé à l'adresse suivante : http://blog.chimrod.com/ + +Installation +============ + +Dépendances +----------- + +Installer pélican dans un environnement virtuel : + +.. code-block:: bash + + mkvirtualenv pelican + workon pelican + pip3 install pelican i18n_subsites3 smartypants pygments + +Copie du dépôt +-------------- + +.. code-block:: bash + + git clone --recursive http://git.chimrod.com/blog.git/ |