Écrire son propre package

Pourquoi ?

Définir son propre package permet :

Principe

Nous allons créer un fichier « mon_package.sty » (ou tout autre nom qui ne crée de doublon) qui comportera les instructions qui permettront :

Organisation

Listing

%% Informations générales
\NeedsTeXFormat{LaTeX2e}[1999/01/01]
\ProvidesPackage{mon_package}[2003/01/01]

%% Chargement des extensions
\RequirePackage[dvips]{graphicx}

%% Déclaration des options
\DeclareOption{option}{...}
\DeclareOption*{...}
\ProcessOptions

%% Définition des commandes
\newcommand{\blabla}{Super commande}

%% Fin du package
\endinput

Explications

Exemple simple

Supposons qu'il m'arrive d'écrire des documents sans algorithmes et d'autres avec. Lorsque j'en ai besoin je charge les packages algorithm et algorithmic qui permettent de mettre en forme des algorithmes, d'en faire une « liste des algorithmes » et de les rendre flottants (voir la page...). Je peux encore utiliser mon propre package pour gérer ces cas, il suffit d'introduire une option « algo » quand on a besoin des fonctionnalités idoines. Voilà comment :

%% (...) \NeedsTeXFormat et \ProvidesPackage
\newif\if@algo \@algofalse
\DeclareOption{algo}{\@algotrue}
\DeclareOption*{}%% Ne rien faire quand une option est inconnue
\ProcessOptions

\if@algo
  \RequirePackage{algorithm}
  \RequirePackage{algorithmic}
  \RequirePackage{mes-commandes-algo}
  \newcommand{\algorithme}{Blabla}
\fi
%% (...) \newcommand's et \endinput

La commande \newif\ifquelquechose crée trois commandes :

Dans notre exemple on crée une instruction conditionnelle \if@algo qui est équivalente à if false (à cause de \@algofalse). Cependant, si l'option « algo » est passée au package, on exécute \@algotrue qui a pour effet de rendre \if@algo équivalent à if true. Les packages relatifs à l'algo (et les commandes définies dans le bloc) seront chargés si et seulement si l'option « algo » aura été passée dans le fichier .tex :

Remarque : notons que dans les packages « @ » est une lettre (contrairement aux document .tex où « @ » est un symbole) et que donc on peut écrire des noms de commandes avec « @ ». D'où les noms \makeatletter et \makeatother utilisées dans des cas un peu exceptionnels. Dans un fichier .tex, « \if@algo » aurait été interprétée comme la commande \if suivie de « @algo ».

Exemple moins simple, plus réaliste

Listing

\NeedsTeXFormat{LaTeX2e}[1999/01/01]
\ProvidesPackage{mon_package}[2003/01/01]

\newif\if@algo     \@algofalse
\newif\if@listings \@listingsfalse

\DeclareOption{listings}{\@listingstrue}
\DeclareOption{algo}{\@algotrue}
\DeclareOption*{\PassOptionsToPackage{\CurrentOption}{babel}}
\ProcessOptions

\RequirePackage{babel}

\if@algo
  \RequirePackage{algorithm}
  \RequirePackage{algorithmic}
  \RequirePackage{mes-commandes-algo}
\fi

\if@listings
  \RequirePackage{listings}
  \RequirePackage{verbatim}
  \RequirePackage{moreverb}
  \RequirePackage{mes-commandes-listings}
\fi

\endinput

Explications

On déclare comme précédemment deux options « normales » : algo et listings mais quand on est face à une option inconnue on la passe à babel, ainsi :

\documentclass[a4paper]{article}
\usepackage[algo, listings, english, greek, francais]{mon_package}
\begin{document}
 .
 .
 .
\end{document}

sera équivalent à charger les modules d'algorithmiques, les modules de d'écriture de programmes, et babel avec les options english, greek et francais (dans cet ordre). La commande \PassOptionsToPackage{<options>}{<package>} ne charge pas le package <package>, elle rajoute les <options> à la liste d'options qui seront passées au package <package> lors de l'appel \RequirePackage{<package>}.

Remarque : les options déclarées avec \DeclareOption{...}{...} peuvent être passées comme options globales du document comme dans :

\documentclass[a4paper, algo, listings]{article}
\usepackage[english, greek, francais]{mon_package}
\begin{document}
 .
 .
 .
\end{document}

En revanche les langages doivent être passés au package lui-même (notamment babel n'a pas tenté de trouver un langage nommé « a4paper » ce qui aurait abouti à une erreur).

Limitations

D'une part on ne peut pas utiliser ce mécanisme pour plusieurs packages auquel on passerait les options non déclarées avec \DeclareOption{...}{...} et d'autre part on ne peut pas s'abstenir de passer au moins une option de langage au package.

Exemple (très) compliqué

Prérequis

Listing général

\NeedsTeXFormat{LaTeX2e}[1999/01/01]
\ProvidesPackage{mon_package}[2003/01/01]
\RequirePackage{keyval}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Pour gérer les deux types d'options :                                  %
%  1. les options normales (\DeclareOption{...}{...})                    %
%  2. les options type key-val option=value (\define@key{...}{...}{...}) %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\@ifundefined{my@unusedlist}{%
  \let\my@unusedlist\@empty}{}
\@ifundefined{my@extractkey}{%
  \def\my@extractkey#1=#2\my@extractkey{#1}}{}
\newcommand{\ProcessUnusedOptions}[1]{%
  \let\my@tempa\@empty
  \let\my@tempb\@empty
  \@for\CurrentOption:=\my@unusedlist\do{%
    \expandafter\expandafter\expandafter\def
    \expandafter\expandafter\expandafter\my@key
    \expandafter\expandafter\expandafter{%
      \expandafter\my@extractkey\CurrentOption=\my@extractkey}%
    \@ifundefined{KV@#1@\my@key}{%
      \edef\my@tempa{\my@tempa,\CurrentOption,}}{%
      \edef\my@tempb{\my@tempb,\CurrentOption,}}}%
  \@for\CurrentOption:=\my@tempa\do{%
    \ifx\CurrentOption\@empty\else
      \PackageWarning{mypack}{`\CurrentOption' not defined.}\fi}%
  \edef\my@tempb{%
    \noexpand\setkeys{#1}{\my@tempb}}%
  \my@tempb
  \AtEndOfPackage{\let\@unprocessedoptions\relax}}

\DeclareOption*{%
  \expandafter\expandafter\expandafter\def
  \expandafter\expandafter\expandafter\my@unusedlist
  \expandafter\expandafter\expandafter{%
    \expandafter\my@unusedlist\expandafter,%
    \CurrentOption}}
%%%%%%%%%%%%%%%
% Fin du bloc %
%%%%%%%%%%%%%%%

%% Options «normales»
\DeclareOption{foo}{<action foo>}
\DeclareOption{bar}{<action bar>}

%% Options passées sous forme key=val
\define@key{mypack}{<key1>}{<action 1> dépendant de #1}
\define@key{mypack}{<key2>}[<default>]{<action 2> dépendant de #1}

\ProcessOptions
\ProcessUnusedOptions{mypack}

\endinput

Explications

Dans l'exemple suivant

\documentclass[foo]{article}
\usepackage[foo, key1=value1, key2=value2]{mypack}
\begin{document}
 .
 .
 .
\end{document}

l'option foo va agir comme dans les exemples simples, key1=value1 va exécuter le code <action 1> en utilisant value1 en lieu et place de #1. Idem pour key2=value2 sauf que on peut omettre la partie « =value2 » auquel cas <action 2> est exécuté en utilisant <default> à la place de #1. Par exemple si mon_package.sty contient :

%% (...) 
\newcounter{nombre}
\define@key{mypack}{number}[14]{%
  \setcounter{nombre}{#1}}
%% (...) 

on pourra l'appeler ainsi

\documentclass[foo]{article}
\usepackage[number=10]{mypack}
\begin{document}
 .
\thenombre\ affiche 10.
 .
 .
\end{document}

ou encore comme cela

\documentclass[foo]{article}
\usepackage[number]{mypack}
\begin{document}
 .
\thenombre\ affiche 14.
 .
 .
\end{document}

ou bien sûr sans option du tout (auquel cas le code \setcounter ne sera pas exécuté et donc le compteur nombre vaudra zéro).

Exemple d'application

Si mon_package contient :

\newcommand{\pack@languages}{francais}
\define@key{mypack}{language}{%
  \renewcommand{\pack@languages}{#1}}
\RequirePackage[\pack@languages]{babel}

alors on peut l'appeler ainsi

\documentclass[a4paper]{article}
\usepackage[language={english, francais}]{mon_package}
\newcommand{\option}{\texttt}
\begin{document}
 .
\paragraph{Remarque}
Noter les accolades pour que l'option \option{francais} ne soit pas
passée comme une option à part.
 .
 .
\end{document}

ou encore ainsi :

\documentclass[a4paper]{article}
\usepackage{mon_package}
\newcommand{\option}{\texttt}
\begin{document}
 .
Babel chargé avec l'option \option{francais}.
 .
 .
\end{document}

Là encore les options de langages ne pourront pas être spécifiées globalement avec la commande \documentclass. (La raison en est que les options globales qui ne correspondent pas à des options déclarées avec \DeclareOption ne sont pas transmises à \DeclareOption*.)

Utiliser son package

Le package mon_package doit pouvoir être trouvé par LaTeX, pour cela on peut :

Documentation et aide

Pour une documentation concernant l'écriture de classe et de packages : clsguide (à noter également les documentations fntguide, usrguide)  pour une documentation générale sur LaTeX : source2e ou plus soft The not so short introduction to LaTeX (disponible en français !).

Sur internet : CTAN, les newsgroup fr.comp.text.tex et comp.text.tex sur lesquels on peut faire une recherche via Google. Par exemple  comment réaliser le symbole « 1 » avec une double barre pour désigner une fonction indicatrice ? La réponse se trouve sur Google, groupes et taper « group:fr.comp.text.tex 1 barre indicatrice ». C'est une source d'informations inépuisable. Sinon les tuteurs ou forum ens.forum.informatique.tex.

Auteur : Josselin Noirel. Dernière modification le .