%% This is file `hadith.sty', %% %% بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ %% Dedicated to the service of Allah ﷻ %% %% Copyright (C) 2025 by Nibras Abo Alzahab %% %% This work may be distributed and/or modified under the %% conditions of the LaTeX Project Public License, either version 1.3c %% of this license or (at your option) any later version. %% The latest version of this license is in %% https://www.latex-project.org/lppl.txt %% and version 1.3c or later is part of all distributions of LaTeX %% version 2005/12/01 or later. %% %% This work has the LPPL maintenance status `maintained'. %% %% Package: hadith %% Version: 1.1.0 %% Date: 2025-01-15 %% Author: Nibras Abo Alzahab %% Email: nibras.abo.alzahab@gmail.com %% License: LPPL 1.3c %% \NeedsTeXFormat{LaTeX2e} \def\hadithdate{2025/01/15} \def\hadithversion{1.1.0} \ProvidesPackage{hadith}[\hadithdate\space v\hadithversion An easy way to typeset Hadith from canonical collections^^J] %% --- Requirements --- \RequirePackage{ifxetex} \RequirePackage{biditools} \RequirePackage{xkeyval} \RequirePackage{xparse} \RequirePackage{xstring} %% --- Package options --- \newif\ifhd@bukhari \newif\ifhd@muslim \newif\ifhd@abudawud \newif\ifhd@tirmidhi \newif\ifhd@nasai \newif\ifhd@ibnmajah \newif\ifhd@malik \newif\ifhd@ahmad \newif\ifhd@darimi \newif\ifhd@all \newif\ifhd@sixbooks \newif\ifhd@nonumber \newif\ifhd@ornbraces \newif\ifhd@compact \newif\ifhd@showgrade \newif\ifhd@showtrans \DeclareOptionX{bukhari}{\hd@bukharitrue} \DeclareOptionX{muslim}{\hd@muslimtrue} \DeclareOptionX{abudawud}{\hd@abudawudtrue} \DeclareOptionX{tirmidhi}{\hd@tirmidhitrue} \DeclareOptionX{nasai}{\hd@nasaitrue} \DeclareOptionX{ibnmajah}{\hd@ibnmajahtrue} \DeclareOptionX{malik}{\hd@maliktrue} \DeclareOptionX{ahmad}{\hd@ahmadtrue} \DeclareOptionX{darimi}{\hd@darimitrue} \DeclareOptionX{all}{\hd@alltrue} \DeclareOptionX{sixbooks}{\hd@sixbookstrue} \DeclareOptionX{nonumber}{\hd@nonumbertrue} \DeclareOptionX{ornbraces}{\hd@ornbracestrue} \DeclareOptionX{compact}{\hd@compacttrue} \DeclareOptionX{showgrade}{\hd@showgradetrue} \DeclareOptionX{showtrans}{\hd@showtranstrue} %% Default: load six books \ExecuteOptionsX{sixbooks,showgrade} \ProcessOptionsX %% --- Resolve all/sixbooks to individual flags --- %% Note: Only 7 of the traditional Nine Books are available in the data source. %% Musnad Ahmad and Sunan al-Darimi are not available in fawazahmed0/hadith-api. \ifhd@all \hd@bukharitrue\hd@muslimtrue\hd@abudawudtrue \hd@tirmidhitrue\hd@nasaitrue\hd@ibnmajahtrue \hd@maliktrue \fi \ifhd@sixbooks \hd@bukharitrue\hd@muslimtrue\hd@abudawudtrue \hd@tirmidhitrue\hd@nasaitrue\hd@ibnmajahtrue \fi %% ============================================================ %% Internal csname separator: use | (not @) to avoid conflicts %% with \makeatletter where @ is a letter and \hd@coll@ would %% be parsed as a single control sequence. %% ============================================================ %% --- Internal command to define hadith text --- %% \hadithdefcmd{collection}{number_key}{text} %% Defines \csname hadithtext|collection|number_key\endcsname %% Public name (no @) so it works in .def files loaded during document body \def\hadithdefcmd#1#2#3{% \@namedef{hadithtext|#1|#2}{#3}% } %% --- Grade and translation markers (public names for .def files) --- \def\hadithgrade#1{\def\hd@currentgrade{#1}} \def\hadithtrans#1{\def\hd@currenttrans{#1}} \def\hd@currentgrade{} \def\hd@currenttrans{} %% --- Hadith number formatting (public name for .def files) --- \ifhd@ornbraces \def\hadithno#1{\,\textarabic{﴿#1﴾}} \else \def\hadithno#1{\,\textarabic{[#1]}} \fi \ifhd@nonumber \def\hadithno#1{} \fi %% --- Collection name storage --- \def\hadithcollectionname#1#2{% \@namedef{hd|name|#1}{#2}% } %% --- Section metadata storage --- \def\hadithsectionname#1#2#3{% \@namedef{hd|section|#1|#2}{#3}% } \def\hadithsectionrange#1#2#3#4{% \@namedef{hd|sectionfirst|#1|#2}{#3}% \@namedef{hd|sectionlast|#1|#2}{#4}% } %% --- Chapter file index --- %% \hadithchapterfile{collection}{book_num}{filename}{first}{last} \def\hadithchapterfile#1#2#3#4#5{% \@namedef{hd|chapfile|#1|#2}{#3}% \@namedef{hd|chapfirst|#1|#2}{#4}% \@namedef{hd|chaplast|#1|#2}{#5}% } %% --- Track which chapter files have been loaded --- \def\hd@markloaded#1#2{% \@namedef{hd|loaded|#1|#2}{1}% } %% --- Sanitize hadith number for \csname (replace . with p) --- \def\hd@sanitize#1{% \StrSubstitute{#1}{.}{p}[\hd@temp]% } %% --- Find and load chapter for a given hadith number --- %% \hd@searchchapters{hadith_num} -> loads the right chapter %% Uses \hd@findcoll and \hd@key set by caller \def\hd@searchchapters#1{% \def\hd@findnum{#1}% \hd@search@try{0}% } \def\hd@search@try#1{% \ifnum#1>200\relax % safety limit, stop \else \@ifundefined{hd|chapfirst|\hd@findcoll|#1}% {% chapter not defined, try next \expandafter\hd@search@try\expandafter{\the\numexpr#1+1\relax}% }% {% \@tempcnta=\@nameuse{hd|chapfirst|\hd@findcoll|#1}\relax \@tempcntb=\@nameuse{hd|chaplast|\hd@findcoll|#1}\relax \ifnum\hd@findnum<\@tempcnta\relax % before this chapter's range, try next \expandafter\hd@search@try\expandafter{\the\numexpr#1+1\relax}% \else \ifnum\hd@findnum>\@tempcntb\relax % after this chapter's range, try next \expandafter\hd@search@try\expandafter{\the\numexpr#1+1\relax}% \else % in range — load chapter and check if hadith is defined \hd@loadchapter{\hd@findcoll}{#1}% \@ifundefined{hadithtext|\hd@findcoll|\hd@key}% {% not in this chapter despite being in range, try next \expandafter\hd@search@try\expandafter{\the\numexpr#1+1\relax}% }% {}% found, stop \fi \fi }% \fi } %% --- On-demand chapter loading --- %% \hd@loadchapter{collection}{book_num} %% Chapter files are in subdirectory: {collection}/hadithtext-{collection}-ch{NN}.def \def\hd@loadchapter#1#2{% \@ifundefined{hd|loaded|#1|#2}% {% \@ifundefined{hd|chapfile|#1|#2}% {\PackageWarning{hadith}{Chapter #2 not found in collection #1}}% {% \edef\hd@chapfilepath{#1/\@nameuse{hd|chapfile|#1|#2}}% \InputIfFileExists{\hd@chapfilepath}{}{% \InputIfFileExists{\@nameuse{hd|chapfile|#1|#2}}{}{% \PackageWarning{hadith}{File \@nameuse{hd|chapfile|#1|#2} not found}}% }% \hd@markloaded{#1}{#2}% }% }% {}% already loaded } %% --- Load metadata (index) files for requested collections --- %% Only loads the index, not the full text \ifhd@bukhari \InputIfFileExists{hadithmeta-bukhari.def}{}{% \PackageWarning{hadith}{hadithmeta-bukhari.def not found.% \MessageBreak Install the hadith package data files.}} \fi \ifhd@muslim \InputIfFileExists{hadithmeta-muslim.def}{}{% \PackageWarning{hadith}{hadithmeta-muslim.def not found.}} \fi \ifhd@abudawud \InputIfFileExists{hadithmeta-abudawud.def}{}{% \PackageWarning{hadith}{hadithmeta-abudawud.def not found.}} \fi \ifhd@tirmidhi \InputIfFileExists{hadithmeta-tirmidhi.def}{}{% \PackageWarning{hadith}{hadithmeta-tirmidhi.def not found.}} \fi \ifhd@nasai \InputIfFileExists{hadithmeta-nasai.def}{}{% \PackageWarning{hadith}{hadithmeta-nasai.def not found.}} \fi \ifhd@ibnmajah \InputIfFileExists{hadithmeta-ibnmajah.def}{}{% \PackageWarning{hadith}{hadithmeta-ibnmajah.def not found.}} \fi \ifhd@malik \InputIfFileExists{hadithmeta-malik.def}{}{% \PackageWarning{hadith}{hadithmeta-malik.def not found.}} \fi \ifhd@ahmad \InputIfFileExists{hadithmeta-ahmad.def}{}{% \PackageWarning{hadith}{hadithmeta-ahmad.def not found.}} \fi \ifhd@darimi \InputIfFileExists{hadithmeta-darimi.def}{}{% \PackageWarning{hadith}{hadithmeta-darimi.def not found.}} \fi %% --- Core: typeset a single hadith --- %% \hadith[collection][number] %% Starred version adds \par after \DeclareDocumentCommand{\hadith}{ s o o }{% \IfNoValueTF{#2}% {\PackageError{hadith}{Collection name required}\MessageBreak Usage: \string\hadith[collection][number]}% {% \IfNoValueTF{#3}% {\PackageError{hadith}{Hadith number required}\MessageBreak Usage: \string\hadith[#2][number]}% {% \hd@typeset{#2}{#3}% \IfBooleanT{#1}{\par}% }% }% } %% --- Internal: lowercase collection name into \hd@coll --- \def\hd@setcoll#1{% \lowercase{\def\hd@coll{#1}}% } %% --- Internal: typeset the hadith text (with on-demand loading) --- \def\hd@typeset#1#2{% % Lowercase the collection name \hd@setcoll{#1}% % Sanitize hadith number \hd@sanitize{#2}% \let\hd@key\hd@temp % Check if hadith is already defined \@ifundefined{hadithtext|\hd@coll|\hd@key}% {% % Not loaded — search chapters and load on demand \let\hd@findcoll\hd@coll \hd@searchchapters{#2}% % Check if found now \@ifundefined{hadithtext|\hd@coll|\hd@key}% {\PackageError{hadith}{Hadith #2 not found in collection #1}% {The collection '#1' may not be loaded, or hadith #2 does not exist.}}% {\hd@render{\hd@coll}{\hd@key}{#2}}% }% {% % Already loaded \hd@render{\hd@coll}{\hd@key}{#2}% }% } %% --- Internal: render the hadith text --- \def\hd@render#1#2#3{% \begingroup \def\hd@currentgrade{}% \def\hd@currenttrans{}% \setRTL \@nameuse{hadithtext|#1|#2}% \endgroup % Show grade if available and option is set \ifhd@showgrade \ifx\hd@currentgrade\empty\else \par\noindent\textit{\small\textenglish{Grade: \hd@currentgrade}}% \fi \fi % Show translation if available and option is set \ifhd@showtrans \ifx\hd@currenttrans\empty\else \par\noindent\textenglish{\hd@currenttrans}% \fi \fi } %% --- Typeset a range of hadiths --- %% \hadithrange[collection][start-end] \DeclareDocumentCommand{\hadithrange}{ o o }{% \IfNoValueTF{#1}% {\PackageError{hadith}{Collection name required}}% {% \IfNoValueTF{#2}% {\PackageError{hadith}{Range required (e.g., 1-10)}}% {\hd@typesetrange{#1}{#2}}% }% } \def\hd@typesetrange#1#2{% \StrBefore{#2}{-}[\hd@start] \StrBehind{#2}{-}[\hd@end] \ifx\hd@start\empty \hd@typeset{#1}{#2}% \else \hd@rangefrom{#1}{\hd@start}{\hd@end}% \fi } \newcounter{hd@count} \def\hd@rangefrom#1#2#3{% \setcounter{hd@count}{#2}% \loop \hd@typeset{#1}{\arabic{hd@count}}\par \stepcounter{hd@count}% \ifnum\value{hd@count}<#3\relax\repeat } %% --- Shortcut commands per collection --- \newcommand{\hadithbukhari}[1]{\hadith[bukhari][#1]} \newcommand{\hadithmuslim}[1]{\hadith[muslim][#1]} \newcommand{\hadithabudawud}[1]{\hadith[abudawud][#1]} \newcommand{\hadithtirmidhi}[1]{\hadith[tirmidhi][#1]} \newcommand{\hadithnasai}[1]{\hadith[nasai][#1]} \newcommand{\hadithibnmajah}[1]{\hadith[ibnmajah][#1]} \newcommand{\hadithmalik}[1]{\hadith[malik][#1]} \newcommand{\hadithahmad}[1]{\hadith[ahmad][#1]} \newcommand{\hadithdarimi}[1]{\hadith[darimi][#1]} %% --- Citation command (inline) --- \DeclareDocumentCommand{\hadithcite}{ o o }{% \IfNoValueTF{#1}% {\textit{[Hadith]}}% {% \hd@setcoll{#1}% \IfNoValueTF{#2}% {\textit{[\@nameuse{hd|name|\hd@coll}]}}% {\textit{[\@nameuse{hd|name|\hd@coll}, \##2]}}% }% } %% --- Get collection name --- \newcommand{\hadithname}[1]{% \hd@setcoll{#1}% \@nameuse{hd|name|\hd@coll}% } %% --- Get section name --- \newcommand{\hadithsection}[2]{% \hd@setcoll{#1}% \@nameuse{hd|section|\hd@coll|#2}% } %% --- End of package --- \endinput