Compare commits

...

48 Commits

Author SHA1 Message Date
Bertrand Benjamin 0d4e4d74b0 Feat(core): update .drone.yml
continuous-integration/drone/push Build is passing Details
2023-04-14 14:29:07 +02:00
Bertrand Benjamin 1ae89f63d0 Feat(plugin): enable source-link plugin 2023-04-14 14:26:11 +02:00
Bertrand Benjamin d90ed8149d Feat(plugin): add link to sources in template 2023-04-14 14:25:52 +02:00
Bertrand Benjamin dab40dddd0 Fix: " et ' 2023-04-14 13:57:39 +02:00
Bertrand Benjamin 9a2d687434 Feat(core): add css build command 2023-04-14 13:56:58 +02:00
Bertrand Benjamin 7ea7fba87c Feat(plugin): add source-link plugin 2023-04-14 13:56:29 +02:00
Bertrand Benjamin ae45ff7d42 Feat: add pre-commit config 2023-04-14 13:56:14 +02:00
Bertrand Benjamin 75060070b9 Feat: change border size for toctree 2022-08-31 06:52:14 +02:00
Bertrand Benjamin 0607f2a69a Feat: hamburger button for phonemode 2022-08-31 06:29:42 +02:00
Bertrand Benjamin 1764489028 Feat: remove nav and toctree in small screen 2022-08-31 05:58:33 +02:00
Bertrand Benjamin b6b089542e Feat: add big-button plugin 2022-08-29 08:01:10 +02:00
Bertrand Benjamin 735cd0bd59 Feat: improve img readability 2022-08-28 22:17:35 +02:00
Bertrand Benjamin dc470aec10 Feat: Update nav bar 2022-08-28 21:21:02 +02:00
Bertrand Benjamin f2adf72aa0 Feat: start 2022-2023 from 2021-2022 2022-08-21 08:40:09 +02:00
Bertrand Benjamin 0653ac2bf8 Feat: add umami js code for analytics:
continuous-integration/drone/push Build is passing Details
2022-06-15 09:21:40 +02:00
Bertrand Benjamin 5b9910bb0f Feat: adapt drone.yml to 2021-2022
continuous-integration/drone/push Build is failing Details
2021-08-23 14:34:58 +02:00
Bertrand Benjamin dbbb0b5ecf Feat: Adapt to year 2021-2022 2021-08-22 18:02:57 +02:00
Bertrand Benjamin 6060a52717 Feat: filter article in pagetree.
continuous-integration/drone/push Build is passing Details
If "Semaine" in tags, it does not show up
2020-12-05 06:53:01 +01:00
Bertrand Benjamin 12d10e6ace Feat: remove pelican-page-hierarchy
continuous-integration/drone/push Build is passing Details
2020-08-25 10:54:59 +02:00
Bertrand Benjamin abe52dd5c9 Merge branch '2020-2021' of git_opytex:lafrite/site_opytex into 2020-2021 2020-08-25 10:50:12 +02:00
Bertrand Benjamin 1c92eedfb2 Feat: activation pdf-img 2020-08-25 10:49:49 +02:00
Bertrand Benjamin 1f34a326ad Feat: activate pdf-img
continuous-integration/drone/push Build is passing Details
2020-08-25 10:43:02 +02:00
Bertrand Benjamin d6e575ea45 Fix: link to projets-informatiques
continuous-integration/drone/push Build is passing Details
2020-08-16 10:33:41 +02:00
Bertrand Benjamin 438251f75d Feat: remove link to bopytex and mapytex
continuous-integration/drone/push Build is passing Details
2020-08-16 10:28:43 +02:00
Bertrand Benjamin b720e17c61 Fix: add requirements
continuous-integration/drone/push Build is passing Details
2020-08-07 18:27:22 +02:00
Bertrand Benjamin 02dbc94f31 Feat: add .drone.yml
continuous-integration/drone/push Build is failing Details
2020-08-07 18:24:12 +02:00
Bertrand Benjamin dd92f1a77e Feat: adapt to year 2019-2020 2020-08-07 18:22:40 +02:00
Bertrand Benjamin 952d19859c Feat: integrate tag_cloud 2020-06-26 10:46:26 +02:00
Bertrand Benjamin ba64771efb Feat: activate always_modified plugin 2020-06-26 10:25:33 +02:00
Bertrand Benjamin c7ecf06733 Feat: expand sidebar on category index 2020-06-26 10:13:48 +02:00
Bertrand Benjamin 548aecf9e3 Feat: toctree based on categories 2020-06-25 16:10:24 +02:00
Bertrand Benjamin d49fabf259 Feat: position of title 2020-06-24 22:18:01 +02:00
Bertrand Benjamin c6ee0ce838 Feat: remove logo, center title, fix links 2020-06-24 14:10:08 +02:00
Bertrand Benjamin edc12f1430 Feat: move HOME to links 2020-06-24 10:33:48 +02:00
Bertrand Benjamin ef808a597b Feat: Turn relative-urls on 2020-06-24 10:05:20 +02:00
Bertrand Benjamin 5b475a2f38 Feat: import work and verify compilation 2020-06-24 08:26:04 +02:00
Bertrand Benjamin 1ad81faabe Feat: home line point to root of website 2020-06-23 10:58:07 +02:00
Bertrand Benjamin da8b91989f Feat: create directory before rsync it 2020-06-23 10:38:14 +02:00
Bertrand Benjamin 5239c34f50 Feat: split with globalconf 2020-06-23 10:05:25 +02:00
Bertrand Benjamin adab4cdd33 Feat: menu is ok 2020-06-23 10:03:36 +02:00
Bertrand Benjamin dbb4f2f9e5 Feat: add content 2020-06-23 09:46:26 +02:00
Bertrand Benjamin 75c13ae761 Feat: activate pdf-img 2020-06-23 09:45:42 +02:00
Bertrand Benjamin c015b18394 Feat: add FAKEDIR and remove RSYNC_EXCLUDE 2020-06-23 09:45:14 +02:00
Bertrand Benjamin 9037131275 Feat: clean rsync commands 2020-06-22 15:10:51 +02:00
Bertrand Benjamin b56e99040e Feat: clean Makefile 2020-06-05 10:45:35 +02:00
Bertrand Benjamin ad1275cfe8 Feat: clean pelicanconf, prod env in publishconf 2020-06-05 10:42:51 +02:00
Bertrand Benjamin 81dc32f301 Feat: remove logo 2020-06-05 10:41:20 +02:00
Bertrand Benjamin 7e4121a577 Feat: remove pages 2020-06-05 10:40:06 +02:00
1628 changed files with 152589 additions and 1777 deletions

28
.drone.yml Normal file
View File

@ -0,0 +1,28 @@
kind: pipeline
name: Opytex 2023-2024
type: docker
trigger:
branch:
- 2023-2024
steps:
- name: Deploy
image: python:3.8-alpine
commands:
- apk add --no-cache openssh-client ca-certificates bash rsync git imagemagick-dev imagemagick
- git submodule init
- git submodule update
- git clone https://git.opytex.org/lafrite/2021-2022.git content
- pip install --no-cache-dir -r requirements.txt
- export MAGICK_HOME=/usr
- pelican ./content/ -o output -s publishconf.py --relative-urls
- eval `ssh-agent -s`
- echo "$SSH_KEY" | ssh-add -
- mkdir -p ~/.ssh
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- ssh sshcontent@91.121.90.228 'mkdir -p ~/opytex.org/www/enseignements/2021-2022/'
- rsync -rv --delete -e "ssh -p 22" ./output/ sshcontent@91.121.90.228:~/opytex.org/www/enseignements/2021-2022/ --checksum
environment:
SSH_KEY:
from_secret: sshcontent-key

3
.gitignore vendored
View File

@ -2,7 +2,8 @@ cache/*
output/*
pelican-plugins/*
pelican-themes/*
content/Enseignements/2*
content
__pycache__
*.pid
venv/
.vim/

15
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,15 @@
---
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 22.6.0
hooks:
- id: black

101
Makefile
View File

@ -2,12 +2,15 @@ PY?=python3
PELICAN?=pelican
PELICANOPTS=
YEARSUBFOLDER=enseignements/DEV/
BASEDIR=$(CURDIR)
INPUTDIR=$(BASEDIR)/content
OUTPUTDIR=$(BASEDIR)/output
CONFFILE=$(BASEDIR)/pelicanconf.py
PUBLISHCONF=$(BASEDIR)/publishconf.py
FAKEDIR=../../output/
FTP_HOST=localhost
FTP_USER=anonymous
FTP_TARGET_DIR=/
@ -15,49 +18,41 @@ FTP_TARGET_DIR=/
SSH_HOST=localhost
SSH_CONF=Embrevade
#SSH_TARGET_DIR=/var/docker/opytex.org/www/
SSH_TARGET_DIR=/home/sshcontent/opytex.org/www/
SSH_TARGET_DIR=/home/sshcontent/opytex.org/www/$(YEARSUBFOLDER)
S3_BUCKET=my_s3_bucket
CLOUDFILES_USERNAME=my_rackspace_username
CLOUDFILES_API_KEY=my_rackspace_api_key
CLOUDFILES_CONTAINER=my_cloudfiles_container
DROPBOX_DIR=~/Dropbox/Public/
GITHUB_PAGES_BRANCH=gh-pages
SRC_ENS=~/Cours/Prof/Enseignements/
SRC_EXCLUDE=--exclude '*/Archive' --exclude '*/tools' --exclude '.git' --exclude '.gitignore' --exclude '*/__pycache__' --exclude '*/config.py' --exclude '*/reflections' --exclude '*/Notes' --exclude '*/notes' --exclude '*/.*' --exclude '*/Makefile' --exclude '2012-2013' --exclude '2013-2014' --exclude '2014-2015' --exclude 'Clipart' --exclude "Shombos" --exclude '*/venv' --exclude "*/.venv"
DEBUG ?= 0
ifeq ($(DEBUG), 1)
PELICANOPTS += -D
endif
help:
@echo 'Makefile for a pelican Web site '
@echo ' '
@echo 'Usage: '
@echo ' make html (re)generate the web site '
@echo ' make clean remove the generated files '
@echo ' make regenerate regenerate files upon modification '
@echo ' make publish generate using production settings '
@echo ' make serve [PORT=8000] serve site at http://localhost:8000'
@echo ' make devserver [PORT=8000] start/restart develop_server.sh '
@echo ' make stopserver stop local server '
@echo ' make ssh_upload upload the web site via SSH '
@echo ' make rsync_upload upload the web site via rsync+ssh '
@echo ' make dropbox_upload upload the web site via Dropbox '
@echo ' make ftp_upload upload the web site via FTP '
@echo ' make s3_upload upload the web site via S3 '
@echo ' make cf_upload upload the web site via Cloud Files'
@echo ' make github upload the web site via gh-pages '
@echo ' '
@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html'
@echo ' '
RELATIVE ?= 1
ifeq ($(RELATIVE), 1)
PELICANOPTS += --relative-urls
endif
html:
help:
@echo 'Makefile for a pelican Web site '
@echo ' '
@echo 'Usage: '
@echo ' make html (re)generate the web site '
@echo ' make clean remove the generated files '
@echo ' make regenerate regenerate files upon modification '
@echo ' make publish generate using production settings '
@echo ' make serve [PORT=8000] serve site at http://localhost:8000'
@echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 '
@echo ' make devserver [PORT=8000] serve and regenerate together '
@echo ' make ssh_upload upload the web site via SSH '
@echo ' make rsync_upload upload the web site via rsync+ssh '
@echo ' '
@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html '
@echo 'Set the RELATIVE variable to 1 to enable relative urls '
@echo ' '
css:
lessc $(BASEDIR)/theme/static/stylesheet/style.less $(BASEDIR)/theme/static/stylesheet/style.min.css -x
html: css
$(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
clean:
@ -68,36 +63,36 @@ regenerate:
serve:
ifdef PORT
cd $(OUTPUTDIR) && $(PY) -m pelican.server $(PORT)
$(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT)
else
cd $(OUTPUTDIR) && $(PY) -m pelican.server
$(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
endif
serve-global:
ifdef SERVER
$(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT) -b $(SERVER)
else
$(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT) -b 0.0.0.0
endif
devserver:
ifdef PORT
$(BASEDIR)/develop_server.sh restart $(PORT)
$(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT)
else
$(BASEDIR)/develop_server.sh restart
$(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
endif
stopserver:
kill -9 `cat pelican.pid`
kill -9 `cat srv.pid`
@echo 'Stopped Pelican and SimpleHTTPServer processes running in background.'
publish:
$(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS)
ssh_upload: publish
scp -r $(OUTPUTDIR)/* $(SSH_CONF):$(SSH_TARGET_DIR)
rsync_upload: publish
rsync -e "ssh" -P -rvzc --delete --exclude "pymath" --exclude "opytex" $(OUTPUTDIR)/ $(SSH_CONF):$(SSH_TARGET_DIR) --cvs-exclude
#rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --cvs-exclude --delete $(OUTPUTDIR)/ $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
rsync -e "ssh" -P -rvzc --delete $(RSYNC_EXCLUDE) $(OUTPUTDIR)/ $(SSH_HOST):$(SSH_TARGET_DIR) --cvs-exclude
#import_year:
# rsync -rv --delete $(SRC_EXCLUDE) --delete-excluded --include '*/' --include '*' --prune-empty-dirs $(SRC_ENS)/Archive/$$year/ $(INPUTDIR)/Enseignements/$$year
fake_upload: html
mkdir -p $(FAKEDIR)$(YEARSUBFOLDER)
rsync -P -rvzc --delete $(OUTPUTDIR)/ $(FAKEDIR)$(YEARSUBFOLDER) --cvs-exclude
import_ens:
rsync -av --delete $(SRC_EXCLUDE) --delete-excluded --include '*/' --include '*' --prune-empty-dirs $(SRC_ENS) $(INPUTDIR)/Enseignements/
.PHONY: html help clean regenerate serve devserver publish ssh_upload rsync_upload dropbox_upload ftp_upload s3_upload cf_upload github import_ens
.PHONY: html help clean regenerate serve serve-global devserver publish rsync_upload fake_upload

View File

@ -1,6 +0,0 @@
Site Opytex
###########
- Soucis avec ImageMagick et la conversion vers pdf (pdf-img plugin)
https://stackoverflow.com/questions/52998331/imagemagick-security-policy-pdf-blocking-conversion

View File

@ -1,47 +0,0 @@
Tout sur les cours
##################
:date: 2016-01-23
:modified: 2016-01-24
:authors: Benjamin Bertrand
:summary: Résumé sur ma façon d'enseigner
:save_as: Enseignements/index.html
Les années qui s'écoulent
=========================
- `2015/2016 <./2015-2016/>`_
- `2016/2017 <./2016-2017/>`_
- `2017/2018 <./2017-2018/>`_
- `2018/2019 <./2018-2019/>`_
- `2019/2020 <./2019-2020/>`_
Mes outils pour faire mes cours
===============================
- `Zoologie des workflows <Divers/Workflows.html>`_
- `Outils Latex <tools/>`_
Méthodes, pratiques et inspirations
===================================
En classe, jaime me souvenir de cette phrase *Plus le professeur travaille, moins lélève travaille*. Je crois que lélève doit, le plus possible, **faire** et pas seulement écouter. Ainsi, lélève est source de contenu pour faire avancer le cours. Il nattend pas passivement que la bonne parole du prof lui soit donnée.
Jai donc abandonné le cahier de cours au profit dun **cahier de bord**, où lavancée et les résultats trouvés par la classe sont inscrits. Les théorèmes ne pouvant plus être donnés, ils sont découverts par la classe et, quand cest possible, démontrés. En relisant ce cahier, lélève peut refaire le cheminement qui nous a permis de trouver un résultat. Il a donc à nouveau la possibilité de **comprendre** et plus seulement dapprendre.
Pour que cela soit possible, il me faut choisir avec attention chaque activité. Elle a besoin dêtre fractale ou riche. Cest à dire dotée de plusieurs niveaux de lecture. Chaque élève doit avoir la possibilité de faire quelque chose. Certains comprendront le sens profond de lactivité tandis que dautres pourront simplement revoir et solidifier des acquis. Lactivité elle-même apporte de la **différenciation**.
Comme exemple, je donnerais lactivité de découverte de `cosinus <./2017-2018/3e/Geometrie/Triangle_rectangle/>`_ . Elle permet de construire des triangles, de revoir la notion dangle, de manipuler le rapporteur, de spiraler sur la notion dagrandissement, de remobiliser les notions de proportionnalité, de définir le cosinus et enfin de ne pas enfermer ce cosinus dans une seule formule.
Ce genre dactivité, exigeante pour les élèves, peut difficilement se faire sur une heure complète. Cest pourquoi, mes heures de classe sont découpées en 2 ou 3 parties distinctes.
Une partie peut être dédiée à la découverte dune notion et la suivante à la consolidation dune notion dans un autre domaine. Ainsi les élèves ne restent pas toute lheure en situation déchec et ont, à chaque cours, lopportunité de montrer quils savent faire des choses. De plus, cette organisation étale davantage dans le temps lapprentissage dune notion pour la rendre plus pérenne.
Certains thèmes sont traités tout au long de lannée à raison dune heure par semaine. Cest le cas du calcul littéral et de la programmation avec Scratch. Au collège, nous avons désormais accès à la salle informatique une heure par semaine. Jen profite pour travailler régulièrement la programmation avec Scratch au travers de **projets** à réaliser en équipe. La progression sur lannée est spiralée. Lobjectif étant davoir fait le tour du programme aux vacances de Noël et ensuite, de revoir et dapprofondir les notions. Cette organisation me laisse le temps de tester plusieurs approches et de désamorcer les problèmes.
Lorganisation de la classe a aussi été transformée afin de tirer profit de plus de diversité. Les élèves sont amenés à travailler individuellement, en groupe et en plénière. À chaque mode dorganisation son support. Quand les élèves travaillent individuellement, ils utilisent leur **cahier de recherche**. En groupe, les traces collectives sont inscrites dans le **cahier de groupe**. Et enfin, les plénières aboutissent généralement à une trace écrite dans le **cahier de bord**. Les cahiers de groupe sont ramassés à chaque fin dheure pour que je puisse préparer la plénière de mise en commun et utiliser les productions délèves.
Ma pratique et mes cours sont très largement inspirés du livre Les maths ensemble et pour chacun. Je minspire aussi de ce que font `Christian den Hartigh <https://pedagogieagile.com/>`_ , `Dan Meyer <http://mrmeyer.com/>`_ ou encore `Arnaud Durand et Julien Durand <http://mathix.org/>`_.

View File

@ -1,103 +0,0 @@
À bas le bon vieux: Classe -> eleve -> classe
#############################################
:date: 2018-07-07
:modified: 2018-07-07
:authors: Benjamin Bertrand
:category: Autres
:summary: Zoologie des workflows rencontrés, expérimentés ou imaginé pour la classe.
:save_as: Enseignements/Divers/Workflows.html
Ici j'essaie de faire une zoologie des façons de travailler que j'ai pu rencontrer, expérimenter ou imaginé. J'espère qu'elle va s'aggrandir avec le temps. J'espérais que cette première énumération m'aiderai à en découvrir d'autre, raté pour le moment..!
L'ancien
========
Le seul l'unique l'indetrônable, le workflow que l'on a tous subit dans notre scolarité.
Pleinière -> Seul -> Pleinière
**Description**: Le prof ecrit le cours (propriété/théorème, exemples), les élèves font les exercices seul dans leur coin (le plus grand silence est imposé) et enfin une correction est donnée au tableau (par les élèves ou le prof).
**Variantes**: Avant le cours, une activité d'introduction ou des révisions sont proposés aux élèves.
Le cours discuté
================
On écrit le cours en posant des micros questions aux élèves. On a donc des micro boucles du genre
Pleinière -> Seul -> Pleinière -> Seul ...
**Description**: Ce cours se fait à partir d'une alternance entre le prof qui fait avancer le cours et les élèves qui réfléchissent sur des temps plutôt court à des questions. Un veritable dialogue entre classe et l'enseignant s'organise.
Le classique
============
Celui là est celui qu'on utilise le plus souvent
Seul -> Groupe -> Pleinière
**Description**: Une activité est donnée au élèves. Ils commencent à plancher dessus seul (sans interactions avec les autres élèves ou avec l'enseignant). Quand ils semblent tous se l'être appropriée (signal de l'enseignant), un travail de groupe démarre. Il se termine sur une production de groupe. Enfin, l'enseignant séléctionne quelques productions pour en faire un bilan en pleinière où la classe toute ensemble reprend les productions les critiques et les améliore pour laisser finalement une trace "parfaite" (point de vue de la classe) sur le cahier de bord.
**Variantes**:
- Avant le travail individuel, il peut être interessant de faire une lecture collective de l'activité pour s'assurer que tout le monde à le vocabulaire ou les notions traitées (à Mayotte c'est la problématique permanante).
- Pour sortir du syndrome de la page blanche, on peut fixer le temps de sortie du travail individuel uniquement une fois que tous les élèves ont commencé à écrire quelque chose d'interessant sur leur cahier de recherche.
- Le temps de travail de groupe peut être coupé par des rapides retours en pleinières pour que les groupes les plus avancés partagent leurs découvertes (surtout celles non terminées).
- Il peut être interessant de changer la composition des groupes surtout vers la fin. Les élèves devront alors expliquer ce qui avait été découvert dans leur ancien groupe et integrer les découvertes des autres groupes.
- Dans le même style, on peut designer un temps où des butineurs (un par équipe) va pouvoir aller voir (sans moyen de prendre de notes) les autres groupes.
- La pleinière peut aussi être annimée par les élèves eux même qui viendront expliquer leur travail aux autres.
DMTC
====
J'aime ce workflow quand il s'ajit de faire faire des taches complexes aux élèves.
Seul -> Groupe -> Seul
**Description**: L'activité débute comme *le classique*. Les élèves décrouvrent le sujet, font leurs première expérimentations puis commencent le travail de groupe. Par contre au lieu de faire un pleinière de mise en commun, les élèves terminent la rédaction seul à la maison. Le but c'est que les difficultés techniques aient été résoluent en classe et avec l'aide des autres et que le travail de rédaction, de représentation (...) soit fait individuellement.
**Variantes**:
- Pour la partie Seul et Groupe, on retrouve les même que pour le *classique*.
- Dan Meyer dans ses 3acts proposent que les élèves cherchent eux même la question et les informations dont ils voudraient avoir accès avant le travail individuel. Cette étape peut être faire sous forme d'un petit *cours discuté*.
La TC prépréparé
================
Le workflow précendent est particulièrement difficile à mettre en place avec les petites classes (6e, 5e). On peut alors plutôt expérimenter celui là
Pleinière -> Pleinière -> individuel/groupe
**Description**: L'activité est donnée relativement en avance. À chaque cours on en parle en pleinière et les élèves sont invités la commencer. Ensuite, on faire l'activité en classe, individuellement ou en groupe.
Je n'ai jamais expérimenté ce workflow mais je sais que Maxime Dupont en était adepte (en espérant ne pas l'avoir trop dénaturé) et arrivait à faire des choses interessantes avec.
La technique/le bachotage
=========================
Workflow parfait pour les séances de travail technique ou les séances de révisions.
Seul -> Groupe -> Seul -> Groupe ...
**Description**: Une série d'exercices techniques est données. Les élèves planchent individuellement dessus. Quand les membres du groupe en ont fait quelques uns, ils mettent leurs résultats en commun et discute des différences constatés pour aboutir à la réponse "parfaite". Si un point de désaccord n'arrive pas à trouver consensus, une correction est sur le bureau qu'ils penvent consulter. Pendant ce genre de scéance, l'enseignant doit être peu solicité, c'est lui qui décide qui il veut voir.
**Variantes**: Organisation du travail avec un kanban. Les élèves y indiquent ce qu'ils prévoient de travailler et là où ils en sont.
Pair à pair
===========
Workflow trop peu exploité. Les élèves produisent des documents à destinations des autres élèves.
Seul -> Groupe -> Seul
**Description**: Les élèves font un premier exercice seul (ou en suivant n'importe quel workflow). Puis en groupe, ils doivent produire un ou plusieurs exercices du même type avec la correction. Les exercices produits sont redonnés aux autres élèves.
On peut demander aux groupes de produire plusieurs exercices du même type mais de difficultés différentes.
**Variantes**:
- Dire aux élèves que le meileur exercice (pas forcement le plus dure) sera dans la prochaine évaluation.
- Echange de travaux de reproduction/codage géométrique.

View File

@ -1,12 +0,0 @@
À propos
########
:date: 2015-05-16
:modified: 2015-05-16
:slug: about
:authors: Benjamin Bertrand
:summary: À propos de l'auteur.
:save_as: about.html
Une petite présentation de ... moi!

View File

@ -1,36 +0,0 @@
Karibu Opytex
##############
:date: 2015-05-16
:modified: 2015-05-16
:slug: index
:authors: Benjamin Bertrand
:summary: Page d'accueil
:save_as: index.html
`Mes cours </Enseignements/>`_
===============================
Ce site regroupe tout ce que j'ai pu écrire pour mes cours de math au collège et au lycée. Vous y trouverez des notes de cours (souvent incomplètes), des fiches d'exercices, des devoirs et quelques réflexions ou commentaires à leur sujet.
`Opytex </opytex>`_
=======================
Outil en ligne de commande pour générer puis compiler des fichiers latex.
J'utilise ce programme essentiellement pour produire des DM aléatoirement avec, quand c'est possible, leur correction (utilise pyMath). En voici un exemple: `DM_15_12_09 <Enseignements/3e/DM/DM_15_12_09/all_DM_15_12_09.pdf>`_.
Voir `la documentation de Opytex </opytex>`_ .
Et `ici <http://opytex.org/Enseignements/3e/DM/DM_15_12_09/>`_ pour des explications sur la création de ces sujets.
Dans la pratique, OpyTex utilise `Jinja2 <http://jinja.pocoo.org/>`_ pour inclure python dans des documents en latex.
`pyMath </pymath>`_
=====================
pyMath est un ensemble de modules écrits en Python qui vise à faciliter et automatiser la conception d'exercices de math et leur correction.
C'est le moteur qui va permettre à `Opytex </opytex>`_ de manipuler les données créées pour faire ces exercices. Il est capable de créer aléatoirement des expressions (calculs de fractions, polynômes, expressions littérales...), des données statistiques, de les simplifier et de faire des calculs avec.
À la différence d'un logiciel de calcul formel classique (comme `sympy <sympy.org>`_), pyMath va non seulement être capable de simplifier des calculs mais surtout d'expliquer comme un élève les étapes qui permettent d'arriver au résultat.

View File

@ -1,103 +0,0 @@
#!/usr/bin/env bash
##
# This section should match your Makefile
##
PY=${PY:-python3}
PELICAN=${PELICAN:-pelican}
PELICANOPTS=
BASEDIR=$(pwd)
INPUTDIR=$BASEDIR/content
OUTPUTDIR=$BASEDIR/output
CONFFILE=$BASEDIR/pelicanconf.py
###
# Don't change stuff below here unless you are sure
###
SRV_PID=$BASEDIR/srv.pid
PELICAN_PID=$BASEDIR/pelican.pid
function usage(){
echo "usage: $0 (stop) (start) (restart) [port]"
echo "This starts Pelican in debug and reload mode and then launches"
echo "an HTTP server to help site development. It doesn't read"
echo "your Pelican settings, so if you edit any paths in your Makefile"
echo "you will need to edit your settings as well."
exit 3
}
function alive() {
kill -0 $1 >/dev/null 2>&1
}
function shut_down(){
PID=$(cat $SRV_PID)
if [[ $? -eq 0 ]]; then
if alive $PID; then
echo "Stopping HTTP server"
kill $PID
else
echo "Stale PID, deleting"
fi
rm $SRV_PID
else
echo "HTTP server PIDFile not found"
fi
PID=$(cat $PELICAN_PID)
if [[ $? -eq 0 ]]; then
if alive $PID; then
echo "Killing Pelican"
kill $PID
else
echo "Stale PID, deleting"
fi
rm $PELICAN_PID
else
echo "Pelican PIDFile not found"
fi
}
function start_up(){
local port=$1
echo "Starting up Pelican and HTTP server"
shift
$PELICAN --debug --autoreload -r $INPUTDIR -o $OUTPUTDIR -s $CONFFILE $PELICANOPTS &
pelican_pid=$!
echo $pelican_pid > $PELICAN_PID
cd $OUTPUTDIR
$PY -m pelican.server $port &
srv_pid=$!
echo $srv_pid > $SRV_PID
cd $BASEDIR
sleep 1
if ! alive $pelican_pid ; then
echo "Pelican didn't start. Is the Pelican package installed?"
return 1
elif ! alive $srv_pid ; then
echo "The HTTP server didn't start. Is there another service using port" $port "?"
return 1
fi
echo 'Pelican and HTTP server processes now running in background.'
}
###
# MAIN
###
[[ ($# -eq 0) || ($# -gt 2) ]] && usage
port=''
[[ $# -eq 2 ]] && port=$2
if [[ $1 == "stop" ]]; then
shut_down
elif [[ $1 == "restart" ]]; then
shut_down
start_up $port
elif [[ $1 == "start" ]]; then
if ! start_up $port; then
shut_down
fi
else
usage
fi

73
fabfile.py vendored
View File

@ -1,73 +0,0 @@
from fabric.api import *
import fabric.contrib.project as project
import os
import sys
import SimpleHTTPServer
import SocketServer
# Local path configuration (can be absolute or relative to fabfile)
env.deploy_path = 'output'
DEPLOY_PATH = env.deploy_path
# Remote server configuration
production = 'root@localhost:22'
dest_path = '/var/www'
# Rackspace Cloud Files configuration settings
env.cloudfiles_username = 'my_rackspace_username'
env.cloudfiles_api_key = 'my_rackspace_api_key'
env.cloudfiles_container = 'my_cloudfiles_container'
def clean():
if os.path.isdir(DEPLOY_PATH):
local('rm -rf {deploy_path}'.format(**env))
local('mkdir {deploy_path}'.format(**env))
def build():
local('pelican -s pelicanconf.py')
def rebuild():
clean()
build()
def regenerate():
local('pelican -r -s pelicanconf.py')
def serve():
os.chdir(env.deploy_path)
PORT = 8000
class AddressReuseTCPServer(SocketServer.TCPServer):
allow_reuse_address = True
server = AddressReuseTCPServer(('', PORT), SimpleHTTPServer.SimpleHTTPRequestHandler)
sys.stderr.write('Serving on port {0} ...\n'.format(PORT))
server.serve_forever()
def reserve():
build()
serve()
def preview():
local('pelican -s publishconf.py')
def cf_upload():
rebuild()
local('cd {deploy_path} && '
'swift -v -A https://auth.api.rackspacecloud.com/v1.0 '
'-U {cloudfiles_username} '
'-K {cloudfiles_api_key} '
'upload -c {cloudfiles_container} .'.format(**env))
@hosts(production)
def publish():
local('pelican -s publishconf.py')
project.rsync_project(
remote_dir=dest_path,
exclude=".DS_Store",
local_dir=DEPLOY_PATH.rstrip('/') + '/',
delete=True,
extra_opts='-c',
)

17
globalconf.py Normal file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- #
from __future__ import unicode_literals
# Menu
MAIN_MENU = True
DISPLAY_PAGES_ON_MENU = False
DISPLAY_CATEGORIES_ON_MENU = False
MENUITEMS = [
#("Dernières modifications", SITEURL+"blog_index.html"),
("Accueil", "/"),
("Contenus de cours", "/pages/tout-sur-mes-cours.html"),
('Blog', '/blog_index.html'),
('Informatique', "/pages/projets-informatiques.html"),
('À propos', "/pages/a-propos.html"),
]

5
notes
View File

@ -1,5 +0,0 @@
# Importer tous les fichier rst
rsync -rv --del --exclude 'Archive' --exclude 'tools/skeleton' --exclude 'tools/Other' --include '*/' --include '*.rst' --exclude '*' --prune-empty-dirs /media/documents/Cours/Prof/Enseignements content/Cours/
# Ajouter une nouvelle année
Éditer pelicanconf.py pour ajouter une entrée dans links

@ -1 +0,0 @@
Subproject commit 5c5f965c984d9cb9324c1c7b251d448992c4bda5

View File

@ -1,48 +1,89 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- #
from __future__ import unicode_literals
import os
import sys
AUTHOR = 'Benjamin Bertrand'
SITENAME = 'OpyTex'
SITETITLE = 'OpyTex'
SITESUBTITLE = "Des cours de maths, d'info et un peu de réfléxions personnelles."
#SITEURL = 'opytex.org'
SITEURL = ''
sys.path.append(os.curdir)
from globalconf import *
AUTHOR = "Benjamin Bertrand"
SITENAME = "OpyTex"
SITETITLE = "OpyTex"
SITESUBTITLE = "2022-2023"
SITEURL = ""
CC_LICENSE_COMMERCIAL = True
CC_LICENSE = True
PATH = 'content'
PATH = "./content"
TIMEZONE = 'Europe/Paris'
TIMEZONE = "Europe/Paris"
DEFAULT_LANG = 'fr'
DEFAULT_LANG = "fr"
# theme
# Uncomment following line if you want document-relative URLs when developing
# RELATIVE_URLS = True
# Files places
IGNORE_FILES = ["venv", ".git", "tools"]
# Pages, articles and static
# PAGE_PATHS = ['pages']
ARTICLE_PATHS = ["."]
STATIC_PATHS = ["."]
INDEX_SAVE_AS = "blog_index.html"
#
USE_FOLDER_AS_CATEGORY = False
# Plugins
PLUGIN_PATHS = ["plugins"]
PLUGINS = [
"i18n_subsites",
"always_modified",
"tag_cloud",
"pdf-img",
"big-button",
"source-link",
]
ALWAYS_MODIFIED = True
# Mirror source structure
PATH_METADATA = "(?P<path_no_ext>.*)\..*"
ARTICLE_URL = ARTICLE_SAVE_AS = PAGE_URL = PAGE_SAVE_AS = "{path_no_ext}.html"
# USE_FOLDER_AS_CATEGORY = True
# DEFAULT_CATEGORY = "Autre"
# Readers
READERS = {"html": None}
# Everythings in french
JINJA_ENVIRONMENT = {"extensions": ["jinja2.ext.i18n"]}
# Default theme language.
I18N_TEMPLATES_LANG = "en"
# Your language.
DEFAULT_LANG = "fr"
OG_LOCALE = "fr"
LOCALE = ("fr", "fr_FR.utf8")
# Themes
THEME = "./theme/"
USE_GOOGLE_FONTS = False
# Pages, articles and static
PAGE_PATHS = ['pages']
#ARTICLE_PATHS = ['pages/Enseignement', 'Blog']
ARTICLE_PATHS = ['Enseignements']
STATIC_PATHS = ['./']
INDEX_SAVE_AS = 'blog_index.html'
# Menu
# Main menu on the top
MAIN_MENU = True
DISPLAY_PAGES_ON_MENU = False
DISPLAY_CATEGORIES_ON_MENU = False
MENUITEMS = [
('Opytex', "/opytex/"),
("pyMath", "/pymath/"),
("Enseignement", "/Enseignements"),
#('blog', '/blog_index.html'),
('À propos', "/about.html"),
('Archives', "/archives.html"),
]
# Sidebar
DISPLAY_PAGES_ON_SIDE = False
TOCTREE = True
TAG_CLOUD = True
# SITELOGO = ""
LINKS = ()
DEFAULT_PAGINATION = 10
# Feed generation is usually not desired when developing
FEED_ALL_ATOM = None
@ -51,50 +92,6 @@ TRANSLATION_FEED_ATOM = None
AUTHOR_FEED_ATOM = None
AUTHOR_FEED_RSS = None
# Blogroll
LINKS = (
('2019/2020', "/Enseignements/2019-2020/"),
('2018/2019', "/Enseignements/2018-2019/"),
('2017/2018', "/Enseignements/2017-2018/"),
('2016/2017', "/Enseignements/2016-2017/"),
('2015/2016', "/Enseignements/2015-2016/"),
)
# Social widget
#SOCIAL = (('You can add links in your config file', '#'),
# )
DEFAULT_PAGINATION = 20
# Date
SHOW_DATE_MODIFIED = True
ARTICLE_ORDER_BY = "modified"
DISPLAY_ARTICLE_INFO_ON_INDEX = True
# Uncomment following line if you want document-relative URLs when developing
#RELATIVE_URLS = True
BOOTSTRAP_THEME = "flatly"
PLUGIN_PATHS = ['./plugins', './pelican-plugins']
PLUGINS = ['hierarchy',
'tag_cloud',
"list_files",
"render_math",
"always_modified",
"pdf-img",
]
READERS = {"html": None}
# hierarchy plugin config
ARTICLE_URL = 'Enseignements/{slug}/'
ARTICLE_SAVE_AS = 'Enseignements/{slug}/index.html'
#SLUGIFY_SOURCE = 'basename'
ARTICLE_NAVIGATION = True
TAGS_URL = "tags.html"
DISPLAY_TAGS_INLINE = True
DISPLAY_CATEGORIES_ON_SIDEBAR = True
# SOURCE LINK
GIT_SOURCE_BASE_URL = "https://git.opytex.org/lafrite/2022-2023/src/branch/main"
SOURCE_ICON_URL = "https://git.opytex.org/assets/img/logo.svg"

1
plugins/__init__.py Normal file
View File

@ -0,0 +1 @@
from .always_modified import *

View File

@ -0,0 +1,22 @@
"""
If "modified" date/time is not defined in article metadata, fall back to the "created" date.
"""
from pelican import signals
from pelican.contents import Content, Article
def add_modified(content):
if not isinstance(content, Article):
return
if not content.settings.get("ALWAYS_MODIFIED", False):
return
if hasattr(content, "date") and not hasattr(content, "modified"):
content.modified = content.date
content.locale_modified = content.locale_date
def register():
signals.content_object_init.connect(add_modified)

View File

@ -0,0 +1,14 @@
# Always Modified
Say you want to sort by modified date/time in a theme template, but not all
your articles have modified date/timestamps explicitly defined in article
metadata. This plugin facilitates that sorting by assuming the modified date
(if undefined) is equal to the created date.
## Usage
1. Add `ALWAYS_MODIFIED = True` to your settings file.
2. Now you can sort by modified date in your templates:
{% for article in articles|sort(reverse=True,attribute='modified') %}

View File

View File

@ -0,0 +1 @@
from .big_button import *

View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
"""
big_button tags for reStructuredText
==============================
This plugin allows you to use big_button tags from within reST documents.
.. big_button::
:title: Title
:link: ""
:color: default
Description
"""
from __future__ import unicode_literals
from docutils import nodes
from docutils.parsers.rst import directives, Directive
class BigButton(Directive):
optional_arguments = 0
final_argument_whitespace = True
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
"title": directives.unicode_code,
"link": directives.path,
}
def settings(self):
self.options['content'] = '\n'.join(self.content)
if not self.options.get('title'):
self.options['title'] = 0
if not self.options.get('link'):
self.options['link'] = 0
def html(self):
html = "<div class='button'>\n"
if self.options["link"]:
html += f"<a href={self.options['link']}>"
else:
html += f"<div class='nolink'>"
if self.options["title"]:
html += f"<h3> {self.options['title']}</h3>"
if self.content:
html += "<div class='content'>"
html += f"{self.options['content']}"
html += "</div>"
if self.options["link"]:
html += "</a>"
else:
html += "</div>"
html += "</div>"
return html
def run(self):
self.settings()
return [nodes.raw('', self.html(), format='html')]
def register():
directives.register_directive("big_button", BigButton)

View File

@ -1,75 +0,0 @@
Page Hierarchy
==============
*Author: Ahmad Khayyat (<akhayyat@gmail.com>)*
A [Pelican][1] plugin that creates a URL hierarchy for pages that
matches the filesystem hierarchy of their sources.
For example, to have the following filesystem structure of page
sources result in the URLs listed next to each file,
```text
└── content/pages/ # PAGE_DIR
├── about.md # URL: pages/about/
├── projects.md # URL: pages/projects/
├── projects/ # (directory)
│ ├── p1.md # URL: pages/projects/p1/
│ ├── p2.md # URL: pages/projects/p2/
│ └── p2/ # (directory)
│ └── features.md # URL: pages/projects/p2/features/
└── contact.md # URL: pages/contact/
```
you can use this plugin with the following Pelican settings:
```python
# pelicanconf.py
PAGE_URL = '{slug}/'
PAGE_SAVE_AS = '{slug}/index.html'
SLUGIFY_SOURCE = 'basename'
```
When generating the `url` and `save_as` attributes, the plugin
prefixes the page's `slug` by its relative path. Although the initial
`slug` is generated from the page's `title` by default, it can be
generated from the source file basename by setting the
`SLUGIFY_SOURCE` setting to `'basename'`, as shown in the settings
snippet above. The `slug` can also be set using [`PATH_METADATA`][2].
This plugin is compatible with [Pelican translations][3].
Parent and Children Pages
-------------------------
This plugin also adds three attributes to each page object:
- `parent`: the immediate parent page. `None` if the page is
top-level. If a translated page has no parent, the default-language
parent is used.
- `parents`: a list of all ancestor pages, starting from the top-level
ancestor.
- `children`: a list of all immediate child pages, in no specific
order.
These attributes can be used to generate breadcrumbs or nested
navigation menus. For example, this is a template excerpt for
breadcrumbs:
```html
<ul class="breadcrumb">
<li><a href="{{ SITEURL }}/" title="{{ SITENAME }}">
<i class="fa fa-home fa-lg"></i>
</a></li>
{% for parent in page.parents %}
<li><a href="{{ SITEURL }}/{{ parent.url }}">{{ parent.title }}</a></li>
{% endfor %}
<li class="active">{{ page.title }}</li>
</ul>
```
[1]: http://getpelican.com/
[2]: http://docs.getpelican.com/en/latest/settings.html#path-metadata
[3]: http://docs.getpelican.com/en/latest/settings.html#translations

View File

@ -1,2 +0,0 @@
#from .page_hierarchy import *
from .article_hierarchy import *

View File

@ -1,88 +0,0 @@
from pelican import signals, contents
import os.path
from copy import copy
from itertools import chain
'''
This plugin creates a URL hierarchy for articles that matches the
directory hierarchy of their sources.
'''
class UnexpectedException(Exception): pass
def get_path(article, settings):
''' Return the dirname relative to ARTICLE_PATHS prefix. '''
path = os.path.split(article.get_relative_source_path())[0] + '/'
path = path.replace( os.path.sep, '/' )
# Try to lstrip the longest prefix first
for prefix in sorted(settings['ARTICLE_PATHS'], key=len, reverse=True):
if not prefix.endswith('/'): prefix += '/'
if path.startswith(prefix):
return path[len(prefix):-1]
raise UnexpectedException('Article outside of ARTICLE_PATHS ?!?')
def in_default_lang(article):
# article.in_default_lang property is undocumented (=unstable) interface
return article.lang == article.settings['DEFAULT_LANG']
def override_metadata(content_object):
if type(content_object) is not contents.Article:
return
article = content_object
path = get_path(article, article.settings)
def _override_value(article, key):
metadata = copy(article.metadata)
# We override the slug to include the path up to the filename
#metadata['slug'] = os.path.join(path, article.slug)
metadata['slug'] = path
# We have to account for non-default language and format either,
# e.g., ARTICLE_SAVE_AS or ARTICLE_LANG_SAVE_AS
#infix = '' if in_default_lang(article) else 'LANG_'
infix = ''
return article.settings['ARTICLE_' + infix + key.upper()].format(**metadata)
for key in ('save_as', 'url'):
if not hasattr(article, 'override_' + key):
setattr(article, 'override_' + key, _override_value(article, key))
def set_relationships(generator):
def _all_articles():
return chain(generator.articles, generator.translations)
# initialize parents and children lists
for article in _all_articles():
article.parent = None
article.parents = []
article.children = []
# set immediate parents and children
for article in _all_articles():
# Parent of /a/b/ is /a/, parent of /a/b.html is /a/
parent_url = os.path.dirname(article.url[:-1])
if parent_url: parent_url += '/'
for article2 in _all_articles():
if article2.url == parent_url and article2 != article:
article.parent = article2
article2.children.append(article)
# If no parent found, try the parent of the default language article
#if not article.parent and not in_default_lang(article):
if not article.parent:
for article2 in generator.articles:
if (article.slug == article2.slug and
os.path.dirname(article.source_path) ==
os.path.dirname(article2.source_path)):
# Only set the parent but not the children, obviously
article.parent = article2.parent
# set all parents (ancestors)
for article in _all_articles():
p = article
while p.parent:
article.parents.insert(0, p.parent)
p = p.parent
def register():
signals.content_object_init.connect(override_metadata)
signals.article_generator_finalized.connect(set_relationships)

View File

@ -1,86 +0,0 @@
from pelican import signals, contents
import os.path
from copy import copy
from itertools import chain
'''
This plugin creates a URL hierarchy for pages that matches the
directory hierarchy of their sources.
'''
class UnexpectedException(Exception): pass
def get_path(page, settings):
''' Return the dirname relative to PAGE_PATHS prefix. '''
path = os.path.split(page.get_relative_source_path())[0] + '/'
path = path.replace( os.path.sep, '/' )
# Try to lstrip the longest prefix first
for prefix in sorted(settings['PAGE_PATHS'], key=len, reverse=True):
if not prefix.endswith('/'): prefix += '/'
if path.startswith(prefix):
return path[len(prefix):-1]
raise UnexpectedException('Page outside of PAGE_PATHS ?!?')
def in_default_lang(page):
# page.in_default_lang property is undocumented (=unstable) interface
return page.lang == page.settings['DEFAULT_LANG']
def override_metadata(content_object):
if type(content_object) is not contents.Page:
return
page = content_object
path = get_path(page, page.settings)
def _override_value(page, key):
metadata = copy(page.metadata)
# We override the slug to include the path up to the filename
#metadata['slug'] = os.path.join(path, page.slug)
metadata['slug'] = path
# We have to account for non-default language and format either,
# e.g., PAGE_SAVE_AS or PAGE_LANG_SAVE_AS
infix = '' if in_default_lang(page) else 'LANG_'
return page.settings['PAGE_' + infix + key.upper()].format(**metadata)
for key in ('save_as', 'url'):
if not hasattr(page, 'override_' + key):
setattr(page, 'override_' + key, _override_value(page, key))
def set_relationships(generator):
def _all_pages():
return chain(generator.pages, generator.translations)
# initialize parents and children lists
for page in _all_pages():
page.parent = None
page.parents = []
page.children = []
# set immediate parents and children
for page in _all_pages():
# Parent of /a/b/ is /a/, parent of /a/b.html is /a/
parent_url = os.path.dirname(page.url[:-1])
if parent_url: parent_url += '/'
for page2 in _all_pages():
if page2.url == parent_url and page2 != page:
page.parent = page2
page2.children.append(page)
# If no parent found, try the parent of the default language page
if not page.parent and not in_default_lang(page):
for page2 in generator.pages:
if (page.slug == page2.slug and
os.path.dirname(page.source_path) ==
os.path.dirname(page2.source_path)):
# Only set the parent but not the children, obviously
page.parent = page2.parent
# set all parents (ancestors)
for page in _all_pages():
p = page
while p.parent:
page.parents.insert(0, p.parent)
p = p.parent
def register():
signals.content_object_init.connect(override_metadata)
signals.page_generator_finalized.connect(set_relationships)

View File

@ -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/

View File

@ -0,0 +1 @@
from .i18n_subsites import *

View File

@ -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)

View File

@ -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;">&nbsp</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;">&nbsp</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>`_.

View File

@ -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.

View File

@ -0,0 +1,2 @@
[jinja2: templates/**.html]

View File

@ -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 ""

View File

@ -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 %}

View File

@ -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"

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Testing site - An untranslated article</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Testovací stránka - An untranslated article</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -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">&lt;p&gt;Jednoduchý článek s překlady.
Zde je odkaz na &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;nějaký obrázek&lt;/a&gt;.&lt;/p&gt;
</content></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">&lt;p&gt;Ein einfacher Artikel mit einer Übersetzung.
Hier ist ein Link zur &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;einigem Bild&lt;/a&gt;.&lt;/p&gt;
</content></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">&lt;p&gt;A simple article with a translation.
Here is a link to &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;some image&lt;/a&gt;.&lt;/p&gt;
</content></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">&lt;p&gt;An article without a translation.
Here is a link to an &lt;a class="reference external" href="http://example.com/test/pages/untranslated-page.html"&gt;untranslated page&lt;/a&gt;&lt;/p&gt;
</content></entry></feed>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="cz">
<head>
<title>Welcome to our Testovací stránka</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="cz">
<head>
<title>Testovací stránka - 404 stránka</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="cz">
<head>
<title>Testovací stránka - Přeložený článek</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Testseite - An untranslated article</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -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">&lt;p&gt;Jednoduchý článek s překlady.
Zde je odkaz na &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;nějaký obrázek&lt;/a&gt;.&lt;/p&gt;
</content></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">&lt;p&gt;Ein einfacher Artikel mit einer Übersetzung.
Hier ist ein Link zur &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;einigem Bild&lt;/a&gt;.&lt;/p&gt;
</content></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">&lt;p&gt;A simple article with a translation.
Here is a link to &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;some image&lt;/a&gt;.&lt;/p&gt;
</content></entry></feed>

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Willkommen Sie zur unserer Testseite</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Testseite - Eine 404 Seite</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Testseite - Untranslated page</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Testseite - Ein übersetzter Artikel</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -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">&lt;p&gt;Jednoduchý článek s překlady.
Zde je odkaz na &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;nějaký obrázek&lt;/a&gt;.&lt;/p&gt;
</content></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">&lt;p&gt;Ein einfacher Artikel mit einer Übersetzung.
Hier ist ein Link zur &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;einigem Bild&lt;/a&gt;.&lt;/p&gt;
</content></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">&lt;p&gt;A simple article with a translation.
Here is a link to &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;some image&lt;/a&gt;.&lt;/p&gt;
</content></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">&lt;p&gt;An article without a translation.
Here is a link to an &lt;a class="reference external" href="http://example.com/test/pages/untranslated-page.html"&gt;untranslated page&lt;/a&gt;&lt;/p&gt;
</content></entry></feed>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Welcome to our Testing site</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Testing site - A 404 page</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Testing site - Untranslated page</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Testing site - A translated article</title>
<meta charset="utf-8" />
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</body>
</html>

View File

@ -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',
},
}

View File

@ -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(out))

View File

@ -1 +0,0 @@
from .list_files import *

View File

@ -1,40 +0,0 @@
from pelican import signals, contents
from os import listdir, getcwd
from os.path import isfile, join, sep, split
from copy import copy
from itertools import chain
'''
This plugin creates a URL hierarchy for articles that matches the
directory hierarchy of their sources.
'''
def lsfiles(path):
return [f for f in listdir(path) if isfile(join(path, f))]
def save_files(content_object):
if type(content_object) is not contents.Article:
return
article = content_object
path = split(article.get_relative_source_path())[0] + '/'
path = path.replace(sep, '/' )
abs_path = getcwd() + "/content/" + path
files = {i:(path+i) for i in lsfiles(abs_path)}
try:
fig_files = lsfiles(abs_path+"/fig")
except FileNotFoundError:
pass
else:
fig_files = {i:(path+"fig/"+i) for i in fig_files}
files.update(fig_files)
article.link_files = []
for i in files.items():
article.link_files.append(i)
article.link_files.sort()
def register():
signals.content_object_init.connect(save_files)

View File

@ -44,7 +44,7 @@ def process_content(article):
soup = BeautifulSoup(article._content,'lxml')
except FeatureNotFound:
soup = BeautifulSoup(article._content,'html.parser')
for img in soup.find_all('img',src=FORMAT_RE):
src = re.sub(article.settings['INTRASITE_LINK_REGEX'],'',img['src'].strip())
if src.startswith(('http://','https://','ftp://')): continue
@ -70,10 +70,10 @@ def process_content(article):
png_url = '/'.join((siteurl, preview_dir, linked_content.url + '.png'))
png_url = png_url.replace('\\', '/')
img['src'] = png_url
article._content = unicode(soup)
def get_pdf_imgs(generators):
# Process the articles and pages
for generator in generators:
@ -83,7 +83,7 @@ def get_pdf_imgs(generators):
elif isinstance(generator, PagesGenerator):
for page in generator.pages:
process_content(page)
def convert_pdfs(pelican):
"""

View File

@ -0,0 +1,31 @@
# Pelican source link plugin
This plugin for [Pelican](https://github.com/getpelican/pelican/) generate two attributes for articles:
- `source_link`: link to the source of the article from your forge
- `path_source_link`: link to the path of the article from your forge
## Installation
Then, add the plugin to your pelicanconf.py file:
```python
PLUGINS = [
# other plugins...
"source_link",
]
```
## Usage
To use the plugin, define a constant named `GIT_SOURCE` in your pelicanconf.py file:
```python
GIT_SOURCE_BASE_URLL = "https://github.com/your-username/your-repo"
```
This constant should contain the base URL of your Git repository where your source code is stored.
Once the constant is defined, the plugin will generate a `source_link` and a `path_source_link` attributes for each article in your Pelican project.
Those links are generated based on the article's source path relative to the root directory of your Pelican project. If the constant PATH is defined in pelicanconf.py, the plugin will use this value as the root directory. Otherwise, it will use the default value of "", which assumes that your Pelican project is located in the same directory as your pelicanconf.py file.

View File

@ -0,0 +1 @@
from .source_link import *

View File

@ -0,0 +1,37 @@
from pelican import signals
import logging
import os
logger = logging.getLogger(__name__)
from pelican.settings import DEFAULT_CONFIG
def set_default_settings(settings):
settings.setdefault("GIT_SOURCE_BASE_URL", None)
def init_default_config(pelican):
set_default_settings(DEFAULT_CONFIG)
if pelican:
set_default_settings(pelican.settings)
def source_link_generator(article_generator):
git_source = article_generator.settings.get(
"GIT_SOURCE_BASE_URL", DEFAULT_CONFIG["GIT_SOURCE_BASE_URL"]
)
root_path = article_generator.settings.get("PATH", DEFAULT_CONFIG.get("PATH", ""))
for article in article_generator.articles:
article_path = os.path.abspath(os.path.join(root_path, article.source_path))
relative_path = os.path.relpath(article_path, root_path)
if git_source.endswith("/"):
article.source_link = f"{git_source}{relative_path}"
else:
article.source_link = f"{git_source}/{relative_path}"
article.path_source_link = "/".join(article.source_link.split("/")[:-1])
def register():
signals.initialized.connect(init_default_config)
signals.article_generator_finalized.connect(source_link_generator)

View File

@ -0,0 +1,98 @@
tag_cloud
=========
This plugin generates a tag-cloud.
Installation
------------
In order to use to use this plugin, you have to edit(*) or create(+) the following files::
blog/
├── pelicanconf.py *
├── content
├── plugins +
│ └── tag_cloud.py +
└── themes
└── mytheme
├── templates
│ └── base.html *
└── static
└── css
└── style.css *
In **pelicanconf.py** you have to activate the plugin::
PLUGIN_PATHS = ["plugins"]
PLUGINS = ["tag_cloud"]
Into your **plugins** folder, you should add tag_cloud.py (from this repository).
In your theme files, you should change **base.html** to apply formats (and sizes) defined in **style.css**, as specified in "Settings", below.
Settings
--------
================================================ =====================================================
Setting name (followed by default value) What does it do?
================================================ =====================================================
``TAG_CLOUD_STEPS = 4`` Count of different font sizes in the tag
cloud.
``TAG_CLOUD_MAX_ITEMS = 100`` Maximum number of tags in the cloud.
``TAG_CLOUD_SORTING = 'random'`` The tag cloud ordering scheme. Valid values:
random, alphabetically, alphabetically-rev, size and
size-rev
``TAG_CLOUD_BADGE = True`` Optionnal setting : can bring **badges**, which mean
say : display the number of each tags present
on all articles.
================================================ =====================================================
The default theme does not include a tag cloud, but it is pretty easy to add one::
<ul class="tagcloud">
{% for tag in tag_cloud %}
<li class="tag-{{ tag.1 }}">
<a href="{{ SITEURL }}/{{ tag.0.url }}">
{{ tag.0 }}
{% if TAG_CLOUD_BADGE %}
<span class="badge">{{ tag.2 }}</span>
{% endif %}
</a>
</li>
{% endfor %}
</ul>
You should then also define CSS styles with appropriate classes (tag-1 to tag-N,
where N matches ``TAG_CLOUD_STEPS``), tag-1 being the most frequent, and
define a ``ul.tagcloud`` class with appropriate list-style to create the cloud.
You should copy/paste this **badge** CSS rule ``ul.tagcloud .list-group-item <span>.badge``
if you're using ``TAG_CLOUD_BADGE`` setting. (this rule, potentially long , is suggested to avoid
conflicts with CSS libs as twitter Bootstrap)
For example::
ul.tagcloud {
list-style: none;
padding: 0;
}
ul.tagcloud li {
display: inline-block;
}
li.tag-1 {
font-size: 150%;
}
li.tag-2 {
font-size: 120%;
}
/* ... add li.tag-3 etc, as much as needed */
ul.tagcloud .list-group-item span.badge {
background-color: grey;
color: white;
}
By default the tags in the cloud are sorted randomly, but if you prefers to have it alphabetically use the `alphabetically` (ascending) and `alphabetically-rev` (descending). Also is possible to sort the tags by it's size (number of articles with this specific tag) using the values `size` (ascending) and `size-rev` (descending).

View File

@ -0,0 +1,2 @@
from .tag_cloud import *

View File

@ -0,0 +1,90 @@
'''
tag_cloud
===================================
This plugin generates a tag cloud from available tags
'''
from __future__ import unicode_literals
from collections import defaultdict
from operator import itemgetter
import logging
import math
import random
from pelican import signals
logger = logging.getLogger(__name__)
def set_default_settings(settings):
settings.setdefault('TAG_CLOUD_STEPS', 4)
settings.setdefault('TAG_CLOUD_MAX_ITEMS', 100)
settings.setdefault('TAG_CLOUD_SORTING', 'random')
settings.setdefault('TAG_CLOUD_BADGE', False)
def init_default_config(pelican):
from pelican.settings import DEFAULT_CONFIG
set_default_settings(DEFAULT_CONFIG)
if(pelican):
set_default_settings(pelican.settings)
def generate_tag_cloud(generator):
tag_cloud = defaultdict(int)
for article in generator.articles:
for tag in getattr(article, 'tags', []):
tag_cloud[tag] += 1
tag_cloud = sorted(tag_cloud.items(), key=itemgetter(1), reverse=True)
tag_cloud = tag_cloud[:generator.settings.get('TAG_CLOUD_MAX_ITEMS')]
tags = list(map(itemgetter(1), tag_cloud))
if tags:
max_count = tags[0]
min_count = tags[-1]
steps = generator.settings.get('TAG_CLOUD_STEPS')
# calculate word sizes
def generate_tag(tag, count):
tag = (
tag,
int(math.floor(steps - (steps - 1) * math.log(count - min_count + 1)
/ (math.log(max_count - min_count + 1) or 1)))
)
if generator.settings.get('TAG_CLOUD_BADGE'):
tag += (count,)
return tag
tag_cloud = [
generate_tag(tag, count)
for tag, count in tag_cloud
]
sorting = generator.settings.get('TAG_CLOUD_SORTING')
if sorting == 'alphabetically':
tag_cloud.sort(key=lambda elem: elem[0].name)
elif sorting == 'alphabetically-rev':
tag_cloud.sort(key=lambda elem: elem[0].name, reverse=True)
elif sorting == 'size':
tag_cloud.sort(key=lambda elem: elem[1])
elif sorting == 'size-rev':
tag_cloud.sort(key=lambda elem: elem[1], reverse=True)
elif sorting == 'random':
random.shuffle(tag_cloud)
else:
logger.warning("setting for TAG_CLOUD_SORTING not recognized: %s, "
"falling back to 'random'", sorting)
random.shuffle(tag_cloud)
# make available in context
generator.tag_cloud = tag_cloud
generator._update_context(['tag_cloud'])
def register():
signals.initialized.connect(init_default_config)
signals.article_generator_finalized.connect(generate_tag_cloud)

View File

@ -10,11 +10,12 @@ import sys
sys.path.append(os.curdir)
from pelicanconf import *
SITEURL = 'opytex.org'
RELATIVE_URLS = True
# If your site is available via HTTPS, make sure SITEURL begins with https://
SITEURL = 'https://opytex.org/enseignements/2022-2023/'
RELATIVE_URLS = False
FEED_ALL_ATOM = 'feeds/all.atom.xml'
CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml'
DELETE_OUTPUT_DIRECTORY = True

View File

@ -1,11 +1,15 @@
beautifulsoup4==4.9.1
blinker==1.4
docutils==0.13.1
feedgenerator==1.9
Jinja2==2.9.6
MarkupSafe==1.0
pelican==3.7.1
Pygments==2.2.0
python-dateutil==2.6.0
pytz==2017.2
six==1.10.0
Unidecode==0.4.20
bs4==0.0.1
docutils==0.16
feedgenerator==1.9.1
Jinja2==2.11.2
MarkupSafe==1.1.1
pelican==4.2.0
Pygments==2.6.1
python-dateutil==2.8.1
pytz==2020.1
six==1.15.0
soupsieve==2.0.1
Unidecode==1.1.1
Wand==0.6.2

110
tasks.py Normal file
View File

@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
import os
import shutil
import sys
import datetime
from invoke import task
from invoke.util import cd
from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
from pelican.settings import DEFAULT_CONFIG, get_settings_from_file
SETTINGS_FILE_BASE = 'pelicanconf.py'
SETTINGS = {}
SETTINGS.update(DEFAULT_CONFIG)
LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE)
SETTINGS.update(LOCAL_SETTINGS)
CONFIG = {
'settings_base': SETTINGS_FILE_BASE,
'settings_publish': 'publishconf.py',
# Output path. Can be absolute or relative to tasks.py. Default: 'output'
'deploy_path': SETTINGS['OUTPUT_PATH'],
# Port for `serve`
'port': 8000,
}
@task
def clean(c):
"""Remove generated files"""
if os.path.isdir(CONFIG['deploy_path']):
shutil.rmtree(CONFIG['deploy_path'])
os.makedirs(CONFIG['deploy_path'])
@task
def build(c):
"""Build local version of site"""
c.run('pelican -s {settings_base}'.format(**CONFIG))
@task
def rebuild(c):
"""`build` with the delete switch"""
c.run('pelican -d -s {settings_base}'.format(**CONFIG))
@task
def regenerate(c):
"""Automatically regenerate site upon file modification"""
c.run('pelican -r -s {settings_base}'.format(**CONFIG))
@task
def serve(c):
"""Serve site at http://localhost:$PORT/ (default port is 8000)"""
class AddressReuseTCPServer(RootedHTTPServer):
allow_reuse_address = True
server = AddressReuseTCPServer(
CONFIG['deploy_path'],
('', CONFIG['port']),
ComplexHTTPRequestHandler)
sys.stderr.write('Serving on port {port} ...\n'.format(**CONFIG))
server.serve_forever()
@task
def reserve(c):
"""`build`, then `serve`"""
build(c)
serve(c)
@task
def preview(c):
"""Build production version of site"""
c.run('pelican -s {settings_publish}'.format(**CONFIG))
@task
def livereload(c):
"""Automatically reload browser tab upon file modification."""
from livereload import Server
build(c)
server = Server()
# Watch the base settings file
server.watch(CONFIG['settings_base'], lambda: build(c))
# Watch content source files
content_file_extensions = ['.md', '.rst']
for extension in content_file_extensions:
content_blob = '{0}/**/*{1}'.format(SETTINGS['PATH'], extension)
server.watch(content_blob, lambda: build(c))
# Watch the theme's templates and static assets
theme_path = SETTINGS['THEME']
server.watch('{}/templates/*.html'.format(theme_path), lambda: build(c))
static_file_extensions = ['.css', '.js']
for extension in static_file_extensions:
static_file = '{0}/static/**/*{1}'.format(theme_path, extension)
server.watch(static_file, lambda: build(c))
# Serve output path on configured port
server.serve(port=CONFIG['port'], root=CONFIG['deploy_path'])
@task
def publish(c):
"""Publish to production via rsync"""
c.run('pelican -s {settings_publish}'.format(**CONFIG))
c.run(
'rsync --delete --exclude ".DS_Store" -pthrvz -c '
'-e "ssh -p {ssh_port}" '
'{} {ssh_user}@{ssh_host}:{ssh_path}'.format(
CONFIG['deploy_path'].rstrip('/') + '/',
**CONFIG))

59
theme/.gitignore vendored
View File

@ -1,59 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
tests/output
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
venv
# NPM
node_modules

View File

@ -1,13 +0,0 @@
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
install:
- pip install pelican markdown
before_script:
- git clone https://github.com/getpelican/pelican-plugins plugins
script: pelican -s tests/pelicanconf.py
notifications:
email: false

View File

@ -2,11 +2,9 @@
The minimalist [Pelican](http://blog.getpelican.com/) theme.
## Notes
## Note
- **DO NOT** send any questions to my personal email, they are **IGNORED**. If you have questions open an issue.
- This theme is not under development anymore, it's stable and has a lot of features. This means that new requests (not bug fixes) will be discussed and have lower priorities.
- If you want a version of this theme to Jekyll or Hugo open an issue and let's discuss it.
DON'T send any questions, issues or anything related to Flex to my personal email. They will be IGNORED by now. Your question maybe also is someone else's question. They SHOULD be public, so others can know how to fix configuration problems.
## Features
@ -26,7 +24,7 @@ The minimalist [Pelican](http://blog.getpelican.com/) theme.
- [AddThis](http://www.addthis.com/) Share Buttons and Related Posts
- [Disqus](https://disqus.com/)
- [Gauges Analytics](http://get.gaug.es/)
- [Google AdSense](https://www.google.com.br/adsense/start/) (new in 2.1.0)
- [Google AdSense](https://www.google.com.br/adsense/start/) (new in 2.1)
- [Google Analytics](https://www.google.com/analytics/web/)
- [Google Tag Manager](https://www.google.com/tagmanager/)
- [Piwik Analytics](http://piwik.org/)
@ -34,18 +32,28 @@ The minimalist [Pelican](http://blog.getpelican.com/) theme.
## Plugins Support
- [Github Corners](https://github.com/tholman/github-corners) (new in 2.2.0)
- [Github Corners](https://github.com/tholman/github-corners) (new in 2.2)
- [I18N Sub-sites](https://github.com/getpelican/pelican-plugins/tree/master/i18n_subsites) (new in 2.0)
- [Minute read](https://github.com/getpelican/pelican-plugins/tree/master/post_stats) (new in 2.0)
- [Related Posts](https://github.com/getpelican/pelican-plugins/tree/master/related_posts)
- [Representative image](https://github.com/getpelican/pelican-plugins/tree/master/representative_image) (new in 2.2.0)
- [Neighbors](https://github.com/getpelican/pelican-plugins/tree/master/neighbors) (new in 2.2.0)
- [Representative image](https://github.com/getpelican/pelican-plugins/tree/master/representative_image) (new in 2.2)
- [Neighbors](https://github.com/getpelican/pelican-plugins/tree/master/neighbors) (new in 2.2)
## Install
The best way to install is over [pelican-themes](https://github.com/getpelican/pelican-themes).
The recommend way to install is over [pelican-themes](https://github.com/getpelican/pelican-themes).
The alternative way is to clone this repository. The `master` branch is stable and is safe to checkout, but I would recommend you to checkout a tag branch.
The `master` branch is the development branch. If you're happy with fresh new things and maybe broken things you can clone the `master`, but I would recommend to you to clone a tag branch.
## Documentation
[Go to Wiki](https://github.com/alexandrevicenzi/Flex/wiki)
## Contributing
Always open an issue before sending a PR. Talk about the problem/feature that you want to fix. If it's really a good thing you can submit your PR. If you send an PR without talking about before what it is, you may work for nothing.
As always, if you want something that only make sense to you, just fork Flex and start a new theme.
## Donate
@ -53,34 +61,19 @@ Are you using this theme? Support bug fixes and new features.
[Click here](https://www.alexandrevicenzi.com/donate) to donate.
## Documentation
The documentation covers most of the settings available and how to use this theme.
If something is missing or broken you can open a PR or fix the documentation by yourself.
[Flex Wiki](https://github.com/alexandrevicenzi/Flex/wiki)
## Live example
You can see how this theme looks like at [http://flex.alxd.me/blog/](http://flex.alxd.me/blog/).
The code is available in this project under `docs` folder.
The code is available in this project inside `docs` folder.
## Contributing
**ALWAYS** open an issue before sending a PR.
Discuss the problem/feature that you want to code.
After discussing, send a PR with your changes.
As always, if you want something that only makes sense to you, fork Flex and create a new theme.
## Translations
## Translate
Translate this theme to new languages at [Transifex](https://www.transifex.com/alexandrevicenzi/flex-pelican/).
![Translations](https://github.com/alexandrevicenzi/Flex/blob/master/translations/translation_chart.png)
Read more about [Translation Support](https://github.com/alexandrevicenzi/Flex/wiki/Translations) in the Wiki.
Read more about [Translation Support](https://github.com/alexandrevicenzi/Flex/wiki/Translations).
## License

1
theme/node_modules/.bin/atob generated vendored Symbolic link
View File

@ -0,0 +1 @@
../atob/bin/atob.js

1
theme/node_modules/.bin/color-support generated vendored Symbolic link
View File

@ -0,0 +1 @@
../color-support/bin.js

1
theme/node_modules/.bin/gulp generated vendored Symbolic link
View File

@ -0,0 +1 @@
../gulp/bin/gulp.js

1
theme/node_modules/.bin/mkdirp generated vendored Symbolic link
View File

@ -0,0 +1 @@
../mkdirp/bin/cmd.js

1
theme/node_modules/.bin/semver generated vendored Symbolic link
View File

@ -0,0 +1 @@
../semver/bin/semver

1
theme/node_modules/.bin/strip-bom generated vendored Symbolic link
View File

@ -0,0 +1 @@
../strip-bom/cli.js

1
theme/node_modules/.bin/user-home generated vendored Symbolic link
View File

@ -0,0 +1 @@
../user-home/cli.js

1
theme/node_modules/.bin/which generated vendored Symbolic link
View File

@ -0,0 +1 @@
../which/bin/which

21
theme/node_modules/ansi-gray/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) <%= year() %>, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

14
theme/node_modules/ansi-gray/index.js generated vendored Normal file
View File

@ -0,0 +1,14 @@
/*!
* ansi-gray <https://github.com/jonschlinkert/ansi-gray>
*
* Copyright (c) 2015, Jon Schlinkert.
* Licensed under the MIT License.
*/
'use strict';
var wrap = require('ansi-wrap');
module.exports = function gray(message) {
return wrap(90, 39, message);
};

86
theme/node_modules/ansi-gray/package.json generated vendored Normal file
View File

@ -0,0 +1,86 @@
{
"_from": "ansi-gray@^0.1.1",
"_id": "ansi-gray@0.1.1",
"_inBundle": false,
"_integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=",
"_location": "/ansi-gray",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "ansi-gray@^0.1.1",
"name": "ansi-gray",
"escapedName": "ansi-gray",
"rawSpec": "^0.1.1",
"saveSpec": null,
"fetchSpec": "^0.1.1"
},
"_requiredBy": [
"/fancy-log"
],
"_resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz",
"_shasum": "2962cf54ec9792c48510a3deb524436861ef7251",
"_spec": "ansi-gray@^0.1.1",
"_where": "/home/lafrite/tmp/site_pelican/theme/node_modules/fancy-log",
"author": {
"name": "Jon Schlinkert",
"url": "https://github.com/jonschlinkert"
},
"bugs": {
"url": "https://github.com/jonschlinkert/ansi-gray/issues"
},
"bundleDependencies": false,
"dependencies": {
"ansi-wrap": "0.1.0"
},
"deprecated": false,
"description": "The color gray, in ansi.",
"devDependencies": {
"mocha": "*"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"homepage": "https://github.com/jonschlinkert/ansi-gray",
"keywords": [
"gray",
"256",
"ansi",
"cli",
"color",
"colors",
"colour",
"command",
"command-line",
"console",
"format",
"formatting",
"iterm",
"log",
"logging",
"rgb",
"shell",
"string",
"style",
"styles",
"styling",
"terminal",
"text",
"tty",
"xterm"
],
"license": "MIT",
"main": "index.js",
"name": "ansi-gray",
"repository": {
"type": "git",
"url": "git+https://github.com/jonschlinkert/ansi-gray.git"
},
"scripts": {
"test": "mocha"
},
"version": "0.1.1"
}

74
theme/node_modules/ansi-gray/readme.md generated vendored Normal file
View File

@ -0,0 +1,74 @@
# ansi-gray [![NPM version](https://badge.fury.io/js/ansi-gray.svg)](http://badge.fury.io/js/ansi-gray)
> The color gray, in ansi.
## Install
Install with [npm](https://www.npmjs.com/)
```sh
$ npm i ansi-gray --save
```
## Usage
```js
var gray = require('ansi-gray');
```
## Related projects
* [ansi-reset](https://github.com/jonschlinkert/ansi-reset)
* [ansi-bold](https://github.com/jonschlinkert/ansi-bold)
* [ansi-dim](https://github.com/jonschlinkert/ansi-dim)
* [ansi-italic](https://github.com/jonschlinkert/ansi-italic)
* [ansi-underline](https://github.com/jonschlinkert/ansi-underline)
* [ansi-inverse](https://github.com/jonschlinkert/ansi-inverse)
* [ansi-hidden](https://github.com/jonschlinkert/ansi-hidden)
* [ansi-strikethrough](https://github.com/jonschlinkert/ansi-strikethrough)
* [ansi-black](https://github.com/jonschlinkert/ansi-black)
* [ansi-red](https://github.com/jonschlinkert/ansi-red)
* [ansi-green](https://github.com/jonschlinkert/ansi-green)
* [ansi-yellow](https://github.com/jonschlinkert/ansi-yellow)
* [ansi-blue](https://github.com/jonschlinkert/ansi-blue)
* [ansi-magenta](https://github.com/jonschlinkert/ansi-magenta)
* [ansi-cyan](https://github.com/jonschlinkert/ansi-cyan)
* [ansi-white](https://github.com/jonschlinkert/ansi-white)
* [ansi-gray](https://github.com/jonschlinkert/ansi-gray)
* [ansi-grey](https://github.com/jonschlinkert/ansi-grey)
* [ansi-bgblack](https://github.com/jonschlinkert/ansi-bgblack)
* [ansi-bgred](https://github.com/jonschlinkert/ansi-bgred)
* [ansi-bggreen](https://github.com/jonschlinkert/ansi-bggreen)
* [ansi-bgyellow](https://github.com/jonschlinkert/ansi-bgyellow)
* [ansi-bgblue](https://github.com/jonschlinkert/ansi-bgblue)
* [ansi-bgmagenta](https://github.com/jonschlinkert/ansi-bgmagenta)
* [ansi-bgcyan](https://github.com/jonschlinkert/ansi-bgcyan)
* [ansi-bgwhite](https://github.com/jonschlinkert/ansi-bgwhite)
## Running tests
Install dev dependencies:
```sh
$ npm i -d && npm test
```
## Contributing
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/ansi-gray/issues/new)
## Author
**Jon Schlinkert**
+ [github/jonschlinkert](https://github.com/jonschlinkert)
+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert)
## License
Copyright © 2015 Jon Schlinkert
Released under the MIT license.
***
_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on May 21, 2015._

4
theme/node_modules/ansi-regex/index.js generated vendored Normal file
View File

@ -0,0 +1,4 @@
'use strict';
module.exports = function () {
return /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]/g;
};

21
theme/node_modules/ansi-regex/license generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

109
theme/node_modules/ansi-regex/package.json generated vendored Normal file
View File

@ -0,0 +1,109 @@
{
"_from": "ansi-regex@^2.0.0",
"_id": "ansi-regex@2.1.1",
"_inBundle": false,
"_integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"_location": "/ansi-regex",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "ansi-regex@^2.0.0",
"name": "ansi-regex",
"escapedName": "ansi-regex",
"rawSpec": "^2.0.0",
"saveSpec": null,
"fetchSpec": "^2.0.0"
},
"_requiredBy": [
"/has-ansi",
"/strip-ansi"
],
"_resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"_shasum": "c3b33ab5ee360d86e0e628f0468ae7ef27d654df",
"_spec": "ansi-regex@^2.0.0",
"_where": "/home/lafrite/tmp/site_pelican/theme/node_modules/has-ansi",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/chalk/ansi-regex/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "Regular expression for matching ANSI escape codes",
"devDependencies": {
"ava": "0.17.0",
"xo": "0.16.0"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"homepage": "https://github.com/chalk/ansi-regex#readme",
"keywords": [
"ansi",
"styles",
"color",
"colour",
"colors",
"terminal",
"console",
"cli",
"string",
"tty",
"escape",
"formatting",
"rgb",
"256",
"shell",
"xterm",
"command-line",
"text",
"regex",
"regexp",
"re",
"match",
"test",
"find",
"pattern"
],
"license": "MIT",
"maintainers": [
{
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
{
"name": "Joshua Appelman",
"email": "jappelman@xebia.com",
"url": "jbnicolai.com"
},
{
"name": "JD Ballard",
"email": "i.am.qix@gmail.com",
"url": "github.com/qix-"
}
],
"name": "ansi-regex",
"repository": {
"type": "git",
"url": "git+https://github.com/chalk/ansi-regex.git"
},
"scripts": {
"test": "xo && ava --verbose",
"view-supported": "node fixtures/view-codes.js"
},
"version": "2.1.1",
"xo": {
"rules": {
"guard-for-in": 0,
"no-loop-func": 0
}
}
}

39
theme/node_modules/ansi-regex/readme.md generated vendored Normal file
View File

@ -0,0 +1,39 @@
# ansi-regex [![Build Status](https://travis-ci.org/chalk/ansi-regex.svg?branch=master)](https://travis-ci.org/chalk/ansi-regex)
> Regular expression for matching [ANSI escape codes](http://en.wikipedia.org/wiki/ANSI_escape_code)
## Install
```
$ npm install --save ansi-regex
```
## Usage
```js
const ansiRegex = require('ansi-regex');
ansiRegex().test('\u001b[4mcake\u001b[0m');
//=> true
ansiRegex().test('cake');
//=> false
'\u001b[4mcake\u001b[0m'.match(ansiRegex());
//=> ['\u001b[4m', '\u001b[0m']
```
## FAQ
### Why do you test for codes not in the ECMA 48 standard?
Some of the codes we run as a test are codes that we acquired finding various lists of non-standard or manufacturer specific codes. If I recall correctly, we test for both standard and non-standard codes, as most of them follow the same or similar format and can be safely matched in strings without the risk of removing actual string content. There are a few non-standard control codes that do not follow the traditional format (i.e. they end in numbers) thus forcing us to exclude them from the test because we cannot reliably match them.
On the historical side, those ECMA standards were established in the early 90's whereas the VT100, for example, was designed in the mid/late 70's. At that point in time, control codes were still pretty ungoverned and engineers used them for a multitude of things, namely to activate hardware ports that may have been proprietary. Somewhere else you see a similar 'anarchy' of codes is in the x86 architecture for processors; there are a ton of "interrupts" that can mean different things on certain brands of processors, most of which have been phased out.
## License
MIT © [Sindre Sorhus](http://sindresorhus.com)

65
theme/node_modules/ansi-styles/index.js generated vendored Normal file
View File

@ -0,0 +1,65 @@
'use strict';
function assembleStyles () {
var styles = {
modifiers: {
reset: [0, 0],
bold: [1, 22], // 21 isn't widely supported and 22 does the same thing
dim: [2, 22],
italic: [3, 23],
underline: [4, 24],
inverse: [7, 27],
hidden: [8, 28],
strikethrough: [9, 29]
},
colors: {
black: [30, 39],
red: [31, 39],
green: [32, 39],
yellow: [33, 39],
blue: [34, 39],
magenta: [35, 39],
cyan: [36, 39],
white: [37, 39],
gray: [90, 39]
},
bgColors: {
bgBlack: [40, 49],
bgRed: [41, 49],
bgGreen: [42, 49],
bgYellow: [43, 49],
bgBlue: [44, 49],
bgMagenta: [45, 49],
bgCyan: [46, 49],
bgWhite: [47, 49]
}
};
// fix humans
styles.colors.grey = styles.colors.gray;
Object.keys(styles).forEach(function (groupName) {
var group = styles[groupName];
Object.keys(group).forEach(function (styleName) {
var style = group[styleName];
styles[styleName] = group[styleName] = {
open: '\u001b[' + style[0] + 'm',
close: '\u001b[' + style[1] + 'm'
};
});
Object.defineProperty(styles, groupName, {
value: group,
enumerable: false
});
});
return styles;
}
Object.defineProperty(module, 'exports', {
enumerable: true,
get: assembleStyles
});

21
theme/node_modules/ansi-styles/license generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

90
theme/node_modules/ansi-styles/package.json generated vendored Normal file
View File

@ -0,0 +1,90 @@
{
"_from": "ansi-styles@^2.2.1",
"_id": "ansi-styles@2.2.1",
"_inBundle": false,
"_integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
"_location": "/ansi-styles",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "ansi-styles@^2.2.1",
"name": "ansi-styles",
"escapedName": "ansi-styles",
"rawSpec": "^2.2.1",
"saveSpec": null,
"fetchSpec": "^2.2.1"
},
"_requiredBy": [
"/chalk"
],
"_resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"_shasum": "b432dd3358b634cf75e1e4664368240533c1ddbe",
"_spec": "ansi-styles@^2.2.1",
"_where": "/home/lafrite/tmp/site_pelican/theme/node_modules/chalk",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/chalk/ansi-styles/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "ANSI escape codes for styling strings in the terminal",
"devDependencies": {
"mocha": "*"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"homepage": "https://github.com/chalk/ansi-styles#readme",
"keywords": [
"ansi",
"styles",
"color",
"colour",
"colors",
"terminal",
"console",
"cli",
"string",
"tty",
"escape",
"formatting",
"rgb",
"256",
"shell",
"xterm",
"log",
"logging",
"command-line",
"text"
],
"license": "MIT",
"maintainers": [
{
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
{
"name": "Joshua Appelman",
"email": "jappelman@xebia.com",
"url": "jbnicolai.com"
}
],
"name": "ansi-styles",
"repository": {
"type": "git",
"url": "git+https://github.com/chalk/ansi-styles.git"
},
"scripts": {
"test": "mocha"
},
"version": "2.2.1"
}

86
theme/node_modules/ansi-styles/readme.md generated vendored Normal file
View File

@ -0,0 +1,86 @@
# ansi-styles [![Build Status](https://travis-ci.org/chalk/ansi-styles.svg?branch=master)](https://travis-ci.org/chalk/ansi-styles)
> [ANSI escape codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors_and_Styles) for styling strings in the terminal
You probably want the higher-level [chalk](https://github.com/chalk/chalk) module for styling your strings.
![](screenshot.png)
## Install
```
$ npm install --save ansi-styles
```
## Usage
```js
var ansi = require('ansi-styles');
console.log(ansi.green.open + 'Hello world!' + ansi.green.close);
```
## API
Each style has an `open` and `close` property.
## Styles
### Modifiers
- `reset`
- `bold`
- `dim`
- `italic` *(not widely supported)*
- `underline`
- `inverse`
- `hidden`
- `strikethrough` *(not widely supported)*
### Colors
- `black`
- `red`
- `green`
- `yellow`
- `blue`
- `magenta`
- `cyan`
- `white`
- `gray`
### Background colors
- `bgBlack`
- `bgRed`
- `bgGreen`
- `bgYellow`
- `bgBlue`
- `bgMagenta`
- `bgCyan`
- `bgWhite`
## Advanced usage
By default you get a map of styles, but the styles are also available as groups. They are non-enumerable so they don't show up unless you access them explicitly. This makes it easier to expose only a subset in a higher-level module.
- `ansi.modifiers`
- `ansi.colors`
- `ansi.bgColors`
###### Example
```js
console.log(ansi.colors.green.open);
```
## License
MIT © [Sindre Sorhus](http://sindresorhus.com)

21
theme/node_modules/ansi-wrap/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

89
theme/node_modules/ansi-wrap/README.md generated vendored Normal file
View File

@ -0,0 +1,89 @@
# ansi-wrap [![NPM version](https://badge.fury.io/js/ansi-wrap.svg)](http://badge.fury.io/js/ansi-wrap)
> Create ansi colors by passing the open and close codes.
## Install
Install with [npm](https://www.npmjs.com/)
```sh
$ npm i ansi-wrap --save
```
## Usage
```js
var wrap = require('ansi-wrap');
```
**Example**
Pass codes for [ansi magenta background](https://github.com/jonschlinkert/ansi-bgmagenta):
```js
console.log(wrap(45, 49, 'This is a message...'));
//=> '\u001b[45mfoo\u001b[49m'
```
Which prints out...
[![screen shot 2015-05-21 at 8 28 32 pm](https://cloud.githubusercontent.com/assets/383994/7761769/12488afa-fff8-11e4-9cc1-71a8a6ec14a4.png)](https://www.npmjs.com/)
## Related projects
This is used in these projects:
* [ansi-reset](https://github.com/jonschlinkert/ansi-reset)
* [ansi-bold](https://github.com/jonschlinkert/ansi-bold)
* [ansi-dim](https://github.com/jonschlinkert/ansi-dim)
* [ansi-italic](https://github.com/jonschlinkert/ansi-italic)
* [ansi-underline](https://github.com/jonschlinkert/ansi-underline)
* [ansi-inverse](https://github.com/jonschlinkert/ansi-inverse)
* [ansi-hidden](https://github.com/jonschlinkert/ansi-hidden)
* [ansi-strikethrough](https://github.com/jonschlinkert/ansi-strikethrough)
* [ansi-black](https://github.com/jonschlinkert/ansi-black)
* [ansi-red](https://github.com/jonschlinkert/ansi-red)
* [ansi-green](https://github.com/jonschlinkert/ansi-green)
* [ansi-yellow](https://github.com/jonschlinkert/ansi-yellow)
* [ansi-blue](https://github.com/jonschlinkert/ansi-blue)
* [ansi-magenta](https://github.com/jonschlinkert/ansi-magenta)
* [ansi-cyan](https://github.com/jonschlinkert/ansi-cyan)
* [ansi-white](https://github.com/jonschlinkert/ansi-white)
* [ansi-gray](https://github.com/jonschlinkert/ansi-gray)
* [ansi-grey](https://github.com/jonschlinkert/ansi-grey)
* [ansi-bgblack](https://github.com/jonschlinkert/ansi-bgblack)
* [ansi-bgred](https://github.com/jonschlinkert/ansi-bgred)
* [ansi-bggreen](https://github.com/jonschlinkert/ansi-bggreen)
* [ansi-bgyellow](https://github.com/jonschlinkert/ansi-bgyellow)
* [ansi-bgblue](https://github.com/jonschlinkert/ansi-bgblue)
* [ansi-bgmagenta](https://github.com/jonschlinkert/ansi-bgmagenta)
* [ansi-bgcyan](https://github.com/jonschlinkert/ansi-bgcyan)
* [ansi-bgwhite](https://github.com/jonschlinkert/ansi-bgwhite)
## Running tests
Install dev dependencies:
```sh
$ npm i -d && npm test
```
## Contributing
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/ansi-wrap/issues/new)
## Author
**Jon Schlinkert**
+ [github/jonschlinkert](https://github.com/jonschlinkert)
+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert)
## License
Copyright © 2015 Jon Schlinkert
Released under the MIT license.
***
_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on May 21, 2015._

5
theme/node_modules/ansi-wrap/index.js generated vendored Normal file
View File

@ -0,0 +1,5 @@
'use strict';
module.exports = function(a, b, msg) {
return '\u001b['+ a + 'm' + msg + '\u001b[' + b + 'm';
};

59
theme/node_modules/ansi-wrap/package.json generated vendored Normal file
View File

@ -0,0 +1,59 @@
{
"_from": "ansi-wrap@0.1.0",
"_id": "ansi-wrap@0.1.0",
"_inBundle": false,
"_integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=",
"_location": "/ansi-wrap",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "ansi-wrap@0.1.0",
"name": "ansi-wrap",
"escapedName": "ansi-wrap",
"rawSpec": "0.1.0",
"saveSpec": null,
"fetchSpec": "0.1.0"
},
"_requiredBy": [
"/ansi-gray"
],
"_resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
"_shasum": "a82250ddb0015e9a27ca82e82ea603bbfa45efaf",
"_spec": "ansi-wrap@0.1.0",
"_where": "/home/lafrite/tmp/site_pelican/theme/node_modules/ansi-gray",
"author": {
"name": "Jon Schlinkert",
"url": "https://github.com/jonschlinkert"
},
"bugs": {
"url": "https://github.com/jonschlinkert/ansi-wrap/issues"
},
"bundleDependencies": false,
"dependencies": {},
"deprecated": false,
"description": "Create ansi colors by passing the open and close codes.",
"devDependencies": {},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"homepage": "https://github.com/jonschlinkert/ansi-wrap",
"keywords": [],
"license": {
"type": "MIT",
"url": "https://github.com/jonschlinkert/ansi-wrap/blob/master/LICENSE"
},
"main": "index.js",
"name": "ansi-wrap",
"repository": {
"type": "git",
"url": "git+https://github.com/jonschlinkert/ansi-wrap.git"
},
"scripts": {
"test": "mocha"
},
"version": "0.1.0"
}

4
theme/node_modules/archy/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.6
- 0.8

18
theme/node_modules/archy/LICENSE generated vendored Normal file
View File

@ -0,0 +1,18 @@
This software is released under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

24
theme/node_modules/archy/examples/beep.js generated vendored Normal file
View File

@ -0,0 +1,24 @@
var archy = require('../');
var s = archy({
label : 'beep',
nodes : [
'ity',
{
label : 'boop',
nodes : [
{
label : 'o_O',
nodes : [
{
label : 'oh',
nodes : [ 'hello', 'puny' ]
},
'human'
]
},
'party\ntime!'
]
}
]
});
console.log(s);

25
theme/node_modules/archy/examples/multi_line.js generated vendored Normal file
View File

@ -0,0 +1,25 @@
var archy = require('../');
var s = archy({
label : 'beep\none\ntwo',
nodes : [
'ity',
{
label : 'boop',
nodes : [
{
label : 'o_O\nwheee',
nodes : [
{
label : 'oh',
nodes : [ 'hello', 'puny\nmeat' ]
},
'creature'
]
},
'party\ntime!'
]
}
]
});
console.log(s);

Some files were not shown because too many files have changed in this diff Show More