diff options
author | Norbert Preining <norbert@preining.info> | 2023-11-11 03:03:26 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2023-11-11 03:03:26 +0000 |
commit | e4ccc7921b6518e670428990524161b3ddbfebd2 (patch) | |
tree | cc5e3d265c59f5f5031b2b085a4621d13f0b1af6 /macros/luatex/latex/gitinfo-lua | |
parent | d8b5161942976a522cdb1a7b580fe3886eea6bc6 (diff) |
CTAN sync 202311110303
Diffstat (limited to 'macros/luatex/latex/gitinfo-lua')
-rw-r--r-- | macros/luatex/latex/gitinfo-lua/README.md | 24 | ||||
-rw-r--r-- | macros/luatex/latex/gitinfo-lua/doc/git-scenario.sh | 61 | ||||
-rw-r--r-- | macros/luatex/latex/gitinfo-lua/doc/gitinfo-lua.pdf | bin | 0 -> 116103 bytes | |||
-rw-r--r-- | macros/luatex/latex/gitinfo-lua/doc/gitinfo-lua.tex | 344 | ||||
-rw-r--r-- | macros/luatex/latex/gitinfo-lua/scripts/gitinfo-lua-cmd.lua | 186 | ||||
-rw-r--r-- | macros/luatex/latex/gitinfo-lua/scripts/gitinfo-lua.lua | 345 | ||||
-rw-r--r-- | macros/luatex/latex/gitinfo-lua/tex/gitinfo-lua.sty | 94 |
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 Binary files differnew file mode 100644 index 0000000000..ad1db4e0b1 --- /dev/null +++ b/macros/luatex/latex/gitinfo-lua/doc/gitinfo-lua.pdf 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 |