summaryrefslogtreecommitdiff
path: root/macros/luatex/latex
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2023-11-11 03:03:26 +0000
committerNorbert Preining <norbert@preining.info>2023-11-11 03:03:26 +0000
commite4ccc7921b6518e670428990524161b3ddbfebd2 (patch)
treecc5e3d265c59f5f5031b2b085a4621d13f0b1af6 /macros/luatex/latex
parentd8b5161942976a522cdb1a7b580fe3886eea6bc6 (diff)
CTAN sync 202311110303
Diffstat (limited to 'macros/luatex/latex')
-rw-r--r--macros/luatex/latex/gitinfo-lua/README.md24
-rw-r--r--macros/luatex/latex/gitinfo-lua/doc/git-scenario.sh61
-rw-r--r--macros/luatex/latex/gitinfo-lua/doc/gitinfo-lua.pdfbin0 -> 116103 bytes
-rw-r--r--macros/luatex/latex/gitinfo-lua/doc/gitinfo-lua.tex344
-rw-r--r--macros/luatex/latex/gitinfo-lua/scripts/gitinfo-lua-cmd.lua186
-rw-r--r--macros/luatex/latex/gitinfo-lua/scripts/gitinfo-lua.lua345
-rw-r--r--macros/luatex/latex/gitinfo-lua/tex/gitinfo-lua.sty94
7 files changed, 1054 insertions, 0 deletions
diff --git a/macros/luatex/latex/gitinfo-lua/README.md b/macros/luatex/latex/gitinfo-lua/README.md
new file mode 100644
index 0000000000..383cf2df33
--- /dev/null
+++ b/macros/luatex/latex/gitinfo-lua/README.md
@@ -0,0 +1,24 @@
+# Git LaTeX
+
+A LaTeX package which provides macros integrated with LuaTeX and the commandline tool `git`.
+
+## Installation
+In order to install this package the right way, one should create the release tarball first with `make package`.
+Afterward be sure to unpack the contents of the release tarball anywhere where TeX will find it, i.e. `~/texmf`.
+
+## Documentation
+The documentation can be built using `make` or manually using `lualatex`:
+```bash
+make build clean
+# Or manually
+cd doc
+lualatex -shell-escape gitinfo-lua.tex
+makeindex -s gind.ist gitinfo-lua.idx
+lualatex -shell-escape gitinfo-lua.tex
+```
+
+Read [gitinfo-lua.pdf](gitinfo-lua.pdf) for more information about this package.
+
+## License
+This project is licensed under the LPPL version 1.3c and maintained by Erik Nijenhuis.
+See [LICENSE.pdf](LICENSE.pdf) for more information.
diff --git a/macros/luatex/latex/gitinfo-lua/doc/git-scenario.sh b/macros/luatex/latex/gitinfo-lua/doc/git-scenario.sh
new file mode 100644
index 0000000000..a3cfd179e9
--- /dev/null
+++ b/macros/luatex/latex/gitinfo-lua/doc/git-scenario.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+set -e
+
+PROJECT_DIR="${1:-../../git-test-project}"
+
+set_author() {
+ git config user.name $1
+ git config user.email $2
+ git config committer.name $1
+ git config committer.email $2
+ git config author.name $1
+ git config author.email $2
+}
+
+alice() {
+ set_author 'Alice' 'alice@example.com'
+}
+bob() {
+ set_author 'Bob' 'bob@example.com'
+}
+charlie() {
+ set_author 'Charlie' 'charlie@example.com'
+}
+
+if [[ -d "${PROJECT_DIR}" ]]; then
+ rm -rf "${PROJECT_DIR}"
+fi
+
+mkdir "${PROJECT_DIR}"
+cd "${PROJECT_DIR}"
+
+git init
+
+alice
+
+echo "# My project" > README.md
+git add README.md
+git commit -m "Add readme" --date="2017-08-04 10:32"
+
+bob
+
+echo "
+Another project by Alice and Bob." >> README.md
+git add README.md
+git commit -m "Add intro (README.md)" --date="2017-08-05 06:12"
+
+alice
+
+GIT_COMMITTER_DATE="2017-08-05 07:11" git tag 0.0.1
+
+bob
+
+curl https://raw.githubusercontent.com/github/gitignore/main/TeX.gitignore > .gitignore
+git add .gitignore
+git commit -m "Add gitignore" --date="2017-08-06 12:03"
+
+charlie
+
+export GIT_COMMITTER_DATE="2017-08-06 08:41"
+git tag -a 0.1.0 -m "Version 0.1.0"
diff --git a/macros/luatex/latex/gitinfo-lua/doc/gitinfo-lua.pdf b/macros/luatex/latex/gitinfo-lua/doc/gitinfo-lua.pdf
new file mode 100644
index 0000000000..ad1db4e0b1
--- /dev/null
+++ b/macros/luatex/latex/gitinfo-lua/doc/gitinfo-lua.pdf
Binary files differ
diff --git a/macros/luatex/latex/gitinfo-lua/doc/gitinfo-lua.tex b/macros/luatex/latex/gitinfo-lua/doc/gitinfo-lua.tex
new file mode 100644
index 0000000000..1587f72d80
--- /dev/null
+++ b/macros/luatex/latex/gitinfo-lua/doc/gitinfo-lua.tex
@@ -0,0 +1,344 @@
+%% gitinfo-lua.tex
+%% Copyright 2023 E. Nijenhuis
+%
+% 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
+% http://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’.
+%
+% The Current Maintainer of this work is E. Nijenhuis.
+%
+% This work consists of the files gitinfo-lua.sty gitinfo-lua.pdf
+% gitinfo-cmd.lua and gitinfo-lua.lua
+\documentclass{ltxdoc}
+\usepackage[english]{babel}
+\usepackage[titlepage]{gitinfo-lua}
+
+\usepackage{listings}
+
+\usepackage{calc}
+\usepackage{multicol}
+\usepackage{tabularx}
+\usepackage{xcolor}
+\usepackage{textcomp}
+
+\usepackage[orig,english]{isodate}
+
+\def\projecturl{https://github.com/Xerdi/gitinfo-lua}
+\title{\textbf{Gitinfo Lua} package\thanks{This document corresponds to package \texttt{gitinfo-lua} version \gitversion{} written on \gitdate{}.}}
+
+\def\cmd{\lstinline[language={[LaTeX]TeX},keywordsprefix={\\}]}
+
+\usepackage{hyperref}
+\usepackage[nonewpage]{imakeidx}
+
+\CodelineIndex
+\EnableCrossrefs
+
+\makeindex[columns=1]
+
+\newlength\xample
+\newlength\xamplesep
+\setlength\xample{6cm-5pt}
+\setlength\xamplesep{5pt}
+
+\begin{document}
+ \RecordChanges
+
+ \maketitle
+
+ \begin{abstract}
+ This project aims to display git project information in PDF documents.
+ It's mostly written in Lua for executing the \texttt{git} commands, therefore making this package only applicable for \texttt{lualatex} with \texttt{shell escape} enabled.
+ If \texttt{lualatex} isn't working for you, you could try \href{https://ctan.org/pkg/gitinfo2}{gitinfo2} instead.
+ For \LaTeX{} it provides a set of standard macros for displaying basic information or setting the project directory, and a set of advanced macros for formatting commits and tags.
+ \end{abstract}
+
+ \begin{multicols}{2}
+ \tableofcontents
+% \lstlistoflistings
+ \printindex
+ \end{multicols}
+ \clearpage
+
+
+ \section{Usage}
+ For the package to work one should work, and only work, with Lua\TeX{}.
+ Another prerequisite is that there is an available git repository either in the working directory, or somewhere else on your machine (see section~\ref{sub:tex-basic}).
+
+ \subsection{Git}
+ For this package to work at a minimum, there has to be an initialized Git repository, and preferably, at least with one commit.
+ For example, the following minimal example should do the trick already:
+ \begin{lstlisting}[language=bash,frame=single,caption={Minimal Git setup},morekeywords={mkdir,git}]
+mkdir my_project
+cd my_project
+echo "# My Project" > README.md
+git init && git commit -am "Init"
+ \end{lstlisting}
+
+ Then in order for the changelog to work, the project needs to contain either `lightweight-' or `annotated' tags.
+ The main difference is that a lightweight tag takes no extra options, for example: \texttt{git tag 0.1}.
+ See listing~\ref{lst:scenario} for more examples on authoring and versioning with \texttt{git}.
+
+ \subsection{Lua\LaTeX{}}
+
+ For generating the document with \LaTeX{} one must make use of \texttt{lualatex}.
+ For example, when having the main file `\texttt{main.tex}':
+ \begin{lstlisting}[language=bash,frame=single,caption={Generating the document with \LaTeX{}},morekeywords={lualatex,latexmk}]
+# Generate once
+lualatex -shell-escape main
+# Generate and keep watching with LaTeXMK
+latexmk -pvc -lualatex -shell-escape main
+ \end{lstlisting}
+ Note that in both cases option \texttt{-shell-escape} is required.
+ This is required for issuing \texttt{git} via the commandline.
+
+
+ \section{LaTeX Interface}
+
+ \subsection{Package Options}
+
+ \cmd{\usepackage}%
+ \oarg{opts...}%
+ \texttt{\textbraceleft gitinfo-lua\textbraceright}\index[pkgopts]{gitinfo-lua(.sty)}
+ This package provides some options for default formatting purposes.
+ The author sorting is one of them.
+ If the options contain \meta{contrib} the authors will be sorted based on their contributions, otherwise the authors will be sorted alphabetically, which is the default option \meta{alpha}.
+ Another option is the \meta{titlepage} option, which sets the \cmd{\author} and \cmd{\date} macros accordingly.
+ By default, it sets the local git author, equivalent to option \meta{author}.
+ Pass option \meta{authors} to set all git authors of the project instead.
+
+ \subsection{Basic macros}\label{sub:tex-basic}
+ By default the main file's directory is used as git project directory.
+ This directory can be manipulated with \DescribeMacro{\gitdirectory}\cmd{\gitdirectory}\marg{path}.
+ This is only tested with relative paths on Linux.
+ \DescribeMacro{\gitunsetdirectory} To undo this operation and switch back to the main file's directory use \cmd{\gitunsetdirectory}.\\
+
+ \noindent
+ \DescribeMacro{\gitversion} The current version can be display by using \cmd{\gitversion} and is equivalent to \texttt{git describe --tags --always}, working for both lightweight and annotated tags.
+ For this project \cmd{\gitversion} results in \gitversion.
+ When the version is dirty it will be post fixed with \texttt{-<commit count>-<short ref>}.
+ For example, when this paragraph was written, the version was displaying 0.0.1-14-gcc2bc30.\\
+
+ The \DescribeMacro{\gitdate}\cmd{\gitdate} macro gets the most recent date from the git log.
+ Meaning, the last `short date' variant is picked from the last commit.
+ This short date is formatted ISO based and is already suitable for use in packages like \texttt{isodate} for more advanced date formatting.\\
+
+
+ The author's name and email can be accessed using \cmd{\gitauthor} and \cmd{\gitemail}\DescribeMacro{\gitauthor}\DescribeMacro{\gitemail}.
+ These values are based on \texttt{git config user.name} and \texttt{git config user.email}.
+
+ \subsection{Multiple Authors}
+ When projects having multiple authors this package can help with the \DescribeMacro{\dogitauthors}\break\cmd{\dogitauthors}\oarg{conj} and \DescribeMacro{\forgitauthors}\cmd{\forgitauthors}\oarg{conj}\marg{csname} macro.
+ Where \cmd{\dogitauthors} executes a default formatting implementation of \break\cmd{\git@format@author} and \cmd{\forgitauthors} executes the given \meta{csname} for every author available.
+ The optional \meta{conj} conjunction makes it possible to even integrate it further.
+ For example, when setting the authors in pdfx, the conjunction would be \texttt{[\textbackslash\textbackslash sep\textasciitilde]}, so that the authors are properly separated in the document properties\footnote{See package documentation of \texttt{pdfx}: \url{https://ctan.org/pkg/pdfx}}.
+
+ \gitdirectory{../../git-test-project}%
+ \setlength\xample{4.6cm-5pt}%
+ \setlength\xamplesep{5pt}%
+ \noindent%
+ \begin{minipage}[t]{\linewidth-\xample-\xamplesep}%
+ \begin{lstlisting}[language={[LaTeX]TeX},numbers=left,caption={Formatting authors},captionpos=t,morekeywords={myauthorformat,href,forgitauthors,dogitauthors}]
+\newcommand{\myauthorformat}[2]{#1 \href{mailto:#2}{#2}}
+\forgitauthors[\\]{myauthorformat}
+% Or using standard format
+\dogitauthors[\\]
+ \end{lstlisting}
+ \end{minipage}\hfill%
+ \begin{minipage}[t]{\xample}
+ \noindent
+ \begin{center}
+ Results in
+ \end{center}
+
+ \noindent
+ \setlength{\fboxsep}{5pt}%
+ \fbox{\parbox{\linewidth-2\fboxsep}{\dogitauthors[\\\\]}}
+ \end{minipage}\\
+
+ This example is generated with the history of the \texttt{git-test-project} (see section~\ref{sec:project}) and is alphabetically sorted with package option \texttt{alpha}.
+ \gitunsetdirectory
+
+ \subsection{Commits}
+ For this section the git project of this document is used due to the fact that there are references to revisions.
+ The test project's revisions change for every user, since they get recreated every time \texttt{test-scenario.sh} is executed (see section~\ref{sec:project}).\\
+
+ \noindent
+ \DescribeMacro{\gitcommit}\oarg{format}\marg{csname}\marg{revision}\\
+ For displaying commit data \cmd{\gitcommit} can be used.
+ The optional \texttt{format} takes variables separated by a comma.
+ The default \texttt{format} is \texttt{h,an,ae,as,s,b}.
+ The \texttt{csname} is a user defined command accepting every variable as argument.
+ \setlength\xample{3.5cm}%
+ \setlength\xamplesep{0pt}%
+ \noindent%
+ \begin{minipage}[t]{\linewidth-\xample-\xamplesep}
+ \begin{lstlisting}[language={[LaTeX]TeX},numbers=left,caption={Formatting a commit},captionpos=t,morekeywords={formatcommit,printdate,gitcommit}]
+\newcommand{\formatcommit}[3]{#1, by #2 on \printdate{#3}}
+
+\gitcommit[s,an,as]{formatcommit}{75dc036}
+ \end{lstlisting}
+ \end{minipage}\hfill%
+ \begin{minipage}[t]{\xample}
+ \noindent
+ \begin{center}
+ Results in
+ \end{center}
+
+ \noindent
+ \setlength{\fboxsep}{5pt}%
+ \newcommand\formatcommit[3]{#1, by #2 on \printdate{#3}}%
+ \fbox{\parbox{\linewidth-2\fboxsep}{\gitcommit[s,an,as]{formatcommit}{75dc036}\footnotemark}}
+ \end{minipage}\\
+ \footnotetext{\cs{printdate} from \texttt{isodate}: \url{https://www.ctan.org/pkg/isodate}}
+
+ Consult \texttt{man git-log} for possible format variables and omit the \% for every variable.\\
+
+ \noindent
+ \DescribeMacro{\forgitcommit}\oarg{format}\marg{csname}\marg{rev\_spec}\\
+ For displaying multiple commits the~\cmd{\forgitcommit} is used, which has the same arguments as \cmd{\gitcommit}, but only this time the \texttt{csname} is executed for every commit.
+ The last argument \texttt{rev\_spec} this time, however, can have no argument or a sequence.
+
+ \noindent\setlength\xample{4.5cm}\setlength\xamplesep{0pt}
+ \begin{minipage}[t]{\linewidth-\xample-\xamplesep}
+ \noindent
+ \begin{lstlisting}[language={[LaTeX]TeX},numbers=left,captionpos=t,caption={Formatting commits},morekeywords={formatcommits,forgitcommit}]
+\newcommand{\formatcommits}[2]{\item #1\\\quad —#2}
+
+\begin{itemize}
+ \forgitcommit[s,an]{formatcommits}{75dc036...e51c481}
+\end{itemize}
+ \end{lstlisting}
+ \end{minipage}\hfill%
+ \begin{minipage}[t]{\xample}
+ \noindent
+ \begin{center}
+ Results in
+ \end{center}
+
+ \noindent
+ \newcommand\formatcommits[2]{\item #1\\\quad —#2}%
+ \setlength{\fboxsep}{0pt}%
+ \fbox{\parbox{\linewidth}{%
+ \begin{itemize}
+ \forgitcommit[s,an]
+ {formatcommits}
+ {75dc036...e51c481}
+ \end{itemize}
+ }}
+ \end{minipage}\\
+ \gitdirectory{../../git-test-project}
+
+ \clearpage
+ \subsection{Tags}
+ In this section the \texttt{git-test-project} is used.
+
+ The tags are mostly useful for generating changelogs.
+ For formatting tags, there's a \DescribeMacro{\forgittag}\cmd{\forgittag}\oarg{format}\marg{csname}.
+ Again, like \cmd{\forgitcommit} it takes a format, however, this time more complex, since the formatting options differ between \texttt{git log} and \texttt{git for-each-ref}.
+ For more info regarding these formatting options consult the man page of \texttt{git-for-each-ref}.
+
+ \noindent\setlength\xample{3.5cm}\setlength\xamplesep{0pt}
+ \begin{minipage}[t]{\linewidth-\xample-\xamplesep}
+ \noindent
+ \begin{lstlisting}[language={[LaTeX]TeX},numbers=left,captionpos=t,caption={Formatting tags},morekeywords={formattags,forgittag}]
+\newcommand{\formattags}[2]{\item Version #1\\type: #2}
+
+\begin{itemize}
+ \forgittag[refname:short,objecttype]{formattags}
+\end{itemize}
+ \end{lstlisting}
+ \end{minipage}\hfill%
+ \begin{minipage}[t]{\xample}
+ \noindent
+ \begin{center}
+ Results in
+ \end{center}
+
+ \noindent
+ \newcommand\formattags[2]{\item Version #1\\type: #2}%
+ \setlength{\fboxsep}{0pt}%
+ \fbox{\parbox{\linewidth}{%
+ \begin{itemize}
+ \forgittag[refname:short,objecttype]
+ {formattags}
+ \end{itemize}
+ }}
+ \end{minipage}\\
+ This example shows that the versions used are mixed.
+ This is, of course, a horrible way to manage a project's version, though, we'll continue on with this hard objective.
+ For example, if we wish to display the author of the lightweight and annotated tag, we can do so by specifying a format using the if-then-else feature of the format specification.
+ The format would be: \texttt{(taggername)(taggername)(authorname)}.
+ Here the \texttt{taggername} will show up, or if not present, the \texttt{authorname} will be shown instead.
+
+ The default format specification is like the \cmd{\forgitcommit} format, but then again, some bit more complex:\\
+ \texttt{refname:short,(taggername)(taggername,taggeremail,taggerdate:short)\\(authorname,authoremail,authordate:short),subject,body}.
+ This is a robust example of getting all information, being it a lightweight- or annotated tag.\\
+
+ For displaying commits in between tags, there's a \DescribeMacro{\forgittagseq}\cmd{\forgittagseq}\marg{csname}.
+ The \meta{csname} takes exactly three arguments, namely, the \meta{current}, \meta{next tag} and \meta{rev spec}.
+ The last iteration gives an empty value for \meta{next tag} and the \meta{rev spec} is identical to \meta{current}.
+
+ Afterward tag info get be fetched using the \DescribeMacro{\gittag} \cmd{\gittag}\oarg{format}\marg{csname}\marg{tag}.
+ This macro takes the same formatting specification as \cmd{\fotgittag}.
+ Beware of using \cmd{\gittag} for the \meta{next tag} parameter in \cmd{\forgittagseq}.
+
+ All these macros put together are demonstrated in listing~\ref{lst:changelog} (see next page).
+ \clearpage
+ \subsection{Changelog}
+ This example demonstrates the generation of a changelog.
+ For simplicity’s sake, every tag is displayed in a \texttt{description} environment's item and within an \texttt{enumerate} environment displaying commits in between.
+ \begin{lstlisting}[language={[LaTeX]TeX},numbers=left,captionpos=t,caption={Formatting a changelog},label={lst:changelog},morekeywords={commitline,formatversion,gittag,forgitcommit,forgittagseq,printdate}]
+\section*{Changelog}
+\newcommand{\commitline}[1]{\item #1}
+\newcommand{\formatversion}[3]{%
+ \item[#1]
+ \gittag[(taggerdate)(taggerdate:short)(authordate:short)]{printdate}{#1}
+ \begin{itemize}
+ \forgitcommit[s]{commitline}{#3}
+ \end{itemize}
+}%
+\begin{description}
+ \forgittagseq{formatversion}
+\end{description}
+ \end{lstlisting}
+ \noindent
+ \newcommand\commitline[1]{\item #1}
+ \newcommand\formatversion[3]{%
+ \item[#1] \gittag[(taggerdate)(taggerdate:short)(authordate:short)]{printdate}{#1}
+ \begin{itemize}
+ \forgitcommit[s]{commitline}{#3}
+ \end{itemize}%
+ }%
+ \setlength{\fboxsep}{16pt}%
+ \begin{center}Results in\end{center}
+
+ \noindent
+ \fbox{
+ \parbox{\linewidth-8pt-2\fboxsep}{
+ {\bfseries\Large Changelog}
+ \begin{description}
+ \forgittagseq{formatversion}%
+ \end{description}
+ }
+ }\\
+
+ For displaying the tagline (see line 5) we use the existing \cmd{\printdate} macro of package \texttt{isodate}, which also takes exactly one argument
+ For every version sequence the commits in between are displayed (see line 7), where the last sequence having the initial commit as second argument plays well with the \cmd{\forgitcommit} macro and makes it possible to show the whole sequence of history.
+
+ \clearpage
+ \section{Project Example}\label{sec:project}
+ This documentation uses an example \texttt{project} which gets created by the \texttt{git-scenario.sh} script (see listing~\ref{lst:scenario}).
+ It creates some commits having dates in the past and different authors set.
+ Lastly it creates a `lightweight-' and `annotated' tag.
+
+ To set up this scenario either do \texttt{make scenario} or \texttt{bash scenario.sh}.
+
+ \lstinputlisting[language=bash,numbers=left,frame=single,label={lst:scenario},caption={git-scenario.sh},captionpos=t,morekeywords={git,alice,bob,charlie,mkdir,rm,curl,set\_author}]{git-scenario.sh}
+\end{document}
diff --git a/macros/luatex/latex/gitinfo-lua/scripts/gitinfo-lua-cmd.lua b/macros/luatex/latex/gitinfo-lua/scripts/gitinfo-lua-cmd.lua
new file mode 100644
index 0000000000..7e27bc05ba
--- /dev/null
+++ b/macros/luatex/latex/gitinfo-lua/scripts/gitinfo-lua-cmd.lua
@@ -0,0 +1,186 @@
+-- gitinfo-lua-cmd.lua
+-- Copyright 2023 E. Nijenhuis
+--
+-- 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
+-- http://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’.
+--
+-- The Current Maintainer of this work is E. Nijenhuis.
+--
+-- This work consists of the files gitinfo-lua.sty gitinfo-lua.pdf
+-- gitinfo-cmd.lua and gitinfo-lua.lua
+
+local api = {
+ cwd = nil,
+ executable = 'git',
+ default_sort = '',
+ attribute_separator = '\\pop',
+ record_separator = '\\end'
+}
+local cache = {}
+function cache:seek(_key)
+ for key, value in pairs(self) do
+ if key == _key then
+ return true, value
+ end
+ end
+ return false, nil
+end
+
+function api.trim(s)
+ return (s:gsub("^%s*(.-)%s*$", "%1"))
+end
+
+function api:exec(command, do_caching, target_dir)
+ local cmd = self.executable .. ' ' .. command
+ local cwd = target_dir or self.cwd
+ if cwd then
+ cmd = 'cd ' .. cwd .. ';' .. cmd
+ end
+ if do_caching then
+ local found, result = cache:seek(cmd)
+ if found then
+ return result
+ end
+ end
+ local f = io.popen(cmd)
+ if f == nil then
+ return nil, "Couldn't execute git command.\n\tIs option '-shell-escape' turned on?"
+ end
+ local s = f:read('*a')
+ if f:close() then
+ if do_caching then
+ cache[cmd] = s
+ end
+ return s
+ else
+ return nil, 'Error executing git command'
+ end
+end
+
+function api:shortlog(sort_by_contrib, include_email, target_dir)
+ local cmd = 'shortlog -s'
+ if sort_by_contrib then
+ cmd = cmd .. 'n'
+ end
+ if include_email then
+ cmd = cmd .. 'e'
+ end
+ cmd = cmd .. ' HEAD'
+ return self:exec(cmd, true, target_dir)
+end
+
+function api:parse_opts(options)
+ if options then
+ for idx, opt in ipairs(options) do
+ options[idx] = '--' .. opt
+ end
+ return table.concat(options, ' ')
+ end
+end
+
+function api:format_attribute(attribute, no_separator, with_parenthesis)
+ if with_parenthesis then
+ attribute = '%(' .. attribute .. ')'
+ else
+ attribute = '%' .. attribute
+ end
+ if not no_separator then
+ attribute = attribute .. self.attribute_separator
+ end
+ return attribute
+end
+
+function api:_parse_format_spec(spec, idx, with_parenthesis)
+ local format = ''
+ local above_limit = #spec + 1
+ while idx and idx <= #spec do
+ local attr_idx, attr_size, attr = string.find(spec, '([a-z:]+)', idx)
+ local if_idx, if_size, if_block, if_then, if_else = string.find(spec, '%((.-)%)%((.-)%)%((.-)%)', idx)
+ if if_idx or attr_idx then
+ if (if_idx or above_limit) > (attr_idx or above_limit) then
+ format = format .. self:format_attribute(attr, false, with_parenthesis)
+ idx = attr_size and (attr_size + 1)
+ else
+ local if_token = self:format_attribute(if_block, true, with_parenthesis)
+ local then_result = self:_parse_format_spec(if_then, idx, with_parenthesis)
+ local else_result = self:_parse_format_spec(if_else, idx, with_parenthesis)
+ format = format .. '%(if)' .. if_token .. '%(then)' .. then_result
+ format = format .. '%(else)' .. else_result .. '%(end)'
+ idx = if_size and (if_size + 1)
+ end
+ end
+ end
+ return format
+end
+
+function api:parse_format_spec(spec, with_parenthesis)
+ if type(spec) ~= 'string' then
+ return nil, 'Pass the attribute format spec separated by "," in a string en enclosed in three parentheses for if statements'
+ end
+ local format = self:_parse_format_spec(spec, 1, with_parenthesis)
+ return format .. self.record_separator
+end
+
+function api:parse_response(buffer)
+ local results = {}
+ for record_buffer in string.gmatch(buffer, '(.-)' .. self.record_separator) do
+ local record = {}
+ for attr in string.gmatch(record_buffer, '(.-)' .. self.attribute_separator) do
+ table.insert(record, self.trim(attr))
+ end
+ table.insert(results, record)
+ end
+ return results
+end
+
+function api:log(format_spec, revision, options, target_dir)
+ local format, err = self:parse_format_spec(format_spec)
+ if err then
+ return nil, err
+ end
+ local cmd = 'log --pretty=format:"' .. format .. '"'
+ local opts = self:parse_opts(options)
+ if opts then
+ cmd = cmd .. ' ' .. opts
+ end
+ if revision and revision ~= '' then
+ cmd = cmd .. ' ' .. revision
+ end
+ local response, err = self:exec(cmd, true, target_dir)
+ if not response then
+ return nil, err
+ end
+ return self:parse_response(response)
+end
+
+function api:for_each_ref(format_spec, revision_type, options, target_dir)
+ local err, format, response
+ format, err = self:parse_format_spec(format_spec, true)
+ if err then return nil, err end
+ local cmd = 'for-each-ref --format="' .. format .. '"'
+ local opts = self:parse_opts(options)
+ if opts then
+ cmd = cmd .. ' ' .. opts
+ end
+ cmd = cmd .. ' ' .. revision_type
+ response, err = self:exec(cmd, true, target_dir)
+ if err then return nil, err end
+ return self:parse_response(response)
+end
+
+local gitinfo_cmd = {}
+local gitinfo_cmd_mt = {
+ __index = api,
+ __newindex = nil
+}
+
+setmetatable(gitinfo_cmd, gitinfo_cmd_mt)
+
+return gitinfo_cmd
diff --git a/macros/luatex/latex/gitinfo-lua/scripts/gitinfo-lua.lua b/macros/luatex/latex/gitinfo-lua/scripts/gitinfo-lua.lua
new file mode 100644
index 0000000000..4cb0f0f467
--- /dev/null
+++ b/macros/luatex/latex/gitinfo-lua/scripts/gitinfo-lua.lua
@@ -0,0 +1,345 @@
+-- gitinfo-lua.lua
+-- Copyright 2023 E. Nijenhuis
+--
+-- 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
+-- http://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’.
+--
+-- The Current Maintainer of this work is E. Nijenhuis.
+--
+-- This work consists of the files gitinfo-lua.sty gitinfo-lua.pdf
+-- gitinfo-cmd.lua and gitinfo-lua.lua
+
+if not modules then
+ modules = {}
+end
+
+local module = {
+ name = 'gitinfo-lua',
+ info = {
+ version = 1.0,
+ comment = "Git info Lua — Git integration with LaTeX",
+ author = "Erik Nijenhuis",
+ license = "free"
+ }
+}
+
+modules[module.name] = module.info
+
+local api = {
+ cur_tok = nil,
+ cmd = require('gitinfo-lua-cmd'),
+ escape_chars = {
+ ['&'] = '\\&',
+ ['%%'] = '\\%%',
+ ['%$'] = '\\$',
+ ['#'] = '\\#',
+ ['_'] = '\\_',
+ ['{'] = '\\{',
+ ['}'] = '\\}',
+ ['~'] = '\\textasciitilde',
+ ['%^'] = '\\textasciicircum'
+ }
+}
+local mt = {
+ __index = api,
+ __newindex = nil
+}
+local gitinfo = {}
+setmetatable(gitinfo, mt)
+
+function api.trim(s)
+ return (s:gsub("^%s*(.-)%s*$", "%1"))
+end
+
+function api:set_date()
+ local date, err = self.cmd:log('cs', '-1', {'max-count=1'})
+ if date and #date == 1 then
+ local _, _, year, month, day = date[1][1]:find('(%d+)[-/](%d+)[-/](%d+)')
+ tex.year = tonumber(year)
+ tex.month = tonumber(month)
+ tex.day = tonumber(day)
+ else
+ return nil, (err or 'Length of output doesn\'t match one (attempt to set git date)')
+ end
+end
+
+function api:escape_str(value)
+ local buf = string.gsub(value, '\\', '\\textbackslash')
+ for search, replace in pairs(self.escape_chars) do
+ buf = string.gsub(buf, search, replace)
+ end
+ return buf
+end
+
+-- experimental
+function api:get_tok()
+ if self.cur_tok == nil then
+ self.cur_tok = token.get_next()
+ end
+ return self.cur_tok
+end
+
+-- experimental
+function api:parse_opts()
+ local tok = self:get_tok()
+ if tok.cmdname == 'other_char' then
+ --token.put_next(tok)
+ local opts = token.scan_word()
+ self.cur_tok = nil
+ -- todo: parse []
+ return opts
+ end
+end
+
+-- experimental
+function api:parse_arguments(argc)
+ local result_list = {}
+ for _ = 1, argc do
+ local tok = self:get_tok()
+ if tok.cmdname == 'left_brace' then
+ token.put_next(tok)
+ table.insert(result_list, token.scan_argument())
+ self.cur_tok = nil
+ else
+ tex.error("Expected left brace")
+ return
+ end
+ end
+ return table.unpack(result_list)
+end
+
+-- experimental
+function api:parse_macro()
+ --tex.print('\\noexpand')
+ local tok = self:get_tok()
+ if (tok.cmdname == 'call') or tok.cmdname == 'long_call' then
+ self.cur_tok = nil
+ return tok
+ else
+ tex.error("Expected Macro")
+ for i = 1, 5 do
+ local _tok = token.get_next()
+ end
+ end
+end
+
+function api:dir(path)
+ self.cmd.cwd = path
+end
+
+function api:version()
+ return self.trim(self.cmd:exec('describe --tags --always', true))
+end
+
+function api:write_version()
+ local version, err = self:version()
+ if version then
+ tex.write(version)
+ else
+ tex.error(err)
+ end
+end
+
+-- todo: prevent output to stderr
+-- todo: add write variant
+-- experimental
+function api:is_dirty()
+ local ok, _ = self.cmd:exec('describe --tags --exact-match')
+ return ok == nil
+end
+
+function api:local_author()
+ return self.trim(self.cmd:exec('config user.name', true))
+end
+
+function api:write_local_author()
+ local name, err = self:local_author()
+ if name then
+ tex.write(name)
+ else
+ tex.error(err)
+ end
+end
+
+function api:local_email()
+ return self.trim(self.cmd:exec('config user.email', true))
+end
+
+function api:write_local_email()
+ local name, err = self:local_email()
+ if name then
+ tex.write(name)
+ else
+ tex.error(err)
+ end
+end
+
+function api:authors(sort_by_contrib)
+ local authors, err = self.cmd:shortlog(sort_by_contrib, true)
+ if authors then
+ local author_list = {}
+ for line in authors:gmatch('(.-)\n') do
+ local contributions, name, email = line:match("^%s-(%d+)%s-(.-)%s-<(.-)>%s-$")
+ table.insert(author_list, {
+ contributions = contributions,
+ name = name,
+ email = email
+ })
+ end
+ return author_list
+ else
+ return nil, err
+ end
+end
+
+function api:cs_for_authors(csname, conjunction, sort_by_contrib)
+ if token.is_defined(csname) then
+ local tok = token.create(csname)
+ local authors, err = self:authors(sort_by_contrib)
+ if authors then
+ local next_conj
+ for _, author in ipairs(authors) do
+ if next_conj then
+ tex.print(next_conj)
+ end
+ next_conj = conjunction
+ tex.print(tok, '{' .. self:escape_str(self.trim(author.name)) .. '}', '{' .. self:escape_str(self.trim(author.email)) .. '}')
+ end
+ else
+ tex.error(err)
+ end
+ else
+ tex.error('ERROR: \\' .. csname .. ' not defined')
+ end
+end
+
+function api:cs_commit(csname, rev, format)
+ if token.is_defined(csname) then
+ local tok = token.create(csname)
+ local log, err = self.cmd:log(format, rev, {'max-count=1'})
+ if log then
+ if #log == 1 then
+ tex.print(tok)
+ for _, value in ipairs(log[1]) do
+ tex.print('{' .. self:escape_str(value) .. '}')
+ end
+ else
+ texio.write_nl('Warning: commit returned none')
+ end
+ else
+ tex.error('ERROR: ' .. (err or 'nil'))
+ end
+ else
+ tex.error('ERROR: \\' .. csname .. ' not defined')
+ end
+end
+
+function api:cs_last_commit(csname, format)
+ return self:cs_commit(csname, '-1', format)
+end
+
+function api:cs_for_commit(csname, rev_spec, format)
+ if token.is_defined(csname) then
+ local tok = token.create(csname)
+ local log, err = self.cmd:log(format, rev_spec)
+ if log then
+ for _, commit in ipairs(log) do
+ tex.print(tok)
+ for _, value in ipairs(commit) do
+ tex.print('{' .. self:escape_str(value) .. '}')
+ end
+ end
+ else
+ tex.error('ERROR:\\' .. err)
+ end
+ else
+ tex.error('ERROR: \\' .. csname .. ' not defined')
+ end
+end
+
+function api:tag_info(format_spec, tag, target_dir)
+ local err, info
+ info, err = self.cmd:for_each_ref(format_spec, 'refs/tags', {'count=1', 'contains=' .. tag}, target_dir)
+ if info and #info == 1 then
+ return info[1]
+ else
+ tex.error(err or 'Result count didn\'t match. (in tag_info)')
+ end
+end
+
+function api:tags(target_dir)
+ local tag_list = {}
+ local tags, err = self.cmd:exec('tag -l --sort=-v:refname', true, target_dir)
+ if tags then
+ for tag in tags:gmatch('(.-)\n') do
+ table.insert(tag_list, self.trim(tag))
+ end
+ else
+ return nil, err
+ end
+ return tag_list
+end
+
+function api:cs_tag(csname, format_spec, tag, target_dir)
+ if token.is_defined(csname) then
+ local tok = token.create(csname)
+ local info = self:tag_info(format_spec, tag, target_dir)
+ if info then
+ tex.print(tok)
+ for _, value in ipairs(info) do
+ tex.print('{' .. self:escape_str(value) .. '}')
+ end
+ end
+ else
+ tex.error('ERROR:\\' .. csname .. ' not defined')
+ end
+end
+
+function api:cs_for_tag(csname, format_spec, target_dir)
+ if token.is_defined(csname) then
+ local tok = token.create(csname)
+ local tags, err = self.cmd:for_each_ref(format_spec, 'refs/tags', {'sort=-authordate'}, target_dir)
+ if tags then
+ for _, info in ipairs(tags) do
+ tex.print(tok)
+ for _, value in ipairs(info) do
+ tex.print('{' .. self:escape_str(value) .. '}')
+ end
+ end
+ else
+ tex.error('ERROR:\\' .. err)
+ end
+ else
+ tex.error('ERROR:\\' .. csname .. ' not defined')
+ end
+end
+
+function api:cs_for_tag_sequence(csname, target_dir)
+ if token.is_defined(csname) then
+ local tok = token.create(csname)
+ local seq, err = self:tags(target_dir)
+ if seq then
+ for idx, tag in ipairs(seq) do
+ if idx < #seq then
+ local next = seq[idx + 1]
+ tex.print(tok, '{' .. tag .. '}{' .. next .. '}{' .. tag .. '...' .. next .. '}')
+ else
+ tex.print(tok, '{' .. tag .. '}{}{' .. tag .. '}')
+ end
+ end
+ else
+ tex.error('ERROR:\\' .. (err or 'Unknown error'))
+ end
+ else
+ tex.error('ERROR:\\' .. csname .. ' not defined')
+ end
+end
+
+return gitinfo
diff --git a/macros/luatex/latex/gitinfo-lua/tex/gitinfo-lua.sty b/macros/luatex/latex/gitinfo-lua/tex/gitinfo-lua.sty
new file mode 100644
index 0000000000..405c668901
--- /dev/null
+++ b/macros/luatex/latex/gitinfo-lua/tex/gitinfo-lua.sty
@@ -0,0 +1,94 @@
+%% gitinfo-lua.sty
+%% Copyright 2023 E. Nijenhuis
+%
+% 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
+% http://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’.
+%
+% The Current Maintainer of this work is E. Nijenhuis.
+%
+% This work consists of the files gitinfo-lua.sty gitinfo-lua.pdf
+% gitinfo-cmd.lua and gitinfo-lua.lua
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{gitinfo-lua}[2023/11/10 Xerdi's Git Package]
+
+\RequirePackage{luacode}
+
+\directlua{git = require('gitinfo-lua')}
+
+\RequirePackage{pgfopts}
+
+\newif\ifgit@multipleauthors
+\newif\ifgit@setmacros
+\pgfkeys{/gitinfo-lua/.is family,
+ /gitinfo-lua,
+ author sort/.default=false,
+ author sort/.store in=\git@author@sort,
+ author sort,
+ contrib/.style = {/git/author sort=true},
+ alpha/.style = {/git/author sort=false},
+ multiple authors/.is if=git@multipleauthors,
+ multiple authors=false,
+ author/.style={multiple authors=false},
+ authors/.style={multiple authors=true},
+ set title page macros/.is if=git@setmacros,
+ set title page macros=false,
+ titlepage/.style={set title page macros=true}
+}
+
+\ProcessPgfPackageOptions*
+
+\newcommand*\git@single@arg[1]{#1}
+
+\newcommand*\gitversion{\directlua{git:write_version()}}
+\newcommand*\gitauthor{\directlua{git:write_local_author()}}
+\newcommand*\gitemail{\directlua{git:write_local_email()}}
+\newcommand*\gitdate{\directlua{git:cs_last_commit('git@single@arg', 'cs')}}
+\newcommand*\gitdirectory[1]{\directlua{git:dir('#1')}}
+\newcommand*\gitunsetdirectory{\directlua{git:dir(nil)}}
+
+\newcommand\git@format@author[2]{%
+ #1
+ \ifcsname href\endcsname%
+ \textlangle\href{mailto:#2}{#2}\textrangle%
+ \else%
+ \textlangle\texttt{#2}\textrangle%
+ \fi%
+}
+
+\newcommand*\dogitauthors[1][,~]{%
+ \directlua{git:cs_for_authors('git@format@author', '#1', \git@author@sort)}%
+}
+
+\newcommand*\forgitauthors[2][\authorconjunction]{%
+ \directlua{git:cs_for_authors('#2', '#1', \git@author@sort)}%
+}
+
+\def\git@default@tag@format{refname:short,(taggername)(taggername,taggeremail,taggerdate:short)(authorname,authoremail,authordate:short),subject,body}
+\newcommand{\gitcommit}[3][h,an,ae,as,s,b]{\directlua{git:cs_commit('#2', '#3', '#1')}}
+\newcommand{\forgitcommit}[3][h,an,ae,as,s,b]{\directlua{git:cs_for_commit('#2', '#3', '#1')}}
+\newcommand{\gittag}[3][\git@default@tag@format]{%
+ \directlua{git:cs_tag('#2', '#1', '#3')}%
+}
+\newcommand{\forgittag}[2][\git@default@tag@format]{%
+ \directlua{git:cs_for_tag('#2', '#1')}%
+}
+\newcommand{\forgittagseq}[1]{\directlua{git:cs_for_tag_sequence('#1')}}
+
+\ifgit@setmacros
+ \ifgit@multipleauthors
+ \author{\dogitauthors[\\\\]}
+ \else
+ \author{\gitauthor}
+ \fi
+ \directlua{git:set_date()}
+ \date{\today}
+\fi
+
+\endinput