Creare un plugin per Pelican e condividerlo su Github

Ho studiato il funzionamento dei plugin di Pelican ed ho imparato a farne di nuovi! In questo articolo spiego come ho creato il plugin “random_quote”, che inserisce una citazione casuale in ogni pagina di questo sito. Inoltre, spiego brevemente come l’ho condiviso nella community di GitHub.

Pubblicato da Roberto Fusi in Web Development. Aggiornato .
Lunghezza testo: 1708 parole. Stima tempo lettura: 7 minuti.
Tag: python, pelican. .

Il funzionamento di Pelican è molto semplice e si basa su questa idea: leggere dei file come input e combinarli in modo opportuno per generare dei file di output. Il processo di creazione di un sito, è scandito da questi componenti:

  1. Readers, usati per leggere file in vari formati (HTML, Markdown, HTML etc…) ed estrarre da questi dei metadati (author, tags, category, etc…) e dei contenuti;
  2. Generators, usati per generare i vari output, per esempio pagine ed articoli, sfruttando i metadati ed contenuti resi disponibili dai readers ed altri componenti esterni, come i template di Jinja2;
  3. Writers, usati per scrivere i file di output (files HTML, feed RSS, etc…) che formeranno il sito statico finale.

Un plugin di Pelican sfrutta i segnali inviati dal framework, per stabilire in che momento eseguire le istruzioni per cui è programmato. In particolare, la funzione register() ha il compito di associare un certo segnale, ad una funzione che implementa il plugin.

Attualmente, nella versione 3.7.1, il framework di Pelican rende disponibili i seguenti segnali.

Segnali principali del plugin:

  • initialized, passa al plugin l’oggetto pelican (pelican object) appena viene inizializzato (torna utile per impostare costanti ed accedere a pelican.settings);
  • get_generators, passa al plugin l’oggetto pelican (pelican object) appena si rende disponibile la lista dei Generators da eseguire;
  • get_writer, passa al plugin l’oggetto pelican (pelican object) appena si rende disponibile la lista dei Writers da eseguire;
  • finalized, passa al plugin l’oggetto pelican (pelican object) prima di terminare ed uscire dal programma.

Segnali dei Readers:

  • readers_init, passa al plugin la lista di tutti i Readers inizializzati;
  • content_object_init, passa al plugin il contenuto inizializzato.

Segnali dei Generators

  • generator_init, passa al plugin l’istanza di un generatore generico;
  • article_generator_init, passa al plugin il generatore di articoli appena viene inizializzato;
  • article_generator_preread, passa al plugin il generatore di articoli prima che elabori i contenuti con generate_context
  • article_generator_context, passa al plugin il generatore di articoli con tutti i metadati raccolti;
  • article_generator_pretaxonomy, passa al plugin il generatore di articoli prima che vengaono generate le liste per categorie e tag;
  • article_generator_finalized, passa al plugin il generatore di articoli e viene invocato al termine di generate_context
  • article_generator_write_article, passa al plugin il generatore di articoli con il contenuto prima che si passi al Writer;
  • page_generator_init, passa al plugin il generatore di pagine appena viene inizializzato;
  • page_generator_preread, passa al plugin il generatore di pagine prima che elabori i contenuti con generate_context
  • page_generator_context, passa al plugin il generatore di pagine con tutti i metadati raccolti;
  • page_generator_finalized, passa al plugin il generatore di pagine e viene invocato al termine di generate_context
  • static_generator_init, passa al plugin il generatore di contenuti statici appena viene inizializzato;
  • static_generator_preread, passa al plugin il generatore di contenuti statici prima che elabori i contenuti con generate_context
  • static_generator_context, passa al plugin il generatore di contenuti statici con tutti i metadati raccolti;
  • static_generator_finalized, passa al plugin il generatore di contenuti statici e viene invocato al termine di generate_context
  • all_generators_finalized, passa al plugin la lista generators di tutti i generatori attivati.

Writers signals

  • article_writer_finalized, passa al plugin il generatore di articoli dopo aver scritto i file, ma prima che il generatore venga terminato;
  • page_writer_finalized, passa al plugin il generatore di pagine dopo aver scritto i file, ma prima che il generatore venga terminato;
  • content_written, passa al plugin il percorso del file di un contenuto, che è stato scritto su disco;
  • feed_written, passa al plugin il percorso del file di un feed, che è stato scritto su disco.

In questo articolo, descrivo come ho creato il plugin “random_quote” che permette di inserire una citazione casuale, letta da un DB SQLite, in ogni pagina di un sito Pelican. Il plugin dovrebbe funzionare in questo modo:

  1. leggere delle citazioni da un DB SQLite appena pelican viene inizializzato;
  2. mantenere in memoria tutte le citazioni, mediante una opportuna variabile globale;
  3. inserire una citazione casuale in ogni articolo e pagina del sito Pelican.

Configurare e testare il nuovo plugin nel progetto Pelican

Per prima cosa, ho creato la directory ‘random_quote’ nella cartella plugins del mio sito Pelican; per esempio, ho ottenuto un percorso del tipo:

c:\Siti\ipertesti.com\plugins\random_quote

Il passo successivo, è stato quello di creare i files init.py e random_quote.py nella cartella appena creata.

Il file init.py* serve per creare un package Python e contiene questo codice:

from .random_quote import *

Invece, il file random_quote.py contiene inizialmente questo codice:

from pelican import signals

def test(sender):
    print "%s initialized !!" % sender

def register():
    signals.initialized.connect(test)

A questo punto, ho modificato il file pelicanconf.py, aggiornando le variabili PLUGIN_PATHS e PLUGINS. Per esempio:

PLUGIN_PATHS.append(‘plugins’) PLUGINS.append(‘random_quote’)

Dunque, ho testato il plugin lanciando il comando pelican per rigenerare il sito; non ho riscontrato errori, così Pelican ha stampato, in console, un messaggio come richiesto dalle funzioni test() e register(). Per esempio:

<pelican.Pelican object at 0x01C927F0> initialized !!

Sviluppo di un nuovo Reader Pelican

La versione finale del plugin random_quote.py include questo codice:

#!/usr/bin/env python
# coding=utf-8
'''random_quote plugin insert a random quote on each page of a website

This plugin is designed for Pelican 3.4 and later
'''


import random
import sqlite3
from copy import deepcopy
from pelican import signals
from pelican.contents import Article, Page

# Global vars
_QUOTES = {}


def organize_quotes(list):
    '''populate the global variable _QUOTES with the records from SQLite DB'''
    global _QUOTES
    for quote in list:
        lang = quote[2].lower()
        if lang not in _QUOTES:
            _QUOTES[lang] = []  # append the language label
        _QUOTES[lang].append({
                'author': quote[0],
                'web' : quote[1],
                'source' : quote[3],
                'content' : quote[4]
            }
        )


def read_quotes(settings):
    '''connect to the SQLite DB file and read the records'''
    con = sqlite3.connect(settings['RANDOM_QUOTE']['sqlite_db_file'])
    cur = con.cursor()
    cur.execute('SELECT Author.name, Author.web, Quote.lang, ' \
        + 'Quote.source, Quote.content ' \
        + 'FROM Author LEFT JOIN Quote ON Quote.author=Author.id')
    organize_quotes(deepcopy(cur.fetchall()))
    con.close()


def init_data(pelican):
    '''initialize the settings and load a default record for the index pages'''
    if not ('RANDOM_QUOTE' in pelican.settings):
        pelican.settings.setdefault('RANDOM_QUOTE', {
            'sqlite_db_file': 'db\\quotes.sqlite',
            'default_quote' : {
                'author' : 'Tim Berners-Lee',
                'web' : 'https://en.wikipedia.org/wiki/Tim_Berners-Lee',
                'content' : "Il Web è più un'innovazione sociale che " \
                    "un'innovazione tecnica. L'ho progettato perché avesse una " \
                    "ricaduta sociale, perché aiutasse le persone a collaborare, " \
                    "e non come un giocattolo tecnologico. " \
                    "Il fine ultimo del Web è migliorare la nostra esistenza " \
                    "reticolare nel mondo. Di solito noi ci agglutiniamo in " \
                    "famiglie, associazioni e aziende. Ci fidiamo a " \
                    "distanza e sospettiamo appena voltato l'angolo."},
            }
        )
    read_quotes(pelican.settings)


def insert_random_quote(content):
    '''select a random quote from _QUOTES and insert it in an Article or Page'''
    global _QUOTES
    if isinstance(content, Article) or isinstance(content, Page):
        if not hasattr(content, 'quote'):
            lang = content.settings['DEFAULT_LANG'].lower()
            i = int(random.uniform(0,len(_QUOTES[lang])-1))
            content.quote = _QUOTES[lang][i]
            content._context['quote'] = content.quote


def register():
    '''inizialize the plugin and call it for each content initialized'''
    signals.initialized.connect(init_data)
    signals.content_object_init.connect(insert_random_quote)

Il plugin random_quote si appoggio al database SQLite dove sono momorizzate le citazioni; il db è formato da due semplici tabelle, descritte da queste due istruzioni SQL, che ne permettono la creazione:

CREATE TABLE `Author` (
  `id`  INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
  `name`    TEXT UNIQUE,
  `presentation`    TEXT,
  `web` TEXT UNIQUE,
  `email`   TEXT
);

CREATE TABLE `Quote` (
  `id`  INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
  `lang`    TEXT NOT NULL DEFAULT 'IT',
  `source`  TEXT,
  `content` BLOB NOT NULL UNIQUE,
  `author`  INTEGER NOT NULL,
  FOREIGN KEY(`author`) REFERENCES `Author`(`id`) ON DELETE SET NULL
);

Per inserire le citazioni nelle pagine web, occorre usare il seguente codice HTML nei template Jinja2:

[base.html]
{% block random_quote %}
  <h2>Citazione: <a href="{{ RANDOM_QUOTE['default_quote']['web'] }}">{{ RANDOM_QUOTE['default_quote']['author'] }}</a></h2>
  <p><em>{{ RANDOM_QUOTE['default_quote']['content'] }}</em></p>
{% endblock %}

[article.html]
{% block random_quote %}
  <h2>Citazione: <a href="{{ article.quote['web'] }}">{{ article.quote['author'] }}</a></h2>
  <p><em>{{ article.quote['content'] }}</em></p>
{% endblock %}

[page.html]
{% block random_quote %}
  <h2>Citazione: <a href="{{ page.quote['web'] }}">{{ page.quote['author'] }}</a></h2>
  <p><em>{{ page.quote['content'] }}</em></p>
{% endblock %}

E’ possibile ma non necessario modificare ulteriormente il file pelicanconf.py per specificare i valori di configurazione, se diversi da quelli di default. Per esempio:

I18N_SUBSITES = {
  'en': {
      'RANDOM_QUOTE': {
          'sqlite_db_file': 'db\\quotes.sqlite',
          'default_quote' : {
              'author' : 'Ted Nelson',
              'web' : 'https://en.wikipedia.org/wiki/Ted_Nelson',
              'content' : "The World Wide Web was precisely what we were trying to PREVENT — ever-breaking links, links going outward only, quotes you can't follow to their origins, no version management, no rights management."},
          }
  },
}
RANDOM_QUOTE = {
    'sqlite_db_file': 'db\\quotes.sqlite',
    'default_quote' : {
        'author' : 'Tim Berners-Lee',
        'web' : 'https://en.wikipedia.org/wiki/Tim_Berners-Lee',
        'content' : "Il Web è più un'innovazione sociale che un'innovazione " \
            "tecnica. L'ho progettato perché avesse una ricaduta sociale, " \
            "perché aiutasse le persone a collaborare, e non come un " \
            "giocattolo tecnologico. Il fine ultimo del Web è migliorare la " \
            "nostra esistenza reticolare nel mondo. Di solito noi ci " \
            "agglutiniamo in famiglie, associazioni e aziende. Ci fidiamo a " \
            "distanza e sospettiamo appena voltato l'angolo."
    },
}

Condivido su GitHub il mio plugin

Forse GitHub è la più grande e famosa piattaforma Web per gestire lo sviluppo di programmi in team: GitHub fornisce servizi di hosting, controllo delle revisioni del codice e di coordinamento delle persone che partecipano ad un progetto software.

I termini essenziali di GitHub sono:

  • repository, è lo spazio dedicato ad un progetto per contenere tutti i file che lo riguardano (sorgenti, documenti, immagini, video, foglie elettronici etc…);
  • fork, permette di “iscriversi” al repository gestito da un altro utente GitHub in modo da partecipare al progetto che lo riguarda;
  • branches, costituiscono un sistema per separare le versioni di “file in aggiornamento” da quelle precedenti, che sono mantenute nella branche predefinita del repository, denominata “master”;
  • commits, è il comando che consente di salvare le modifiche effettuate nel file di una branche;
  • pull requests, è il comando che chiede di confermare le modifiche ai file di una branche e sostituire i vecchi file nella branche “master”, con i file aggiornati.

Ho aggiunto il mio plugin “random_quote” al repository getpelican/pelican-plugins.

Per prima cosa, faccio un fork di “pelican-plugins” con il mio account GitHub; poi, vi registro una nuova branche con nome “new plugin: random_quote”. Per finire, nella nuova branch, carico la cartella con i files del mio plugin “random_quote”; faccio il commit per salvare, ed invio una “pull request” per chiedere che il mio plugin entri nel repository ufficiale di Pelican.

Conclusioni

Grazie al sistema dei Plugin, è possibile personalizzare in maniera molto semplice le funzionalità del generatore di siti web statici; l’esempio che ho proposto, rivela l’idea che non si dovrebbe pensare ad un sito statico come ad un sistema chiuso capace di elaborare contenuti da file di testo. In verità, è possibile generare pagine web aggregando informazioni disponibili sui supporti più disparati come, ad esempio, un database SQL.

Ho trovato molto interessante studiare i plugin fatti da altri su GitHub, e mano a mano che le mie conoscenze ed esperienze si arricchivano sempre più, ho comincio a dare il mio contributo, per risolvere bug e realizzare nuove applicazioni.

Spero che dopo aver letto questo articolo, comincerai anche tu a programmare plugin per Pelican in Python. Hai già qualche idea?

Webography

  1. Plugins
  2. Pelican internals
  3. Contributing a plugin
  4. GitHub guides

Libri suggeriti

Condividi questo articolo

Se ti è piaciuto questo articolo e pensi possa essere utile anche ad altri, condividilo con i tuoi amici e conoscenti, facendo click sui pulsanti dei tuoi social network preferiti.

P.S. Grazie!

Commenti

Cosa pensi di questo articolo? Hai dei suggerimenti da darmi o vuoi segnalare la tua esperienza in questa pagina? Registrati con Disqus ed inserisci il tuo commento qui sotto!

Inoltre, se lo desideri puoi anche scrivermi una e-mail.

Presentazione

IpeRteSTi é un blog curato da Roberto Fusi. Raccoglie suggerimenti ed esperienze personali riguardo Internet, Web Development e la Digital Economy.

Seguimi sui social

LinkedInTwitterGooglePlusFeedburner for IpeRteSTi

Iscriviti alla mia newsletter