%% %% inspirehep.sty -- live INSPIRE-HEP data in a LaTeX document. %% %% Copyright (C) 2026 Lawrence Lee %% %% 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 %% This work has the LPPL maintenance status `maintained'. %% The Current Maintainer of this work is Lawrence Lee. %% %% https://github.com/lawrenceleejr/inspirehep-latex %% \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{inspirehep}[2026/08/29 v0.2 Live INSPIRE-HEP citation counts, references, and plots] \RequirePackage{xparse} \RequirePackage{l3keys2e} \ExplSyntaxOn %% =========================================================================== %% Settings %% =========================================================================== %% Every setting is both a package option and a per-call option, so a document %% sets house style once and any single entry can depart from it: %% %% \usepackage[cites=false]{inspirehep} %% \inspirepub[cites]{2642414} %% %% There is deliberately no author= option: a document may well discuss several %% people, so every author-level command takes the id it is asking about. \bool_new:N \g_inspirehep_pgfplots_bool \bool_new:N \l_inspirehep_cites_bool \bool_new:N \l_inspirehep_ref_bool \bool_new:N \l_inspirehep_year_bool \bool_new:N \l_inspirehep_errata_bool \bool_new:N \l_inspirehep_collab_bool \bool_new:N \l_inspirehep_link_bool \tl_new:N \l_inspirehep_title_tl \int_new:N \l_inspirehep_round_int \tl_new:N \g_inspirehep_data_tl \tl_new:N \g_inspirehep_bib_tl \tl_new:N \g_inspirehep_style_tl \tl_new:N \g_inspirehep_fetch_tl \tl_new:N \l_inspirehep_sep_tl \tl_new:N \g_inspirehep_script_tl \int_new:N \g_inspirehep_maxage_int \int_new:N \l_inspirehep_mincites_int \keys_define:nn { inspirehep } { % what a record entry shows cites .bool_set:N = \l_inspirehep_cites_bool, cites .initial:n = { true }, ref .bool_set:N = \l_inspirehep_ref_bool, ref .initial:n = { false }, year .bool_set:N = \l_inspirehep_year_bool, year .initial:n = { false }, % INSPIRE's reference carries any erratum; most CVs do not want it. errata .bool_set:N = \l_inspirehep_errata_bool, errata .initial:n = { false }, % Credit a collaboration paper to the collaboration rather than to the % first author and "et al." -- the usual convention in a CV. collab .bool_set:N = \l_inspirehep_collab_bool, collab .initial:n = { false }, link .bool_set:N = \l_inspirehep_link_bool, link .initial:n = { true }, title .tl_set:N = \l_inspirehep_title_tl, title .initial:n = { }, round .int_set:N = \l_inspirehep_round_int, round .initial:n = { 1 }, % document-wide plumbing data .tl_gset:N = \g_inspirehep_data_tl, data .initial:n = { inspirehep-data }, bib .tl_gset:N = \g_inspirehep_bib_tl, bib .initial:n = { inspirehep-refs }, style .tl_gset:N = \g_inspirehep_style_tl, style .initial:n = { latex-eu }, fetch .tl_gset:N = \g_inspirehep_fetch_tl, fetch .initial:n = { auto }, maxage .int_gset:N = \g_inspirehep_maxage_int, maxage .initial:n = { 120 }, % Local, so [mincites=...] on one entry cannot leak past its group. mincites .int_set:N = \l_inspirehep_mincites_int, mincites .initial:n = { 1 }, % Where the auto-fetch finds the helper: adjust when the package lives in % a subdirectory or a git submodule. script .tl_gset:N = \g_inspirehep_script_tl, script .initial:n = { inspirehep-fetch.py }, % pgfplots can only be loaded in the preamble, and a CV that never plots % anything should not pay for tikz, so plotting is opt-in. plots .bool_gset:N = \g_inspirehep_pgfplots_bool, plots .initial:n = { false }, % thousands separator: comma by default, or any of these, or your own text % The separator is set locally, so a change inside a group -- or on one % call -- ends with that group. Set at the top level it holds document-wide. sep .choices:nn = { comma, period, space, thin, underscore, none } { \tl_set:Nx \l_inspirehep_sep_tl { \str_case:Vn \l_keys_choice_tl { { comma } { , } { period } { . } { space } { \exp_not:N \nobreakspace } { thin } { \exp_not:N \, } { underscore } { \exp_not:N \_ } { none } { } } } }, sep .value_required:n = true, sepstring .tl_set:N = \l_inspirehep_sep_tl, } \tl_set:Nn \l_inspirehep_sep_tl { , } \ProcessKeysOptions { inspirehep } % Loading pgfplots has to happen now, while we are still in the preamble. \bool_if:NT \g_inspirehep_pgfplots_bool { \RequirePackage { pgfplots } \pgfplotsset { compat = 1.18 } } % \inspiresetup{} -- change the defaults anywhere in the document. \NewDocumentCommand \inspiresetup { m } { \keys_set:nn { inspirehep } {#1} } %% =========================================================================== %% Stored data %% =========================================================================== %% The generated file is a flat list of declarations, written either by this %% package through shell escape or by inspirehep-fetch.py. Both write the same %% file, so it does not matter which produced it. \prop_new:N \g_inspirehep_cites_prop \prop_new:N \g_inspirehep_title_prop \prop_new:N \g_inspirehep_key_prop \prop_new:N \g_inspirehep_recid_prop \prop_new:N \g_inspirehep_ref_prop % a whole reference, set by hand \prop_new:N \g_inspirehep_refauthors_prop \prop_new:N \g_inspirehep_refpub_prop \prop_new:N \g_inspirehep_referrata_prop \prop_new:N \g_inspirehep_refcollab_prop \prop_new:N \g_inspirehep_years_prop \prop_new:N \g_inspirehep_stat_prop \prop_new:N \g_inspirehep_ayears_prop \tl_new:N \g_inspirehep_fetched_tl \int_new:N \g_inspirehep_fetchedday_int % Everything the document asks about, so the fetcher knows what to request. \seq_new:N \g_inspirehep_records_seq \seq_new:N \g_inspirehep_plots_seq \seq_new:N \g_inspirehep_bibs_seq \seq_new:N \g_inspirehep_authors_seq \bool_new:N \g_inspirehep_missing_bool \cs_new_protected:Npn \inspirehep_want:Nn #1#2 { \seq_if_in:NnF #1 {#2} { \seq_gput_right:Nn #1 {#2} } } \NewDocumentCommand \inspiresetcites { m m } { \prop_gput:Nnn \g_inspirehep_cites_prop {#1} {#2} } \NewDocumentCommand \inspiresettitle { m m } { \prop_gput:Nnn \g_inspirehep_title_prop {#1} {#2} } \NewDocumentCommand \inspiresetkey { m m } { \prop_gput:Nnn \g_inspirehep_key_prop {#1} {#2} } \NewDocumentCommand \inspiresetrecid { m m } { \prop_gput:Nnn \g_inspirehep_recid_prop {#1} {#2} } \NewDocumentCommand \inspiresetref { m m } { \prop_gput:Nnn \g_inspirehep_ref_prop {#1} {#2} } \NewDocumentCommand \inspiresetrefauthors { m m } { \prop_gput:Nnn \g_inspirehep_refauthors_prop {#1} {#2} } \NewDocumentCommand \inspiresetrefpub { m m } { \prop_gput:Nnn \g_inspirehep_refpub_prop {#1} {#2} } \NewDocumentCommand \inspiresetreferrata { m m } { \prop_gput:Nnn \g_inspirehep_referrata_prop {#1} {#2} } \NewDocumentCommand \inspiresetrefcollab { m m } { \prop_gput:Nnn \g_inspirehep_refcollab_prop {#1} {#2} } \NewDocumentCommand \inspiresetyears { m m } { \prop_gput:Nnn \g_inspirehep_years_prop {#1} {#2} } \NewDocumentCommand \inspiresetstat { m m } { \prop_gput:Nnn \g_inspirehep_stat_prop {#1} {#2} } \NewDocumentCommand \inspiresetauthorstat { m m m } { \prop_gput:Nnn \g_inspirehep_stat_prop {#1/#2} {#3} } \NewDocumentCommand \inspiresetauthoryears { m m m } { \prop_gput:Nnn \g_inspirehep_ayears_prop {#1/#2} {#3} } \NewDocumentCommand \inspiresetfetched { m m } { \tl_gset:Nn \g_inspirehep_fetched_tl {#1} \int_gset:Nn \g_inspirehep_fetchedday_int {#2} } % \inspiredefault...{...}{...} -- values used only until the real ones arrive. \NewDocumentCommand \inspiredefaultstat { m m } { \prop_if_in:NnF \g_inspirehep_stat_prop {#1} { \inspiresetstat {#1} {#2} } } \NewDocumentCommand \inspiredefaultauthorstat { m m m } { \prop_if_in:NnF \g_inspirehep_stat_prop {#1/#2} { \inspiresetauthorstat {#1} {#2} {#3} } } %% =========================================================================== %% Numbers %% =========================================================================== \providecommand \inspirenumsep { \l_inspirehep_sep_tl } \cs_new:Npn \inspirehep_group:n #1 { \int_compare:nNnTF { \tl_count:n {#1} } > { 3 } { \exp_args:Nf \inspirehep_group:n { \tl_range:nnn {#1} { 1 } { \int_eval:n { \tl_count:n {#1} - 3 } } } \inspirenumsep \tl_range:nnn {#1} { \int_eval:n { \tl_count:n {#1} - 2 } } { \tl_count:n {#1} } } {#1} } \providecommand \inspirenumformat [1] { \exp_args:Nf \inspirehep_group:n { \int_to_arabic:n {#1} } } % Round DOWN to a multiple of the round= option, so an "over N" claim stays % true as the real figure grows. \cs_new:Npn \inspirehep_round:n #1 { \int_compare:nNnTF { \l_inspirehep_round_int } > { 1 } { \int_eval:n { \int_div_truncate:nn {#1} { \l_inspirehep_round_int } * \l_inspirehep_round_int } } { \int_eval:n {#1} } } \ExplSyntaxOff \ExplSyntaxOn %% =========================================================================== %% Typesetting hooks %% =========================================================================== \providecommand \inspiretitleformat [1] { \textbf {#1} } \providecommand \inspirecitestext [1] { #1 \nobreakspace \int_compare:nNnTF {#1} = { 1 } { citation } { citations } } \providecommand \inspirecitesformat [1] { \nobreakspace { \small [#1] } } \providecommand \inspireyearformat [1] { \nobreakspace (#1) } \providecommand \inspiremissing [1] { \textbf { [?~#1] } } \cs_new:Npn \inspirehep_link:nn #1#2 { #2 } \AddToHook { begindocument } { \cs_if_exist:NT \href { \cs_gset:Npn \inspirehep_link:nn #1#2 { \href {#1} {#2} } } } %% A record can be named either by its recid (2642414) or by its INSPIRE %% texkey (Lee:2018pag); the two are told apart by shape, so nothing has to be %% declared. Lookups just use whichever string the document wrote, because the %% fetcher stores the data under that same string. Only the URL needs the %% number, which the fetcher supplies alongside when a texkey was used. \prg_new_conditional:Npnn \inspirehep_is_texkey:n #1 { TF } { \regex_match:nnTF { \A [A-Za-z] [\w.'\-]* : \d{4} [A-Za-z0-9]+ \Z } {#1} { \prg_return_true: } { \prg_return_false: } } %% The URL is built into a variable rather than expanded in place: \href reads %% its argument under special catcodes and will not tolerate a conditional %% there, so anything non-expandable has to happen first. \cs_new_protected:Npn \inspirehep_url:nN #1#2 { \prop_get:NnNF \g_inspirehep_recid_prop {#1} #2 { \tl_set:Nn #2 {#1} } \tl_put_left:Nn #2 { https://inspirehep.net/literature/ } % Flatten to a string, so every character sits at catcode 12. hyperref % decides between a URL and a local file by looking for a protocol, and % under \ExplSyntaxOn a `:' is a letter rather than an ordinary character % -- so `https:' did not read as a protocol, \href took the target for a % filename, and hyperref appended its default .pdf extension. Every link % the package drew pointed at inspirehep.net/literature/.pdf, which is % not a page. \tl_set:Nx #2 { \tl_to_str:N #2 } } \cs_new_protected:Npn \inspirehep_lookup:NnnTF #1#2#3 { \prop_get:NnNTF #1 {#2} #3 { \use_i:nn } { \bool_gset_true:N \g_inspirehep_missing_bool \use_ii:nn } } %% =========================================================================== %% Records %% =========================================================================== %% \inspirecites{} -- the citation annotation alone. \NewDocumentCommand \inspirecites { m } { \inspirehep_want:Nn \g_inspirehep_records_seq {#1} \prop_get:NnNTF \g_inspirehep_cites_prop {#1} \l_tmpa_tl { \int_compare:nNnT { \l_tmpa_tl } > { \l_inspirehep_mincites_int - 1 } { \inspirecitesformat { \inspirecitestext { \tl_use:N \l_tmpa_tl } } } } { \bool_gset_true:N \g_inspirehep_missing_bool } } %% \inspiretitle{} -- the title as INSPIRE records it. \NewDocumentCommand \inspiretitle { m } { \inspirehep_want:Nn \g_inspirehep_records_seq {#1} \inspirehep_lookup:NnnTF \g_inspirehep_title_prop {#1} \l_tmpa_tl { \tl_use:N \l_tmpa_tl } { \inspiremissing { title~#1 } } } %% \inspirehep_ref:n -- the reference, assembled from the parts INSPIRE's own %% renderer produced. Keeping them apart is what lets `errata' and `collab' be %% ordinary options rather than choices frozen when the data was fetched. %% %% A whole reference set by hand with \inspiresetref wins over the parts, so an %% entry INSPIRE does not carry can still be written out in full. \providecommand \inspirecollabformat [1] { #1~Collaboration } \cs_new_protected:Npn \inspirehep_ref:n #1 { \prop_get:NnNTF \g_inspirehep_ref_prop {#1} \l_tmpa_tl { \tl_use:N \l_tmpa_tl } { % who \bool_lazy_and:nnTF { \l_inspirehep_collab_bool } { \prop_if_in_p:Nn \g_inspirehep_refcollab_prop {#1} } { \prop_get:NnN \g_inspirehep_refcollab_prop {#1} \l_tmpa_tl \inspirecollabformat { \tl_use:N \l_tmpa_tl } } { \prop_get:NnNT \g_inspirehep_refauthors_prop {#1} \l_tmpa_tl { \tl_use:N \l_tmpa_tl } } % title \prop_get:NnNT \g_inspirehep_title_prop {#1} \l_tmpa_tl { ,~ `` \tl_use:N \l_tmpa_tl ,'' } % where \prop_get:NnNT \g_inspirehep_refpub_prop {#1} \l_tmpa_tl { ~ \tl_use:N \l_tmpa_tl } % and the erratum, if asked for \bool_if:NT \l_inspirehep_errata_bool { \prop_get:NnNT \g_inspirehep_referrata_prop {#1} \l_tmpa_tl { ~ \tl_use:N \l_tmpa_tl } } } } \cs_new_protected:Npn \inspirehep_has_ref:nTF #1 { \bool_lazy_or:nnTF { \prop_if_in_p:Nn \g_inspirehep_ref_prop {#1} } { \prop_if_in_p:Nn \g_inspirehep_refpub_prop {#1} } } %% \inspireref[]{} -- the full reference. \NewDocumentCommand \inspireref { O{} m } { \group_begin: \keys_set:nn { inspirehep } {#1} \inspirehep_want:Nn \g_inspirehep_records_seq {#2} \inspirehep_has_ref:nTF {#2} { \inspirehep_ref:n {#2} } { \bool_gset_true:N \g_inspirehep_missing_bool \inspiremissing { ref~#2 } } \bool_if:NT \l_inspirehep_cites_bool { \inspirecites {#2} } \group_end: } %% \inspirekey{} -- the BibTeX key, and \inspirecite{} -- \cite with it. \NewDocumentCommand \inspirekey { m } { \inspirehep_want:Nn \g_inspirehep_bibs_seq {#1} \prop_get:NnNTF \g_inspirehep_key_prop {#1} \l_tmpa_tl { \tl_use:N \l_tmpa_tl } { % Given a texkey, that is already the answer -- so \inspirecite works % before anything has ever been fetched. Given a recid we cannot know % it yet, so say so and ask the next fetch for it. \bool_gset_true:N \g_inspirehep_missing_bool \inspirehep_is_texkey:nTF {#1} {#1} { \inspiremissing { key~#1 } } } } %% \cite turns its argument into a control sequence, so it cannot be handed an %% unexpanded \inspirekey -- the key is looked up first and passed by value. \NewDocumentCommand \inspirecite { m } { \inspirehep_want:Nn \g_inspirehep_bibs_seq {#1} \prop_get:NnNTF \g_inspirehep_key_prop {#1} \l_tmpa_tl { \exp_args:NV \cite \l_tmpa_tl } { \bool_gset_true:N \g_inspirehep_missing_bool \inspirehep_is_texkey:nTF {#1} { \cite {#1} } % a texkey is already the key { \inspiremissing { key~#1 } } } } %% \inspirepub[]{} -- a publication entry. %% %% With no options it is the title, linked, with its citation count. The title %% comes from INSPIRE unless title= supplies one; ref= swaps the title for the %% full reference; year= appends the publication year; cites=false drops the %% count; link=false drops the hyperlink. \NewDocumentCommand \inspirepub { O{} m } { \group_begin: \keys_set:nn { inspirehep } {#1} \inspirehep_want:Nn \g_inspirehep_records_seq {#2} \bool_if:NTF \l_inspirehep_ref_bool { \inspirehep_has_ref:nTF {#2} { \inspirehep_show:nn {#2} { \inspirehep_ref:n {#2} } } { \bool_gset_true:N \g_inspirehep_missing_bool \inspiremissing { ref~#2 } } } { \tl_if_empty:NTF \l_inspirehep_title_tl { \inspirehep_lookup:NnnTF \g_inspirehep_title_prop {#2} \l_tmpa_tl { \inspirehep_show:nn {#2} { \inspiretitleformat { \tl_use:N \l_tmpa_tl } } } { \inspiremissing { title~#2 } } } { \inspirehep_show:nn {#2} { \inspiretitleformat { \tl_use:N \l_inspirehep_title_tl } } } } \bool_if:NT \l_inspirehep_cites_bool { \inspirecites {#2} } \bool_if:NT \l_inspirehep_year_bool { \inspireyear {#2} } \group_end: } \tl_new:N \l_inspirehep_url_tl \cs_new_protected:Npn \inspirehep_show:nn #1#2 { \bool_if:NTF \l_inspirehep_link_bool { \inspirehep_url:nN {#1} \l_inspirehep_url_tl \exp_args:NV \inspirehep_link:nn \l_inspirehep_url_tl {#2} } {#2} } %% \inspireyear{} -- the publication year, taken from the reference. \NewDocumentCommand \inspireyear { m } { \inspirehep_want:Nn \g_inspirehep_records_seq {#1} \prop_get:NnNTF \g_inspirehep_refpub_prop {#1} \l_tmpa_tl { \regex_extract_once:nVNTF { \((\d{4})\) } \l_tmpa_tl \l_tmpa_seq { \inspireyearformat { \seq_item:Nn \l_tmpa_seq { 2 } } } { } } { \bool_gset_true:N \g_inspirehep_missing_bool } } %% =========================================================================== %% Authors %% =========================================================================== %% Each takes the id it is asking about, so one document can discuss several %% people. The id is the number in a profile URL, inspirehep.net/authors/N, %% or a BAI such as J.Smith.1. \NewDocumentCommand \inspireauthorstat { O{} m m } { \group_begin: \keys_set:nn { inspirehep } {#1} \inspirehep_want:Nn \g_inspirehep_authors_seq {#2} \inspirehep_lookup:NnnTF \g_inspirehep_stat_prop {#2/#3} \l_tmpa_tl { \inspirenumformat { \inspirehep_round:n { \l_tmpa_tl } } } { \inspiremissing {#3} } \group_end: } \NewDocumentCommand \inspirepapers { O{} m } { \inspireauthorstat [#1] {#2} { papers } } \NewDocumentCommand \inspirecitations { O{} m } { \inspireauthorstat [#1] {#2} { citations } } \NewDocumentCommand \inspirehindex { O{} m } { \inspireauthorstat [#1] {#2} { hindex } } \ExplSyntaxOff \ExplSyntaxOn %% =========================================================================== %% Plots %% =========================================================================== %% A year series arrives as "2019:1,2020:7,..." and is drawn with pgfplots, %% which is loaded only if a plot is actually asked for -- a CV that never %% plots anything should not pay for the dependency. %% %% The default style is deliberately plain: one line, no grid, no legend, no %% box, axis lines only where they carry information. Everything is %% overridable, and \inspireplotstyle is where to put house style once. \cs_new_protected:Npn \inspirehep_need_pgfplots: { \cs_if_exist:NF \pgfplotsset { \PackageError { inspirehep } { plotting~needs~pgfplots } { Pass~`plots'~to~\string\usepackage{inspirehep},~or~load~pgfplots~yourself. } } } \providecommand \inspireplotstyle {} \providecommand \inspireplotwidth { 0.8\linewidth } \providecommand \inspireplotheight { 4cm } %% The picture itself is built at normal catcodes: pgfplots keys are full of %% characters that \ExplSyntaxOn reinterprets, and the coordinate list has to %% keep its spaces. expl3 only prepares the coordinates and hands them over. \ExplSyntaxOff \newcommand{\inspirehepdrawplot}[2]{% \begin{tikzpicture} \begin{axis}[ width=\inspireplotwidth, height=\inspireplotheight, axis lines=left, axis line style={-,gray!60}, tick style={gray!60}, tick align=outside, xtick distance=5, scaled y ticks=false, y tick label style={/pgf/number format/1000 sep={\inspirenumsep}}, x tick label style={/pgf/number format/1000 sep={}}, ylabel={#2}, ylabel style={font=\small}, tick label style={font=\small}, % A little headroom above the peak: without it the highest point sits % exactly on the top edge, where a thick line reads as clipped. Only % the upper bound moves, so the axis still starts at zero. ymin=0, enlarge y limits={upper, value=0.08}, enlarge x limits=0.03, clip=false, \inspireplotstyle ] \addplot[thick, mark=none, black] coordinates {#1}; \end{axis} \end{tikzpicture}% } \ExplSyntaxOn \cs_new_protected:Npn \inspirehep_draw:nn #1#2 { \inspirehep_need_pgfplots: \inspirehepdrawplot {#1} {#2} } %% \inspireplot[]{} -- citations per year for one paper. \NewDocumentCommand \inspireplot { O{} m } { \group_begin: \keys_set:nn { inspirehep } {#1} \inspirehep_want:Nn \g_inspirehep_plots_seq {#2} \inspirehep_want:Nn \g_inspirehep_records_seq {#2} \inspirehep_lookup:NnnTF \g_inspirehep_years_prop {#2} \l_tmpa_tl { \inspirehep_draw:nn { \l_tmpa_tl } { citations } } { \inspiremissing { plot~#2 } } \group_end: } %% \inspireauthorplot[]{}{papers|citations} -- per year, for a %% person: how much they published, or how much they were cited. \NewDocumentCommand \inspireauthorplot { O{} m m } { \group_begin: \keys_set:nn { inspirehep } {#1} \inspirehep_want:Nn \g_inspirehep_authors_seq {#2} \inspirehep_lookup:NnnTF \g_inspirehep_ayears_prop {#2/#3} \l_tmpa_tl { \inspirehep_draw:nn { \l_tmpa_tl } {#3} } { \inspiremissing { plot~#2/#3 } } \group_end: } %% =========================================================================== %% Refreshing the data %% =========================================================================== %% The package can refresh everything itself through TeX's shell escape, by %% handing the work to inspirehep-fetch.py -- which is shipped alongside it and %% already knows how to read the sources and write the file. That keeps one %% implementation of the API work instead of two that can disagree. %% %% Without shell escape nothing is fetched and the committed data file is used %% as-is, which is what happens on Overleaf. See the README. \bool_new:N \g_inspirehep_stale_bool \bool_new:N \g_inspirehep_canfetch_bool \cs_new:Npn \inspirehep_dayno: { \int_eval:n { 365 * \c_sys_year_int + 31 * \c_sys_month_int + \c_sys_day_int } } \cs_new:Npn \inspirehep_datafile: { \g_inspirehep_data_tl .tex } \str_case:VnF \g_inspirehep_fetch_tl { { off } { } { on } { \sys_if_shell_unrestricted:TF { \bool_gset_true:N \g_inspirehep_canfetch_bool } { \PackageWarningNoLine { inspirehep } { fetch=on~but~unrestricted~shell~escape~is~off. \MessageBreak Compile~with~-shell-escape,~run~inspirehep-fetch.py,~or~set~fetch=off } } } { auto } { \sys_if_shell_unrestricted:T { \bool_gset_true:N \g_inspirehep_canfetch_bool } } } { \PackageWarningNoLine { inspirehep } { unknown~fetch=\g_inspirehep_fetch_tl } } \AddToHook { begindocument } { \file_if_exist:nTF { \inspirehep_datafile: } { \file_input:n { \inspirehep_datafile: } } { \bool_gset_true:N \g_inspirehep_stale_bool } \int_compare:nNnT { \g_inspirehep_fetchedday_int } > { 0 } { \int_compare:nNnT { \inspirehep_dayno: - \g_inspirehep_fetchedday_int } > { \g_inspirehep_maxage_int } { \bool_gset_true:N \g_inspirehep_stale_bool \bool_if:NF \g_inspirehep_canfetch_bool { \PackageWarningNoLine { inspirehep } { The~data~dates~from~\g_inspirehep_fetched_tl\space (about~ \int_eval:n { \inspirehep_dayno: - \g_inspirehep_fetchedday_int }~days~ago). \MessageBreak Refresh~with~-shell-escape~or~inspirehep-fetch.py } } } } } \AddToHook { enddocument / afterlastpage } { \bool_lazy_and:nnT { \g_inspirehep_canfetch_bool } { \bool_lazy_or_p:nn { \g_inspirehep_stale_bool } { \g_inspirehep_missing_bool } } { \PackageInfo { inspirehep } { refreshing~from~inspirehep.net } \sys_shell_now:x { % The separators below are \c_space_tl, not ~, after a variable. % Under \ExplSyntaxOn a ~ is an ordinary space token, and TeX % discards spaces following a control word -- so a ~ written % straight after \g_inspirehep_script_tl is swallowed as that % csname's terminator, and the script name runs into the file % name. A ~ after a plain character (python3, --output) is % fine, which is why only some of these need it. python3~\g_inspirehep_script_tl \c_space_tl \c_sys_jobname_str .tex \c_space_tl --output~\g_inspirehep_data_tl .tex \c_space_tl --bib~\g_inspirehep_bib_tl .bib \c_space_tl --style~\g_inspirehep_style_tl } \PackageWarningNoLine { inspirehep } { The~data~was~refreshed.~Rerun~LaTeX~to~pick~it~up } } } \ExplSyntaxOff \endinput