├── template.bib
├── README.md
├── .gitignore
├── template.log.pulp
├── .latexmkrc
├── makefile
├── template.tex
├── acmart.cls
└── latexrun
/template.bib:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | See http://norswap.com/latex-tooling/
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /local/
2 | /auto/
3 | /latex.out
4 | /*.pdf
5 | *.aux
6 | *.blg
7 | *.bbl
8 | *.log
9 | *.out
10 | *.vtc
11 | *.fls
12 | *.cut
13 | *.fdb_latexmk
14 | *.synctex.gz
15 |
--------------------------------------------------------------------------------
/template.log.pulp:
--------------------------------------------------------------------------------
1 | !(boring | info | message | under | over
2 | | p "hyperref" & "Token not allowed in a PDF string"
3 | | "Class acmart Info"
4 | | "file:line:error style messages enabled"
5 | | "Excluding.*comment"
6 | | "Processing.*comment"
7 | | "Include comment"
8 | | "comment.cut"
9 | | "(msharpe)"
10 | | "]" && !".."
11 | )
12 |
--------------------------------------------------------------------------------
/.latexmkrc:
--------------------------------------------------------------------------------
1 | $pdflatex = 'internal pulplatex pdflatex %Y%B.log %O %S';
2 | $latex = 'internal pulplatex latex %Y%B.log %O %S';
3 |
4 | sub pulplatex {
5 | my ($prog, $log, @args) = @_;
6 | print "====Running $prog @args\n";
7 | my $ret1 = system( $prog, '-interaction=batchmode', @args );
8 | print "====Summarizing the log file $log\n";
9 | system( 'pulp', $log );
10 | return $ret1;
11 | }
12 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | TEX=pdflatex -shell-escape -interaction=nonstopmode -file-line-error
2 | PRE= $(TEX) -ini -job-name="preamble" "&pdflatex preamble.tex\dump"
3 | BIB=bibtex
4 | NAME=template
5 |
6 | ifeq ($(OS),Windows_NT)
7 | VIEWER=start
8 | else
9 | UNAME_S := $(shell uname -s)
10 | ifeq ($(UNAME_S),Linux)
11 | VIEWER=xdg-open
12 | endif
13 | ifeq ($(UNAME_S),Darwin)
14 | VIEWER=open
15 | endif
16 | endif
17 |
18 | # Test if latexmk is available and not buggy (like on my Windows machine...)
19 | HAS_LATEXMK=FALSE
20 | ifneq (,$(shell which latexmk))
21 | ifeq (0,$(shell latexmk --help > /dev/null 2>&1; echo $$?))
22 | HAS_LATEXMK=TRUE
23 | endif
24 | endif
25 |
26 | .PHONY: all view clean watch verbose rebuild mrproper run pulp
27 | .SILENT:
28 |
29 | view : $(NAME).pdf
30 | @$(VIEWER) $(NAME).pdf
31 |
32 | pdf : $(NAME).pdf
33 |
34 | ifeq ($(HAS_LATEXMK),TRUE)
35 |
36 | $(NAME).pdf: $(NAME).tex $(NAME).bib
37 | @latexmk -pdf -quiet $(NAME).tex
38 | @echo "/!\ In case of error, run 'make verbose'"
39 |
40 | verbose:
41 | @latexmk -pdf -gg $(NAME).tex
42 |
43 | rebuild:
44 | @latexmk -pdf -quiet -gg $(NAME).tex
45 |
46 | clean:
47 | @latexmk -c $(NAME).tex
48 | @rm -f $(NAME).vtc $(NAME).synctex.gz comment.cut
49 | @rm -rf auto latex.out
50 |
51 | watch:
52 | @latexmk -pdf -pvc $(NAME).tex
53 |
54 | else # latexmk unavailable
55 |
56 | # adapted from: http://tex.stackexchange.com/questions/40738/
57 |
58 | $(NAME).pdf : $(NAME).tex $(NAME).bbl $(NAME).blg
59 | echo yay
60 | $(TEX) $(NAME).tex
61 |
62 | $(NAME).bbl $(NAME).blg : $(NAME).bib $(NAME).aux
63 | $(BIB) $(NAME)
64 |
65 | $(NAME).aux: $(NAME).tex
66 | $(TEX) $(NAME).tex
67 |
68 | clean:
69 | @rm -f $(NAME).aux $(NAME).bbl $(NAME).blg $(NAME).log $(NAME).out \\
70 | $(NAME).vtc $(NAME).fdb_latexmk $(NAME).fls $(NAME).synctex.gz latex.out comment.cut
71 | @rm -rf auto latex.out
72 |
73 | endif
74 |
75 | run:
76 | ./latexrun $(NAME).tex
77 |
78 | pulp:
79 | pulp $(NAME).log
80 |
81 | mrproper: clean
82 | @rm -f $(NAME).pdf
83 |
--------------------------------------------------------------------------------
/template.tex:
--------------------------------------------------------------------------------
1 | %% For double-blind review submission
2 | %\documentclass[sigplan,10pt,review,anonymous]{acmart}\settopmatter{printfolios=true}
3 | %% For single-blind review submission
4 | \documentclass[sigplan,10pt,review]{acmart}\settopmatter{printfolios=true}
5 | %% For final camera-ready submission
6 | %\documentclass[sigplan,10pt]{acmart}\settopmatter{}
7 |
8 | %% Note: Authors migrating a paper from traditional SIGPLAN
9 | %% proceedings format to PACMPL format should change 'sigplan,10pt' to
10 | %% 'acmlarge'.
11 |
12 | %% Some recommended packages.
13 | \usepackage{booktabs} %% For formal tables:
14 | %% http://ctan.org/pkg/booktabs
15 | \usepackage{subcaption} %% For complex figures with subfigures/subcaptions
16 | %% http://ctan.org/pkg/subcaption
17 |
18 |
19 | \makeatletter\if@ACM@journal\makeatother
20 | %% Journal information (used by PACMPL format)
21 | %% Supplied to authors by publisher for camera-ready submission
22 | \acmJournal{PACMPL}
23 | \acmVolume{1}
24 | \acmNumber{1}
25 | \acmArticle{1}
26 | \acmYear{2017}
27 | \acmMonth{1}
28 | \acmDOI{10.1145/nnnnnnn.nnnnnnn}
29 | \startPage{1}
30 | \else\makeatother
31 | %% Conference information (used by SIGPLAN proceedings format)
32 | %% Supplied to authors by publisher for camera-ready submission
33 | \acmConference[PL'17]{ACM SIGPLAN Conference on Programming Languages}{January 01--03, 2017}{New York, NY, USA}
34 | \acmYear{2017}
35 | \acmISBN{978-x-xxxx-xxxx-x/YY/MM}
36 | \acmDOI{10.1145/nnnnnnn.nnnnnnn}
37 | \startPage{1}
38 | \fi
39 |
40 |
41 | %% Copyright information
42 | %% Supplied to authors (based on authors' rights management selection;
43 | %% see authors.acm.org) by publisher for camera-ready submission
44 | \setcopyright{none} %% For review submission
45 | %\setcopyright{acmcopyright}
46 | %\setcopyright{acmlicensed}
47 | %\setcopyright{rightsretained}
48 | %\copyrightyear{2017} %% If different from \acmYear
49 |
50 |
51 | %% Bibliography style
52 | \bibliographystyle{ACM-Reference-Format}
53 | %% Citation style
54 | %% Note: author/year citations are required for papers published as an
55 | %% issue of PACMPL.
56 | %\citestyle{acmauthoryear} %% For author/year citations
57 | %\citestyle{acmnumeric} %% For numeric citations
58 | %\setcitestyle{nosort} %% With 'acmnumeric', to disable automatic
59 | %% sorting of references within a single citation;
60 | %% e.g., \cite{Smith99,Carpenter05,Baker12}
61 | %% rendered as [14,5,2] rather than [2,5,14].
62 | %\setcitesyle{nocompress} %% With 'acmnumeric', to disable automatic
63 | %% compression of sequential references within a
64 | %% single citation;
65 | %% e.g., \cite{Baker12,Baker14,Baker16}
66 | %% rendered as [2,3,4] rather than [2-4].
67 |
68 |
69 |
70 | \begin{document}
71 |
72 | %% Title information
73 | \title[Short Title]{Full Title} %% [Short Title] is optional;
74 | %% when present, will be used in
75 | %% header instead of Full Title.
76 | \titlenote{with title note} %% \titlenote is optional;
77 | %% can be repeated if necessary;
78 | %% contents suppressed with 'anonymous'
79 | \subtitle{Subtitle} %% \subtitle is optional
80 | \subtitlenote{with subtitle note} %% \subtitlenote is optional;
81 | %% can be repeated if necessary;
82 | %% contents suppressed with 'anonymous'
83 |
84 |
85 | %% Author information
86 | %% Contents and number of authors suppressed with 'anonymous'.
87 | %% Each author should be introduced by \author, followed by
88 | %% \authornote (optional), \orcid (optional), \affiliation, and
89 | %% \email.
90 | %% An author may have multiple affiliations and/or emails; repeat the
91 | %% appropriate command.
92 | %% Many elements are not rendered, but should be provided for metadata
93 | %% extraction tools.
94 |
95 | %% Author with single affiliation.
96 | \author{First1 Last1}
97 | \authornote{with author1 note} %% \authornote is optional;
98 | %% can be repeated if necessary
99 | \orcid{nnnn-nnnn-nnnn-nnnn} %% \orcid is optional
100 | \affiliation{
101 | \position{Position1}
102 | \department{Department1} %% \department is recommended
103 | \institution{Institution1} %% \institution is required
104 | \streetaddress{Street1 Address1}
105 | \city{City1}
106 | \state{State1}
107 | \postcode{Post-Code1}
108 | \country{Country1}
109 | }
110 | \email{first1.last1@inst1.edu} %% \email is recommended
111 |
112 | %% Author with two affiliations and emails.
113 | \author{First2 Last2}
114 | \authornote{with author2 note} %% \authornote is optional;
115 | %% can be repeated if necessary
116 | \orcid{nnnn-nnnn-nnnn-nnnn} %% \orcid is optional
117 | \affiliation{
118 | \position{Position2a}
119 | \department{Department2a} %% \department is recommended
120 | \institution{Institution2a} %% \institution is required
121 | \streetaddress{Street2a Address2a}
122 | \city{City2a}
123 | \state{State2a}
124 | \postcode{Post-Code2a}
125 | \country{Country2a}
126 | }
127 | \email{first2.last2@inst2a.com} %% \email is recommended
128 | \affiliation{
129 | \position{Position2b}
130 | \department{Department2b} %% \department is recommended
131 | \institution{Institution2b} %% \institution is required
132 | \streetaddress{Street3b Address2b}
133 | \city{City2b}
134 | \state{State2b}
135 | \postcode{Post-Code2b}
136 | \country{Country2b}
137 | }
138 | \email{first2.last2@inst2b.org} %% \email is recommended
139 |
140 |
141 | %% Paper note
142 | %% The \thanks command may be used to create a "paper note" ---
143 | %% similar to a title note or an author note, but not explicitly
144 | %% associated with a particular element. It will appear immediately
145 | %% above the permission/copyright statement.
146 | \thanks{with paper note} %% \thanks is optional
147 | %% can be repeated if necesary
148 | %% contents suppressed with 'anonymous'
149 |
150 |
151 | %% Abstract
152 | %% Note: \begin{abstract}...\end{abstract} environment must come
153 | %% before \maketitle command
154 | \begin{abstract}
155 | Text of abstract. YEAH. \ldots.
156 | \end{abstract}
157 |
158 |
159 | %% 2012 ACM Computing Classification System (CSS) concepts
160 | %% Generate at 'http://dl.acm.org/ccs/ccs.cfm'.
161 | \begin{CCSXML}
162 |
163 |
164 | 10011007.10011006.10011008
165 | Software and its engineering~General programming languages
166 | 500
167 |
168 |
169 | 10003456.10003457.10003521.10003525
170 | Social and professional topics~History of programming languages
171 | 300
172 |
173 |
174 | \end{CCSXML}
175 |
176 | \ccsdesc[500]{Software and its engineering~General programming languages}
177 | \ccsdesc[300]{Social and professional topics~History of programming languages}
178 | %% End of generated code
179 |
180 |
181 | %% Keywords
182 | %% comma separated list
183 | \keywords{keyword1, keyword2, keyword3} %% \keywords is optional
184 |
185 |
186 | %% \maketitle
187 | %% Note: \maketitle command must come after title commands, author
188 | %% commands, abstract environment, Computing Classification System
189 | %% environment and commands, and keywords command.
190 | \maketitle
191 |
192 |
193 | \section{Introduction}
194 |
195 | Text of paper \ldots
196 |
197 |
198 | %% Acknowledgments
199 | \begin{acks} %% acks environment is optional
200 | %% contents suppressed with 'anonymous'
201 | %% Commands \grantsponsor{}{}{} and
202 | %% \grantnum[]{}{} should be used to
203 | %% acknowledge financial support and will be used by metadata
204 | %% extraction tools.
205 | This material is based upon work supported by the
206 | \grantsponsor{GS100000001}{National Science
207 | Foundation}{http://dx.doi.org/10.13039/100000001} under Grant
208 | No.~\grantnum{GS100000001}{nnnnnnn} and Grant
209 | No.~\grantnum{GS100000001}{mmmmmmm}. Any opinions, findings, and
210 | conclusions or recommendations expressed in this material are those
211 | of the author and do not necessarily reflect the views of the
212 | National Science Foundation.
213 | \end{acks}
214 |
215 |
216 | %% Bibliography
217 | %\bibliography{bibfile}
218 |
219 |
220 | %% Appendix
221 | \appendix
222 | \section{Appendix}
223 |
224 | Text of appendix \ldots
225 |
226 | \end{document}
227 |
--------------------------------------------------------------------------------
/acmart.cls:
--------------------------------------------------------------------------------
1 | %%
2 | %% This is file `acmart.cls',
3 | %% generated with the docstrip utility.
4 | %%
5 | %% The original source files were:
6 | %%
7 | %% acmart.dtx (with options: `class')
8 | %%
9 | %% IMPORTANT NOTICE:
10 | %%
11 | %% For the copyright see the source file.
12 | %%
13 | %% Any modified versions of this file must be renamed
14 | %% with new filenames distinct from acmart.cls.
15 | %%
16 | %% For distribution of the original source see the terms
17 | %% for copying and modification in the file acmart.dtx.
18 | %%
19 | %% This generated file may be distributed as long as the
20 | %% original source files, as listed above, are part of the
21 | %% same distribution. (The sources need not necessarily be
22 | %% in the same archive or directory.)
23 | %% \CharacterTable
24 | %% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
25 | %% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
26 | %% Digits \0\1\2\3\4\5\6\7\8\9
27 | %% Exclamation \! Double quote \" Hash (number) \#
28 | %% Dollar \$ Percent \% Ampersand \&
29 | %% Acute accent \' Left paren \( Right paren \)
30 | %% Asterisk \* Plus \+ Comma \,
31 | %% Minus \- Point \. Solidus \/
32 | %% Colon \: Semicolon \; Less than \<
33 | %% Equals \= Greater than \> Question mark \?
34 | %% Commercial at \@ Left bracket \[ Backslash \\
35 | %% Right bracket \] Circumflex \^ Underscore \_
36 | %% Grave accent \` Left brace \{ Vertical bar \|
37 | %% Right brace \} Tilde \~}
38 |
39 | \NeedsTeXFormat{LaTeX2e}
40 | \ProvidesClass{acmart}
41 | [2016/12/03 v1.25 Typesetting articles for Association of
42 | Computing Machinery]
43 | \def\@classname{acmart}
44 | \RequirePackage{xkeyval}
45 | \define@choicekey*+{acmart.cls}{format}[\ACM@format\ACM@format@nr]{%
46 | manuscript, acmsmall, acmlarge, acmtog, sigconf, siggraph,
47 | sigplan, sigchi, sigchi-a}[manuscript]{}{%
48 | \ClassError{\@classname}{The option format must be manuscript,
49 | acmsmall, acmlarge, acmtog, sigconf, siggraph,
50 | sigplan, sigchi or sigchi-a}}
51 | \def\@DeclareACMFormat#1{\DeclareOptionX{#1}{\setkeys{acmart.cls}{format=#1}}}
52 | \@DeclareACMFormat{manuscript}
53 | \@DeclareACMFormat{acmsmall}
54 | \@DeclareACMFormat{acmlarge}
55 | \@DeclareACMFormat{acmtog}
56 | \@DeclareACMFormat{sigconf}
57 | \@DeclareACMFormat{siggraph}
58 | \@DeclareACMFormat{sigplan}
59 | \@DeclareACMFormat{sigchi}
60 | \@DeclareACMFormat{sigchi-a}
61 | \ExecuteOptionsX{format}
62 | \define@boolkey+{acmart.cls}[@ACM@]{screen}[true]{%
63 | \if@ACM@screen
64 | \PackageInfo{\@classname}{Using screen mode}%
65 | \else
66 | \PackageInfo{\@classname}{Not using screen mode}%
67 | \fi}{\PackageError{\@classname}{Option screen can be either true or
68 | false}}
69 | \ExecuteOptionsX{screen=false}
70 | \define@boolkey+{acmart.cls}[@ACM@]{review}[true]{%
71 | \if@ACM@review
72 | \PackageInfo{\@classname}{Using review mode}%
73 | \else
74 | \PackageInfo{\@classname}{Not using review mode}%
75 | \fi}{\PackageError{\@classname}{Option review can be either true or
76 | false}}
77 | \ExecuteOptionsX{review=false}
78 | \define@boolkey+{acmart.cls}[@ACM@]{authorversion}[true]{%
79 | \if@ACM@authorversion
80 | \PackageInfo{\@classname}{Using authorversion mode}%
81 | \else
82 | \PackageInfo{\@classname}{Not using authorversion mode}%
83 | \fi}{\PackageError{\@classname}{Option authorversion can be either true or
84 | false}}
85 | \ExecuteOptionsX{authorversion=false}
86 | \newif\if@ACM@natbib@override
87 | \@ACM@natbib@overridefalse
88 | \define@boolkey+{acmart.cls}[@ACM@]{natbib}[true]{%
89 | \@ACM@natbib@overridetrue
90 | \if@ACM@natbib
91 | \PackageInfo{\@classname}{Explicitly selecting natbib mode}%
92 | \else
93 | \PackageInfo{\@classname}{Explicitly deselecting natbib mode}%
94 | \fi}{\PackageError{\@classname}{Option natbib can be either true or
95 | false}}
96 | \define@boolkey+{acmart.cls}[@ACM@]{anonymous}[true]{%
97 | \if@ACM@anonymous
98 | \PackageInfo{\@classname}{Using anonymous mode}%
99 | \else
100 | \PackageInfo{\@classname}{Not using anonymous mode}%
101 | \fi}{\PackageError{\@classname}{Option anonymous can be either true or
102 | false}}
103 | \ExecuteOptionsX{anonymous=false}
104 | \def\ACM@fontsize{}
105 | \DeclareOptionX{9pt}{\edef\ACM@fontsize{\CurrentOption}}
106 | \DeclareOptionX{10pt}{\edef\ACM@fontsize{\CurrentOption}}
107 | \DeclareOptionX{11pt}{\edef\ACM@fontsize{\CurrentOption}}
108 | \DeclareOptionX{12pt}{\edef\ACM@fontsize{\CurrentOption}}
109 | \DeclareOptionX{draft}{\PassOptionsToClass{\CurrentOption}{amsart}}
110 | \DeclareOptionX{*}{\PassOptionsToClass{\CurrentOption}{amsart}}
111 | \ProcessOptionsX
112 | \ClassInfo{\@classname}{Using format \ACM@format, number \ACM@format@nr}
113 | \newif\if@ACM@manuscript
114 | \newif\if@ACM@journal
115 | \newif\if@ACM@sigchiamode
116 | \ifnum\ACM@format@nr=0\relax
117 | \@ACM@manuscripttrue
118 | \else
119 | \@ACM@manuscriptfalse
120 | \fi
121 | \@ACM@sigchiamodefalse
122 | \ifcase\ACM@format@nr
123 | \relax % manuscript
124 | \@ACM@journaltrue
125 | \or % acmsmall
126 | \@ACM@journaltrue
127 | \or % acmlarge
128 | \@ACM@journaltrue
129 | \or % acmtog
130 | \@ACM@journaltrue
131 | \or % sigconf
132 | \@ACM@journalfalse
133 | \or % siggraph
134 | \@ACM@journalfalse
135 | \or % sigplan
136 | \@ACM@journalfalse
137 | \or % sigchi
138 | \@ACM@journalfalse
139 | \or % sigchi-a
140 | \@ACM@journalfalse
141 | \@ACM@sigchiamodetrue
142 | \fi
143 | \if@ACM@natbib@override\else
144 | \@ACM@natbibtrue
145 | \fi
146 | \ifx\ACM@fontsize\@empty
147 | \ifcase\ACM@format@nr
148 | \relax % manuscript
149 | \def\ACM@fontsize{9pt}%
150 | \or % acmsmall
151 | \def\ACM@fontsize{10pt}%
152 | \or % acmlarge
153 | \def\ACM@fontsize{10pt}%
154 | \or % acmtog
155 | \def\ACM@fontsize{9pt}%
156 | \or % sigconf
157 | \def\ACM@fontsize{9pt}%
158 | \or % siggraph
159 | \def\ACM@fontsize{9pt}%
160 | \or % sigplan
161 | \def\ACM@fontsize{9pt}%
162 | \or % sigchi
163 | \def\ACM@fontsize{10pt}%
164 | \or % sigchi-a
165 | \def\ACM@fontsize{10pt}%
166 | \fi
167 | \fi
168 | \ClassInfo{\@classname}{Using fontsize \ACM@fontsize}
169 | \LoadClass[\ACM@fontsize, reqno]{amsart}
170 | \RequirePackage{microtype}
171 | \RequirePackage{totpages}
172 | \RequirePackage{environ}
173 | \if@ACM@manuscript
174 | \RequirePackage{setspace}
175 | \onehalfspacing
176 | \fi
177 | \if@ACM@natbib
178 | \RequirePackage{natbib}
179 | \renewcommand{\bibsection}{%
180 | \section*{\refname}%
181 | \phantomsection\addcontentsline{toc}{section}{\refname}%
182 | }
183 | \renewcommand{\bibfont}{\bibliofont}
184 | \renewcommand\setcitestyle[1]{
185 | \@for\@tempa:=#1\do
186 | {\def\@tempb{round}\ifx\@tempa\@tempb
187 | \renewcommand\NAT@open{(}\renewcommand\NAT@close{)}\fi
188 | \def\@tempb{square}\ifx\@tempa\@tempb
189 | \renewcommand\NAT@open{[}\renewcommand\NAT@close{]}\fi
190 | \def\@tempb{angle}\ifx\@tempa\@tempb
191 | \renewcommand\NAT@open{$<$}\renewcommand\NAT@close{$>$}\fi
192 | \def\@tempb{curly}\ifx\@tempa\@tempb
193 | \renewcommand\NAT@open{\{}\renewcommand\NAT@close{\}}\fi
194 | \def\@tempb{semicolon}\ifx\@tempa\@tempb
195 | \renewcommand\NAT@sep{;}\fi
196 | \def\@tempb{colon}\ifx\@tempa\@tempb
197 | \renewcommand\NAT@sep{;}\fi
198 | \def\@tempb{comma}\ifx\@tempa\@tempb
199 | \renewcommand\NAT@sep{,}\fi
200 | \def\@tempb{authoryear}\ifx\@tempa\@tempb
201 | \NAT@numbersfalse\fi
202 | \def\@tempb{numbers}\ifx\@tempa\@tempb
203 | \NAT@numberstrue\NAT@superfalse\fi
204 | \def\@tempb{super}\ifx\@tempa\@tempb
205 | \NAT@numberstrue\NAT@supertrue\fi
206 | \def\@tempb{nobibstyle}\ifx\@tempa\@tempb
207 | \let\bibstyle=\@gobble\fi
208 | \def\@tempb{bibstyle}\ifx\@tempa\@tempb
209 | \let\bibstyle=\@citestyle\fi
210 | \def\@tempb{sort}\ifx\@tempa\@tempb
211 | \def\NAT@sort{\@ne}\fi
212 | \def\@tempb{nosort}\ifx\@tempa\@tempb
213 | \def\NAT@sort{\z@}\fi
214 | \def\@tempb{compress}\ifx\@tempa\@tempb
215 | \def\NAT@cmprs{\@ne}\fi
216 | \def\@tempb{nocompress}\ifx\@tempa\@tempb
217 | \def\NAT@cmprs{\@z}\fi
218 | \def\@tempb{sort&compress}\ifx\@tempa\@tempb
219 | \def\NAT@sort{\@ne}\def\NAT@cmprs{\@ne}\fi
220 | \def\@tempb{mcite}\ifx\@tempa\@tempb
221 | \let\NAT@merge\@ne\fi
222 | \def\@tempb{merge}\ifx\@tempa\@tempb
223 | \@ifnum{\NAT@merge<\tw@}{\let\NAT@merge\tw@}{}\fi
224 | \def\@tempb{elide}\ifx\@tempa\@tempb
225 | \@ifnum{\NAT@merge<\thr@@}{\let\NAT@merge\thr@@}{}\fi
226 | \def\@tempb{longnamesfirst}\ifx\@tempa\@tempb
227 | \NAT@longnamestrue\fi
228 | \def\@tempb{nonamebreak}\ifx\@tempa\@tempb
229 | \def\NAT@nmfmt#1{\mbox{\NAT@up#1}}\fi
230 | \expandafter\NAT@find@eq\@tempa=\relax\@nil
231 | \if\@tempc\relax\else
232 | \expandafter\NAT@rem@eq\@tempc
233 | \def\@tempb{open}\ifx\@tempa\@tempb
234 | \xdef\NAT@open{\@tempc}\fi
235 | \def\@tempb{close}\ifx\@tempa\@tempb
236 | \xdef\NAT@close{\@tempc}\fi
237 | \def\@tempb{aysep}\ifx\@tempa\@tempb
238 | \xdef\NAT@aysep{\@tempc}\fi
239 | \def\@tempb{yysep}\ifx\@tempa\@tempb
240 | \xdef\NAT@yrsep{\@tempc}\fi
241 | \def\@tempb{notesep}\ifx\@tempa\@tempb
242 | \xdef\NAT@cmt{\@tempc}\fi
243 | \def\@tempb{citesep}\ifx\@tempa\@tempb
244 | \xdef\NAT@sep{\@tempc}\fi
245 | \fi
246 | }%
247 | \NAT@@setcites
248 | }
249 | \renewcommand\citestyle[1]{%
250 | \ifcsname bibstyle@#1\endcsname%
251 | \csname bibstyle@#1\endcsname\let\bibstyle\@gobble%
252 | \else%
253 | \@latex@error{Undefined `#1' citestyle}%
254 | \fi
255 | }%
256 | \fi
257 | \newcommand{\bibstyle@acmauthoryear}{%
258 | \setcitestyle{%
259 | authoryear,%
260 | open={(},close={)},citesep={;},%
261 | aysep={},yysep={,},%
262 | notesep={, }}}
263 | \newcommand{\bibstyle@acmnumeric}{%
264 | \setcitestyle{%
265 | numbers,sort&compress,%
266 | open={[},close={]},citesep={,},%
267 | notesep={, }}}
268 | \citestyle{acmnumeric}
269 | \def\@startsection#1#2#3#4#5#6{%
270 | \if@noskipsec \leavevmode \fi
271 | \par
272 | \@tempskipa #4\relax
273 | \@afterindenttrue
274 | \ifdim \@tempskipa <\z@
275 | \@tempskipa -\@tempskipa \@afterindentfalse
276 | \fi
277 | \if@nobreak
278 | \everypar{}%
279 | \else
280 | \addpenalty\@secpenalty\addvspace\@tempskipa
281 | \fi
282 | \@ifstar
283 | {\@ssect{#3}{#4}{#5}{#6}}%
284 | {\@dblarg{\@sect{#1}{#2}{#3}{#4}{#5}{#6}}}}
285 | \def\@sect#1#2#3#4#5#6[#7]#8{%
286 | \ifnum #2>\c@secnumdepth
287 | \let\@svsec\@empty
288 | \else
289 | \refstepcounter{#1}%
290 | \protected@edef\@svsec{\@seccntformat{#1}\relax}%
291 | \fi
292 | \@tempskipa #5\relax
293 | \ifdim \@tempskipa>\z@
294 | \begingroup
295 | #6{%
296 | \@hangfrom{\hskip #3\relax\@svsec}%
297 | \interlinepenalty \@M #8\@@par}%
298 | \endgroup
299 | \csname #1mark\endcsname{#7}%
300 | \addcontentsline{toc}{#1}{%
301 | \ifnum #2>\c@secnumdepth \else
302 | \protect\numberline{\csname the#1\endcsname}%
303 | \fi
304 | #7}%
305 | \else
306 | \def\@svsechd{%
307 | #6{\hskip #3\relax
308 | \@svsec #8}%
309 | \csname #1mark\endcsname{#7}%
310 | \addcontentsline{toc}{#1}{%
311 | \ifnum #2>\c@secnumdepth \else
312 | \protect\numberline{\csname the#1\endcsname}%
313 | \fi
314 | #7}}%
315 | \fi
316 | \@xsect{#5}}
317 | \def\@xsect#1{%
318 | \@tempskipa #1\relax
319 | \ifdim \@tempskipa>\z@
320 | \par \nobreak
321 | \vskip \@tempskipa
322 | \@afterheading
323 | \else
324 | \@nobreakfalse
325 | \global\@noskipsectrue
326 | \everypar{%
327 | \if@noskipsec
328 | \global\@noskipsecfalse
329 | {\setbox\z@\lastbox}%
330 | \clubpenalty\@M
331 | \begingroup \@svsechd \endgroup
332 | \unskip
333 | \@tempskipa #1\relax
334 | \hskip -\@tempskipa
335 | \else
336 | \clubpenalty \@clubpenalty
337 | \everypar{}%
338 | \fi}%
339 | \fi
340 | \ignorespaces}
341 | \def\@seccntformat#1{\csname the#1\endcsname\quad}
342 | \def\@ssect#1#2#3#4#5{%
343 | \@tempskipa #3\relax
344 | \ifdim \@tempskipa>\z@
345 | \begingroup
346 | #4{%
347 | \@hangfrom{\hskip #1}%
348 | \interlinepenalty \@M #5\@@par}%
349 | \endgroup
350 | \else
351 | \def\@svsechd{#4{\hskip #1\relax #5}}%
352 | \fi
353 | \@xsect{#3}}
354 | \let\@footnotemark@nolink\@footnotemark
355 | \let\@footnotetext@nolink\@footnotetext
356 | \RequirePackage[bookmarksnumbered]{hyperref}
357 | \urlstyle{rm}
358 | \ifcase\ACM@format@nr
359 | \relax % manuscript
360 | \or % acmsmall
361 | \or % acmlarge
362 | \or % acmtog
363 | \or % sigconf
364 | \or % siggraph
365 | \or % sigplan
366 | \urlstyle{sf}
367 | \or % sigchi
368 | \or % sigchi-a
369 | \urlstyle{sf}
370 | \fi
371 | \if@ACM@screen
372 | \hypersetup{colorlinks,
373 | linkcolor=ACMRed,
374 | citecolor=ACMPurple,
375 | urlcolor=ACMDarkBlue,
376 | filecolor=ACMDarkBlue}
377 | \else
378 | \hypersetup{hidelinks}
379 | \fi
380 | \if@ACM@natbib
381 | \let\citeN\cite
382 | \let\cite\citep
383 | \let\citeANP\citeauthor
384 | \let\citeNN\citeyearpar
385 | \let\citeyearNP\citeyear
386 | \let\citeyear\citeyearpar
387 | \let\citeNP\citealt
388 | \def\shortcite#1{\citeyear{#1}}
389 | \DeclareRobustCommand\citeA
390 | {\begingroup\NAT@swafalse
391 | \let\NAT@ctype\@ne\NAT@partrue\NAT@fullfalse\NAT@open\NAT@citetp}%
392 | \providecommand\newblock{}%
393 | \else
394 | \providecommand\citename[1]{#1}
395 | \fi
396 | \def\bibliographystyle#1{%
397 | \ifx\@begindocumenthook\@undefined\else
398 | \expandafter\AtBeginDocument
399 | \fi
400 | {\if@filesw
401 | \immediate\write\@auxout{\string\bibstyle{#1}}%
402 | \fi}}
403 | \RequirePackage{graphicx, xcolor}
404 | \definecolor[named]{ACMBlue}{cmyk}{1,0.1,0,0.1}
405 | \definecolor[named]{ACMYellow}{cmyk}{0,0.16,1,0}
406 | \definecolor[named]{ACMOrange}{cmyk}{0,0.42,1,0.01}
407 | \definecolor[named]{ACMRed}{cmyk}{0,0.90,0.86,0}
408 | \definecolor[named]{ACMLightBlue}{cmyk}{0.49,0.01,0,0}
409 | \definecolor[named]{ACMGreen}{cmyk}{0.20,0,1,0.19}
410 | \definecolor[named]{ACMPurple}{cmyk}{0.55,1,0,0.15}
411 | \definecolor[named]{ACMDarkBlue}{cmyk}{1,0.58,0,0.21}
412 | \RequirePackage{geometry}
413 | \ifcase\ACM@format@nr
414 | \relax % manuscript
415 | \geometry{letterpaper,head=1pc}%
416 | \or % acmsmall
417 | \geometry{twoside=true,
418 | includeheadfoot, head=1pc, foot=2pc,
419 | paperwidth=6.75in, paperheight=10in,
420 | top=58pt, bottom=44pt, inner=46pt, outer=46pt
421 | }%
422 | \or % acmlarge
423 | \geometry{twoside=true, head=1pc, foot=2pc,
424 | paperwidth=8.5in, paperheight=11in,
425 | includeheadfoot,
426 | top=78pt, bottom=114pt, inner=81pt, outer=81pt
427 | }%
428 | \or % acmtog
429 | \geometry{twoside=true, head=1pc, foot=2pc,
430 | paperwidth=8.5in, paperheight=11in,
431 | includeheadfoot, columnsep=24pt,
432 | top=52pt, bottom=75pt, inner=52pt, outer=52pt
433 | }%
434 | \or % sigconf
435 | \geometry{twoside=true, head=1pc,
436 | paperwidth=8.5in, paperheight=11in,
437 | includeheadfoot, columnsep=2pc,
438 | top=57pt, bottom=73pt, inner=54pt, outer=54pt
439 | }%
440 | \or % siggraph
441 | \geometry{twoside=true, head=1pc,
442 | paperwidth=8.5in, paperheight=11in,
443 | includeheadfoot, columnsep=2pc,
444 | top=57pt, bottom=73pt, inner=54pt, outer=54pt
445 | }%
446 | \or % sigplan
447 | \geometry{twoside=true, head=1pc,
448 | paperwidth=8.5in, paperheight=11in,
449 | includeheadfoot=false, columnsep=2pc,
450 | top=1in, bottom=1in, inner=0.75in, outer=0.75in
451 | }%
452 | \or % sigchi
453 | \geometry{twoside=true, head=1pc,
454 | paperwidth=8.5in, paperheight=11in,
455 | includeheadfoot, columnsep=2pc,
456 | top=66pt, bottom=73pt, inner=54pt, outer=54pt
457 | }%
458 | \or % sigchi-a
459 | \geometry{twoside=false, head=1pc,
460 | paperwidth=11in, paperheight=8.5in,
461 | includeheadfoot, marginparsep=72pt,
462 | marginparwidth=170pt, columnsep=20pt,
463 | top=72pt, bottom=72pt, left=314pt, right=72pt
464 | }%
465 | \@mparswitchfalse
466 | \reversemarginpar
467 | \fi
468 | \setlength\parindent{10\p@}
469 | \setlength\parskip{\z@}
470 | \ifcase\ACM@format@nr
471 | \relax % manuscript
472 | \or % acmsmall
473 | \or % acmlarge
474 | \or % acmtog
475 | \setlength\parindent{9\p@}%
476 | \or % sigconf
477 | \or % siggraph
478 | \or % sigplan
479 | \or % sigchi
480 | \or % sigchi-a
481 | \fi
482 | \def\copyrightpermissionfootnoterule{\kern-3\p@
483 | \hrule \@width \columnwidth \kern 2.6\p@}
484 | \RequirePackage{manyfoot}
485 | \SelectFootnoteRule[2]{copyrightpermission}
486 | \DeclareNewFootnote{copyrightpermission}
487 | \def\footnoterule{\kern-3\p@
488 | \hrule \@width 4pc \kern 2.6\p@}
489 | \def\endminipage{%
490 | \par
491 | \unskip
492 | \ifvoid\@mpfootins\else
493 | \vskip\skip\@mpfootins
494 | \normalcolor
495 | \unvbox\@mpfootins
496 | \fi
497 | \@minipagefalse %% added 24 May 89
498 | \color@endgroup
499 | \egroup
500 | \expandafter\@iiiparbox\@mpargs{\unvbox\@tempboxa}}
501 | \def\@makefntext{\noindent\@makefnmark}
502 | \if@ACM@sigchiamode
503 | \long\def\@footnotetext#1{\marginpar{%
504 | \reset@font\small
505 | \interlinepenalty\interfootnotelinepenalty
506 | \protected@edef\@currentlabel{%
507 | \csname p@footnote\endcsname\@thefnmark
508 | }%
509 | \color@begingroup
510 | \@makefntext{%
511 | \rule\z@\footnotesep\ignorespaces#1\@finalstrut\strutbox}%
512 | \color@endgroup}}%
513 | \fi
514 | \long\def\@mpfootnotetext#1{%
515 | \global\setbox\@mpfootins\vbox{%
516 | \unvbox\@mpfootins
517 | \reset@font\footnotesize
518 | \hsize\columnwidth
519 | \@parboxrestore
520 | \protected@edef\@currentlabel
521 | {\csname p@mpfootnote\endcsname\@thefnmark}%
522 | \color@begingroup\centering
523 | \@makefntext{%
524 | \rule\z@\footnotesep\ignorespaces#1\@finalstrut\strutbox}%
525 | \color@endgroup}}
526 | \def\@makefnmark{\hbox{\@textsuperscript{\normalfont\@thefnmark}}}
527 | \newif\if@ACM@newfonts
528 | \@ACM@newfontstrue
529 | \IfFileExists{libertine.sty}{}{\ClassWarning{\@classname}{You do not
530 | have libertine package installed. Please upgrade your
531 | TeX}\@ACM@newfontsfalse}
532 | \IfFileExists{zi4.sty}{}{\ClassWarning{\@classname}{You do not
533 | have zi4 package installed. Please upgrade your TeX}\@ACM@newfontsfalse}
534 | \IfFileExists{newtxmath.sty}{}{\ClassWarning{\@classname}{You do not
535 | have newtxmath package installed. Please upgrade your
536 | TeX}\@ACM@newfontsfalse}
537 | \if@ACM@newfonts
538 | \RequirePackage[tt=false]{libertine}
539 | \RequirePackage[varqu]{zi4}
540 | \RequirePackage[libertine]{newtxmath}
541 | \else
542 | \RequirePackage{textcomp}
543 | \fi
544 | \if@ACM@sigchiamode
545 | \renewcommand{\familydefault}{\sfdefault}
546 | \fi
547 | \RequirePackage{caption, float}
548 | \captionsetup[table]{position=top}
549 | \if@ACM@journal
550 | \captionsetup{labelfont={sf, small},
551 | textfont={sf, small}, margin=\z@}
552 | \captionsetup[figure]{name={Fig.}}
553 | \else
554 | \captionsetup{labelfont={bf},
555 | textfont={bf}, labelsep=colon, margin=\z@}
556 | \ifcase\ACM@format@nr
557 | \relax % manuscript
558 | \or % acmsmall
559 | \or % acmlarge
560 | \or % acmtog
561 | \or % sigconf
562 | \or % siggraph
563 | \captionsetup{textfont={it}}
564 | \or % sigplan
565 | \captionsetup{labelfont={bf},
566 | textfont={normalfont}, labelsep=period, margin=\z@}
567 | \or % sigchi
568 | \captionsetup[figure]{labelfont={bf, small},
569 | textfont={bf, small}}
570 | \or % sigchi-a
571 | \captionsetup[figure]{labelfont={bf, small},
572 | textfont={bf, small}}
573 | \fi
574 | \fi
575 | \newfloat{sidebar}{}{sbar}
576 | \floatname{sidebar}{Sidebar}
577 | \renewenvironment{sidebar}{\Collect@Body\@sidebar}{}
578 | \long\def\@sidebar#1{\bgroup\captionsetup{type=sidebar}%
579 | \marginpar{\small#1}\egroup}
580 | \newenvironment{marginfigure}{\Collect@Body\@marginfigure}{}
581 | \long\def\@marginfigure#1{\bgroup\captionsetup{type=figure}%
582 | \marginpar{\centering\small#1}\egroup}
583 | \newenvironment{margintable}{\Collect@Body\@margintable}{}
584 | \long\def\@margintable#1{\bgroup\captionsetup{type=table}%
585 | \marginpar{\centering\small#1}\egroup}
586 | \newdimen\fulltextwidth
587 | \fulltextwidth=\dimexpr(\textwidth+\marginparwidth+\marginparsep)
588 | \if@ACM@sigchiamode
589 | \def\@dblfloat{\bgroup\columnwidth=\fulltextwidth
590 | \let\@endfloatbox\@endwidefloatbox
591 | \def\@fpsadddefault{\def\@fps{tp}}%
592 | \@float}
593 | \fi
594 | \if@ACM@sigchiamode
595 | \def\end@dblfloat{%
596 | \end@float\egroup}
597 | \fi
598 | \def\@endwidefloatbox{%
599 | \par\vskip\z@skip
600 | \@minipagefalse
601 | \outer@nobreak
602 | \egroup
603 | \color@endbox
604 | \global\setbox\@currbox=\vbox{\moveleft
605 | \dimexpr(\fulltextwidth-\textwidth)\box\@currbox}%
606 | \wd\@currbox=\textwidth
607 | }
608 | \ifcase\ACM@format@nr
609 | \relax % manuscript
610 | \or % acmsmall
611 | \or % acmlarge
612 | \or % acmtog
613 | \or % sigconf
614 | \or % siggraph
615 | \or % sigplan
616 | \def\labelenumi{\theenumi.}
617 | \def\labelenumii{\theenumii.}
618 | \def\labelenumiii{\theenumiii.}
619 | \def\labelenumiv{\theenumiv.}
620 | \or % sigchi
621 | \or % sigchi-a
622 | \fi
623 | \renewcommand{\descriptionlabel}[1]{\hspace\labelsep \upshape\bfseries #1}
624 | \renewenvironment{description}{\list{}{%
625 | \itemindent-12\p@
626 | \labelwidth\z@ \let\makelabel\descriptionlabel}%
627 | }{
628 | \endlist
629 | }
630 | \let\enddescription=\endlist % for efficiency
631 | \define@choicekey*+{ACM}{acmJournal}[\@journalCode\@journalCode@nr]{%
632 | CIE,%
633 | CSUR,%
634 | IMWUT,%
635 | JACM,%
636 | JDIQ,%
637 | JEA,%
638 | JERIC,%
639 | JETC,%
640 | JOCCH,%
641 | PACMPL,%
642 | TAAS,%
643 | TACCESS,%
644 | TACO,%
645 | TALG,%
646 | TALLIP,%
647 | TAP,%
648 | TCPS,%
649 | TEAC,%
650 | TECS,%
651 | TIIS,%
652 | TISSEC,%
653 | TIST,%
654 | TKDD,%
655 | TMIS,%
656 | TOCE,%
657 | TOCHI,%
658 | TOCL,%
659 | TOCS,%
660 | TOCT,%
661 | TODAES,%
662 | TODS,%
663 | TOG,%
664 | TOIS,%
665 | TOIT,%
666 | TOMACS,%
667 | TOMM,%
668 | TOMPECS,%
669 | TOMS,%
670 | TOPC,%
671 | TOPS,%
672 | TOPLAS,%
673 | TOS,%
674 | TOSEM,%
675 | TOSN,%
676 | TRETS,%
677 | TSAS,%
678 | TSC,%
679 | TSLP,%
680 | TWEB%
681 | }{%
682 | \ifcase\@journalCode@nr
683 | \relax % CIE
684 | \def\@journalName{ACM Computers in Entertainment}%
685 | \def\@journalNameShort{ACM Comput. Entertain.}%
686 | \def\@permissionCodeOne{1544-3574}%
687 | \or % CSUR
688 | \def\@journalName{ACM Computing Surveys}%
689 | \def\@journalNameShort{ACM Comput. Surv.}%
690 | \def\@permissionCodeOne{0360-0300}%
691 | \or % IMWUT
692 | \def\@journalName{PACM on Interactive, Mobile, Wearable and
693 | Ubiquitous Technologies}%
694 | \def\@journalNameShort{PACM Interact. Mob. Wearable Ubiquitous Technol.}%
695 | \def\@permissionCodeOne{2474-9567}%
696 | \or % JACM
697 | \def\@journalName{Journal of the ACM}%
698 | \def\@journalNameShort{J. ACM}%
699 | \def\@permissionCodeOne{0004-5411}%
700 | \or % JDIQ
701 | \def\@journalName{ACM Journal of Data and Information Quality}%
702 | \def\@journalNameShort{ACM J. Data Inform. Quality}%
703 | \def\@permissionCodeOne{1936-1955}%
704 | \or % JEA
705 | \def\@journalName{ACM Journal of Experimental Algorithmics}%
706 | \def\@journalNameShort{ACM J. Exp. Algor.}%
707 | \def\@permissionCodeOne{1084-6654}%
708 | \or % JERIC
709 | \def\@journalName{ACM Journal of Educational Resources in Computing}%
710 | \def\@journalNameShort{ACM J. Edu. Resources in Comput.}%
711 | \def\@permissionCodeOne{1073-0516}%
712 | \or % JETC
713 | \def\@journalName{ACM Journal on Emerging Technologies in Computing Systems}%
714 | \def\@journalNameShort{ACM J. Emerg. Technol. Comput. Syst.}%
715 | \def\@permissionCodeOne{1550-4832}%
716 | \or % JOCCH
717 | \def\@journalName{ACM Journal on Computing and Cultural Heritage}%
718 | \def\@journalName{ACM J. Comput. Cult. Herit.}%
719 | \or % PACMPL
720 | \def\@journalName{PACM on Programming Languages}%
721 | \def\@journalName{PACM Progr. Lang.}%
722 | \def\@permissionCodeOne{2475-1421}%
723 | \or % TAAS
724 | \def\@journalName{ACM Transactions on Autonomous and Adaptive Systems}%
725 | \def\@journalNameShort{ACM Trans. Autonom. Adapt. Syst.}%
726 | \def\@permissionCodeOne{1556-4665}%
727 | \or % TACCESS
728 | \def\@journalName{ACM Transactions on Accessible Computing}%
729 | \def\@journalNameShort{ACM Trans. Access. Comput.}%
730 | \def\@permissionCodeOne{1936-7228}%
731 | \or % TACO
732 | \def\@journalName{ACM Transactions on Architecture and Code Optimization}%
733 | \or % TALG
734 | \def\@journalName{ACM Transactions on Algorithms}%
735 | \def\@journalNameShort{ACM Trans. Algor.}%
736 | \def\@permissionCodeOne{1549-6325}%
737 | \or % TALLIP
738 | \def\@journalName{ACM Transactions on Asian and Low-Resource Language Information Processing}%
739 | \def\@journalNameShort{ACM Trans. Asian Low-Resour. Lang. Inf. Process.}%
740 | \def\@permissionCodeOne{2375-4699}%
741 | \or % TAP
742 | \def\@journalName{ACM Transactions on Applied Perception}%
743 | \or % TCPS
744 | \def\@journalName{ACM Transactions on Cyber-Physical Systems}%
745 | \or % TEAC
746 | \def\@journalName{ACM Transactions on Economics and Computation}%
747 | \or % TECS
748 | \def\@journalName{ACM Transactions on Embedded Computing Systems}%
749 | \def\@journalNameShort{ACM Trans. Embedd. Comput. Syst.}%
750 | \def\@permissionCodeOne{1539-9087}%
751 | \or % TIIS
752 | \def\@journalName{ACM Transactions on Interactive Intelligent Systems}%
753 | \def\@journalNameShort{ACM Trans. Interact. Intell. Syst.}%
754 | \def\@permissionCodeOne{2160-6455}%
755 | \or % TISSEC
756 | \def\@journalName{ACM Transactions on Information and System Security}%
757 | \def\@journalNameShort{ACM Trans. Info. Syst. Sec.}%
758 | \def\@permissionCodeOne{1094-9224}%
759 | \or % TIST
760 | \def\@journalName{ACM Transactions on Intelligent Systems and Technology}%
761 | \def\@journalNameShort{ACM Trans. Intell. Syst. Technol.}%
762 | \def\@permissionCodeOne{2157-6904}%
763 | \or % TKDD
764 | \def\@journalName{ACM Transactions on Knowledge Discovery from Data}%
765 | \def\@journalNameShort{ACM Trans. Knowl. Discov. Data.}%
766 | \def\@permissionCodeOne{1556-4681}%
767 | \or % TMIS
768 | \def\@journalName{ACM Transactions on Management Information Systems}%
769 | \def\@journalNameShort{ACM Trans. Manag. Inform. Syst.}%
770 | \def\@permissionCodeOne{2158-656X}%
771 | \or % TOCE
772 | \def\@journalName{ACM Transactions on Computing Education}%
773 | \def\@journalNameShort{ACM Trans. Comput. Educ.}%
774 | \def\@permissionCodeOne{1946-6226}%
775 | \or % TOCHI
776 | \def\@journalName{ACM Transactions on Computer-Human Interaction}%
777 | \def\@journalNameShort{ACM Trans. Comput.-Hum. Interact.}%
778 | \def\@permissionCodeOne{1073-0516}%
779 | \or % TOCL
780 | \def\@journalName{ACM Transactions on Computational Logic}%
781 | \def\@journalNameShort{ACM Trans. Comput. Logic}%
782 | \def\@permissionCodeOne{1529-3785}%
783 | \or % TOCS
784 | \def\@journalName{ACM Transactions on Computer Systems}%
785 | \def\@journalNameShort{ACM Trans. Comput. Syst.}%
786 | \def\@permissionCodeOne{0734-2071}%
787 | \or % TOCT
788 | \def\@journalName{ACM Transactions on Computation Theory}%
789 | \def\@journalNameShort{ACM Trans. Comput. Theory}%
790 | \def\@permissionCodeOne{1942-3454}%
791 | \or % TODAES
792 | \def\@journalName{ACM Transactions on Design Automation of Electronic Systems}%
793 | \def\@journalNameShort{ACM Trans. Des. Autom. Electron. Syst.}%
794 | \def\@permissionCodeOne{1084-4309}%
795 | \or % TODS
796 | \def\@journalName{ACM Transactions on Database Systems}%
797 | \def\@journalNameShort{ACM Trans. Datab. Syst.}%
798 | \def\@permissionCodeOne{0362-5915}%
799 | \or % TOG
800 | \def\@journalName{ACM Transactions on Graphics}%
801 | \def\@journalNameShort{ACM Trans. Graph.}%
802 | \def\@permissionCodeOne{0730-0301}
803 | \or % TOIS
804 | \def\@journalName{ACM Transactions on Information Systems}%
805 | \def\@journalName{ACM Transactions on Information Systems}%
806 | \def\@permissionCodeOne{1046-8188}%
807 | \or % TOIT
808 | \def\@journalName{ACM Transactions on Internet Technology}%
809 | \def\@journalNameShort{ACM Trans. Internet Technol.}%
810 | \def\@permissionCodeOne{1533-5399}%
811 | \or % TOMACS
812 | \def\@journalName{ACM Transactions on Modeling and Computer Simulation}%
813 | \def\@journalName{ACM Transactions on Modeling and Computer Simulation}%
814 | \def\@journalNameShort{ACM Trans. Model. Comput. Simul.}%
815 | \or % TOMM
816 | \def\@journalName{ACM Transactions on Multimedia Computing, Communications and Applications}%
817 | \def\@journalNameShort{ACM Trans. Multimedia Comput. Commun. Appl.}%
818 | \def\@permissionCodeOne{1551-6857}%
819 | \def\@permissionCodeTwo{0100}%
820 | \or % TOMPECS
821 | \def\@journalName{ACM Transactions on Modeling and Performance Evaluation of Computing Systems}%
822 | \def\@journalNameShort{ACM Trans. Model. Perform. Eval. Comput. Syst.}%
823 | \def\@permissionCodeOne{2376-3639}%
824 | \or % TOMS
825 | \def\@journalName{ACM Transactions on Mathematical Software}%
826 | \def\@journalNameShort{ACM Trans. Math. Softw.}%
827 | \def\@permissionCodeOne{0098-3500}%
828 | \or % TOPC
829 | \def\@journalName{ACM Transactions on Parallel Computing}%
830 | \def\@journalNameShort{ACM Trans. Parallel Comput.}%
831 | \def\@permissionCodeOne{1539-9087}%
832 | \or % TOPS
833 | \def\@journalName{ACM Transactions on Privacy and Security}%
834 | \def\@journalNameShort{ACM Trans. Priv. Sec.}%
835 | \def\@permissionCodeOne{2471-2566}%
836 | \or % TOPLAS
837 | \def\@journalName{ACM Transactions on Programming Languages and Systems}%
838 | \def\@journalNameShort{ACM Trans. Program. Lang. Syst.}%
839 | \def\@permissionCodeOne{0164-0925}%
840 | \or % TOS
841 | \def\@journalName{ACM Transactions on Storage}%
842 | \def\@journalNameShort{ACM Trans. Storage}%
843 | \def\@permissionCodeOne{1553-3077}%
844 | \or % TOSEM
845 | \def\@journalName{ACM Transactions on Software Engineering and Methodology}%
846 | \def\@journalNameShort{ACM Trans. Softw. Eng. Methodol.}%
847 | \def\@permissionCodeOne{1049-331X}%
848 | \or % TOSN
849 | \def\@journalName{ACM Transactions on Sensor Networks}%
850 | \def\@journalNameShort{ACM Trans. Sensor Netw.}%
851 | \def\@permissionCodeOne{1550-4859}%
852 | \or % TRETS
853 | \def\@journalName{ACM Transactions on Reconfigurable Technology and Systems}%
854 | \def\@journalNameShort{ACM Trans. Reconfig. Technol. Syst.}%
855 | \def\@permissionCodeOne{1936-7406}%
856 | \or % TSAS
857 | \def\@journalName{ACM Transactions on Spatial Algorithms and Systems}%
858 | \def\@journalNameShort{ACM Trans. Spatial Algorithms Syst.}%
859 | \def\@permissionCodeOne{2374-0353}%
860 | \or % TSC
861 | \def\@journalName{ACM Transactions on Social Computing}%
862 | \def\@journalNameShort{ACM Trans. Soc. Comput.}%
863 | \def\@permissionCodeOne{2469-7818}%
864 | \or % TSLP
865 | \def\@journalName{ACM Transactions on Speech and Language Processing}%
866 | \def\@journalNameShort{ACM Trans. Speech Lang. Process.}%
867 | \def\@permissionCodeOne{1550-4875}%
868 | \or % TWEB
869 | \def\@journalName{ACM Transactions on the Web}%
870 | \def\@journalNameShort{ACM Trans. Web}%
871 | \def\@permissionCodeOne{1559-1131}%
872 | \fi
873 | \ClassInfo{\@classname}{Using journal code \@journalCode}%
874 | }{%
875 | \ClassError{\@classname}{Incorrect journal #1}%
876 | }%
877 | \def\acmJournal#1{\setkeys{ACM}{acmJournal=#1}}
878 | \def\@journalCode@nr{0}
879 | \def\@journalName{}%
880 | \def\@journalNameShort{\@journalName}%
881 | \def\@permissionCodeOne{XXXX-XXXX}%
882 | \def\@permissionCodeTwo{}%
883 | \newcommand\acmConference[4][]{%
884 | \gdef\acmConference@shortname{#1}%
885 | \gdef\acmConference@name{#2}%
886 | \gdef\acmConference@date{#3}%
887 | \gdef\acmConference@venue{#4}%
888 | \ifx\acmConference@shortname\@empty
889 | \gdef\acmConference@shortname{#2}%
890 | \fi}
891 | \acmConference[Conference'17]{ACM Conference}{July 2017}{Washington,
892 | DC, USA}
893 | \def\subtitle#1{\def\@subtitle{#1}}
894 | \subtitle{}
895 | \newcount\num@authorgroups
896 | \num@authorgroups=0\relax
897 | \newif\if@insideauthorgroup
898 | \@insideauthorgroupfalse
899 | \renewcommand\author[2][]{%
900 | \if@insideauthorgroup\else
901 | \global\advance\num@authorgroups by 1\relax
902 | \global\@insideauthorgrouptrue
903 | \fi
904 | \ifx\addresses\@empty
905 | \if@ACM@anonymous
906 | \gdef\addresses{\@author{Anonymous Author(s)}}%
907 | \gdef\authors{Anonymous Author(s)}%
908 | \else
909 | \gdef\addresses{\@author{#2}}%
910 | \gdef\authors{#2}%
911 | \fi
912 | \else
913 | \if@ACM@anonymous\else
914 | \g@addto@macro\addresses{\and\@author{#2}}%
915 | \g@addto@macro\authors{\and#2}%
916 | \fi
917 | \fi
918 | \if@ACM@anonymous
919 | \ifx\shortauthors\@empty
920 | \gdef\shortauthors{Anon.}%
921 | \fi
922 | \else
923 | \def\@tempa{#1}%
924 | \ifx\@tempa\@empty
925 | \ifx\shortauthors\@empty
926 | \gdef\shortauthors{#2}%
927 | \else
928 | \g@addto@macro\shortauthors{\and#2}%
929 | \fi
930 | \else
931 | \ifx\shortauthors\@empty
932 | \gdef\shortauthors{#1}%
933 | \else
934 | \g@addto@macro\shortauthors{\and#1}%
935 | \fi
936 | \fi
937 | \fi}
938 | \newcommand{\affiliation}[2][]{%
939 | \global\@insideauthorgroupfalse
940 | \if@ACM@anonymous\else
941 | \g@addto@macro\addresses{\affiliation{#1}{#2}}%
942 | \fi}
943 | \renewcommand{\email}[2][]{%
944 | \if@ACM@anonymous\else
945 | \g@addto@macro\addresses{\email{#1}{#2}}%
946 | \fi}
947 | \let\orcid\@gobble
948 | \def\@titlenotes{}
949 | \def\titlenote#1{%
950 | \g@addto@macro\@title{\footnotemark}%
951 | \if@ACM@anonymous
952 | \g@addto@macro\@titlenotes{%
953 | \stepcounter{footnote}\footnotetext{Title note}}%
954 | \else
955 | \g@addto@macro\@titlenotes{\stepcounter{footnote}\footnotetext{#1}}%
956 | \fi}
957 | \def\@subtitlenotes{}
958 | \def\subtitlenote#1{%
959 | \g@addto@macro\@subtitle{\footnotemark}%
960 | \if@ACM@anonymous
961 | \g@addto@macro\@subtitlenotes{%
962 | \stepcounter{footnote}\footnotetext{Subtitle note}}%
963 | \else
964 | \g@addto@macro\@subtitlenotes{%
965 | \stepcounter{footnote}\footnotetext{#1}}%
966 | \fi}
967 | \def\@authornotes{}
968 | \def\authornote#1{%
969 | \if@ACM@anonymous\else
970 | \g@addto@macro\addresses{\@authornotemark}
971 | \g@addto@macro\@authornotes{%
972 | \stepcounter{footnote}\footnotetext{#1}}%
973 | \fi}
974 | \def\acmVolume#1{\def\@acmVolume{#1}}
975 | \acmVolume{1}
976 | \def\acmNumber#1{\def\@acmNumber{#1}}
977 | \acmNumber{1}
978 | \def\acmArticle#1{\def\@acmArticle{#1}}
979 | \acmArticle{1}
980 | \def\acmArticleSeq#1{\def\@acmArticleSeq{#1}}
981 | \acmArticleSeq{\@acmArticle}
982 | \def\acmYear#1{\def\@acmYear{#1}}
983 | \acmYear{2016}
984 | \def\acmMonth#1{\def\@acmMonth{#1}}
985 | \acmMonth{1}
986 | \def\@acmPubDate{\ifcase\@acmMonth\or
987 | January\or February\or March\or April\or May\or June\or
988 | July\or August\or September\or October\or November\or
989 | December\fi~\@acmYear}
990 | \def\acmPrice#1{\def\@acmPrice{#1}}
991 | \acmPrice{15.00}
992 | \def\acmISBN#1{\def\@acmISBN{#1}}
993 | \acmISBN{978-x-xxxx-xxxx-x/YY/MM}
994 | \def\acmDOI#1{\def\@acmDOI{#1}}
995 | \acmDOI{10.1145/nnnnnnn.nnnnnnn}
996 | \newif\if@ACM@badge
997 | \@ACM@badgefalse
998 | \newlength\@ACM@badge@width
999 | \setlength\@ACM@badge@width{5pc}
1000 | \newlength\@ACM@title@width
1001 | \newlength\@ACM@badge@skip
1002 | \setlength\@ACM@badge@skip{1pc}
1003 | \newcommand\acmBadgeR[2][]{\@ACM@badgetrue
1004 | \def\@acmBadgeR@url{#1}%
1005 | \def\@acmBadgeR@image{#2}}
1006 | \def\@acmBadgeR@url{}
1007 | \def\@acmBadgeR@image{}
1008 | \newcommand\acmBadgeL[2][]{\@ACM@badgetrue
1009 | \def\@acmBadgeL@url{#1}%
1010 | \def\@acmBadgeL@image{#2}}
1011 | \def\@acmBadgeL@url{}
1012 | \def\@acmBadgeL@image{}
1013 | \def\startPage#1{\def\@startPage{#1}}
1014 | \startPage{}
1015 | \def\terms#1{\def\@terms{#1}}
1016 | \terms{}
1017 | \def\keywords#1{\def\@keywords{#1}}
1018 | \keywords{}
1019 | \renewenvironment{abstract}{\Collect@Body\@saveabstract}{}
1020 | \long\def\@saveabstract#1{\long\gdef\@abstract{#1}}
1021 | \@saveabstract{}
1022 | \long\def\@lempty{}
1023 | \define@boolkey+{@ACM@topmatter@}[@ACM@]{printcss}[true]{%
1024 | \if@ACM@printcss
1025 | \ClassInfo{\@classname}{Printing CSS}%
1026 | \else
1027 | \ClassInfo{\@classname}{Suppressing CSS}%
1028 | \fi}{\ClassError{\@classname}{printcss must be true or false}}
1029 | \define@boolkey+{@ACM@topmatter@}[@ACM@]{printacmref}[true]{%
1030 | \if@ACM@printacmref
1031 | \ClassInfo{\@classname}{Printing bibformat}%
1032 | \else
1033 | \ClassInfo{\@classname}{Suppressing bibformat}%
1034 | \fi}{\ClassError{\@classname}{printacmref must be true or false}}
1035 | \define@boolkey+{@ACM@topmatter@}[@ACM@]{printfolios}[true]{%
1036 | \if@ACM@printfolios
1037 | \ClassInfo{\@classname}{Printing folios}%
1038 | \else
1039 | \ClassInfo{\@classname}{Suppressing folios}%
1040 | \fi}{\ClassError{\@classname}{printfolios must be true or false}}
1041 | \def\settopmatter#1{\setkeys{@ACM@topmatter@}{#1}}
1042 | \settopmatter{printcss=true, printacmref=true}
1043 | \if@ACM@manuscript
1044 | \settopmatter{printfolios=true}
1045 | \else
1046 | \if@ACM@journal
1047 | \settopmatter{printfolios=true}
1048 | \else
1049 | \settopmatter{printfolios=false}
1050 | \fi
1051 | \fi
1052 | \def\@received{}
1053 | \newcommand\received[2][]{\def\@tempa{#1}%
1054 | \ifx\@tempa\@empty
1055 | \ifx\@received\@empty
1056 | \gdef\@received{Received #2}%
1057 | \else
1058 | \g@addto@macro{\@received}{; revised #2}%
1059 | \fi
1060 | \else
1061 | \ifx\@received\@empty
1062 | \gdef\@received{#1 #2}%
1063 | \else
1064 | \g@addto@macro{\@received}{; #1 #2}%
1065 | \fi
1066 | \fi}
1067 | \AtEndDocument{%
1068 | \ifx\@received\@empty\else
1069 | \par\bigskip\noindent\small\normalfont\@received\par
1070 | \fi}
1071 | \RequirePackage{comment}
1072 | \excludecomment{CCSXML}
1073 | \let\@concepts\@empty
1074 | \newcommand\ccsdesc[2][100]{%
1075 | \ccsdesc@parse#1~#2~}
1076 | \def\ccsdesc@parse#1~#2~#3~{%
1077 | \expandafter\ifx\csname CCS@#2\endcsname\relax
1078 | \expandafter\gdef\csname CCS@#2\endcsname{\textbullet\textbf{#2} $\to$ }%
1079 | \g@addto@macro{\@concepts}{\csname CCS@#2\endcsname}\fi
1080 | \expandafter\g@addto@macro\expandafter{\csname CCS@#2\endcsname}{%
1081 | \ifnum#1>499\textbf{#3; }\else
1082 | \ifnum#1>299\textit{#3; }\else
1083 | #3; \fi\fi}}
1084 | \newif\if@printcopyright
1085 | \@printcopyrighttrue
1086 | \newif\if@printpermission
1087 | \@printpermissiontrue
1088 | \newif\if@acmowned
1089 | \@acmownedtrue
1090 | \define@choicekey*{ACM@}{acmcopyrightmode}[%
1091 | \acm@copyrightinput\acm@copyrightmode]{none,acmcopyright,acmlicensed,%
1092 | rightsretained,usgov,usgovmixed,cagov,cagovmixed,%
1093 | licensedusgovmixed,licensedcagovmixed,othergov,licensedothergov}{%
1094 | \@printpermissiontrue
1095 | \@printcopyrighttrue
1096 | \@acmownedtrue
1097 | \ifnum\acm@copyrightmode=0\relax % none
1098 | \@printpermissionfalse
1099 | \@printcopyrightfalse
1100 | \@acmownedfalse
1101 | \fi
1102 | \ifnum\acm@copyrightmode=2\relax % acmlicensed
1103 | \@acmownedfalse
1104 | \fi
1105 | \ifnum\acm@copyrightmode=3\relax % rightsretained
1106 | \@acmownedfalse
1107 | \fi
1108 | \ifnum\acm@copyrightmode=4\relax % usgov
1109 | \@printpermissiontrue
1110 | \@printcopyrightfalse
1111 | \@acmownedfalse
1112 | \fi
1113 | \ifnum\acm@copyrightmode=6\relax % cagov
1114 | \@acmownedfalse
1115 | \fi
1116 | \ifnum\acm@copyrightmode=8\relax % licensedusgovmixed
1117 | \@acmownedfalse
1118 | \fi
1119 | \ifnum\acm@copyrightmode=9\relax % licensedcagovmixed
1120 | \@acmownedfalse
1121 | \fi
1122 | \ifnum\acm@copyrightmode=10\relax % othergov
1123 | \@acmownedtrue
1124 | \fi
1125 | \ifnum\acm@copyrightmode=11\relax % licensedothergov
1126 | \@acmownedfalse
1127 | \fi}
1128 | \def\setcopyright#1{\setkeys{ACM@}{acmcopyrightmode=#1}}
1129 | \setcopyright{acmcopyright}
1130 | \def\@copyrightowner{%
1131 | \ifcase\acm@copyrightmode\relax % none
1132 | \or % acmcopyright
1133 | ACM\@.
1134 | \or % acmlicensed
1135 | Copyright held by the owner/author(s). Publication rights licensed to
1136 | ACM\@.
1137 | \or % rightsretained
1138 | Copyright held by the owner/author(s).
1139 | \or % usgov
1140 | \or % usgovmixed
1141 | ACM\@.
1142 | \or % cagov
1143 | Crown in Right of Canada.
1144 | \or %cagovmixed
1145 | ACM\@.
1146 | \or %licensedusgovmixed
1147 | Copyright held by the owner/author(s). Publication rights licensed to
1148 | ACM\@.
1149 | \or %licensedcagovmixed
1150 | Copyright held by the owner/author(s). Publication rights licensed to
1151 | ACM\@.
1152 | \or % othergov
1153 | ACM\@.
1154 | \or % licensedothergov
1155 | Copyright held by the owner/author(s). Publication rights licensed to
1156 | ACM\@.
1157 | \fi}
1158 | \def\@formatdoi#1{\url{http://dx.doi.org/#1}}
1159 | \def\@copyrightpermission{%
1160 | \ifcase\acm@copyrightmode\relax % none
1161 | \or % acmcopyright
1162 | Permission to make digital or hard copies of all or part of this
1163 | work for personal or classroom use is granted without fee provided
1164 | that copies are not made or distributed for profit or commercial
1165 | advantage and that copies bear this notice and the full citation on
1166 | the first page. Copyrights for components of this work owned by
1167 | others than ACM must be honored. Abstracting with credit is
1168 | permitted. To copy otherwise, or republish, to post on servers or to
1169 | redistribute to lists, requires prior specific permission
1170 | and\hspace*{.5pt}/or a fee. Request permissions from
1171 | permissions@acm.org.
1172 | \or % acmlicensed
1173 | Permission to make digital or hard copies of all or part of this
1174 | work for personal or classroom use is granted without fee provided
1175 | that copies are not made or distributed for profit or commercial
1176 | advantage and that copies bear this notice and the full citation on
1177 | the first page. Copyrights for components of this work owned by
1178 | others than the author(s) must be honored. Abstracting with credit
1179 | is permitted. To copy otherwise, or republish, to post on servers
1180 | or to redistribute to lists, requires prior specific permission
1181 | and\hspace*{.5pt}/or a fee. Request permissions from
1182 | permissions@acm.org.
1183 | \or % rightsretained
1184 | Permission to make digital or hard copies of part or all of this work
1185 | for personal or classroom use is granted without fee provided that
1186 | copies are not made or distributed for profit or commercial advantage
1187 | and that copies bear this notice and the full citation on the first
1188 | page. Copyrights for third-party components of this work must be
1189 | honored. For all other uses, contact the
1190 | owner\hspace*{.5pt}/author(s).
1191 | \or % usgov
1192 | This paper is authored by an employee(s) of the United States
1193 | Government and is in the public domain. Non-exclusive copying or
1194 | redistribution is allowed, provided that the article citation is
1195 | given and the authors and agency are clearly identified as its
1196 | source.
1197 | \or % usgovmixed
1198 | ACM acknowledges that this contribution was authored or co-authored
1199 | by an employee, or contractor of the national government. As such,
1200 | the Government retains a nonexclusive, royalty-free right to
1201 | publish or reproduce this article, or to allow others to do so, for
1202 | Government purposes only. Permission to make digital or hard copies
1203 | for personal or classroom use is granted. Copies must bear this
1204 | notice and the full citation on the first page. Copyrights for
1205 | components of this work owned by others than ACM must be
1206 | honored. To copy otherwise, distribute, republish, or post,
1207 | requires prior specific permission and\hspace*{.5pt}/or a
1208 | fee. Request permissions from permissions@acm.org.
1209 | \or % cagov
1210 | This article was authored by employees of the Government of Canada.
1211 | As such, the Canadian government retains all interest in the
1212 | copyright to this work and grants to ACM a nonexclusive,
1213 | royalty-free right to publish or reproduce this article, or to allow
1214 | others to do so, provided that clear attribution is given both to
1215 | the authors and the Canadian government agency employing them.
1216 | Permission to make digital or hard copies for personal or classroom
1217 | use is granted. Copies must bear this notice and the full citation
1218 | on the first page. Copyrights for components of this work owned by
1219 | others than the Canadain Government must be honored. To copy
1220 | otherwise, distribute, republish, or post, requires prior specific
1221 | permission and\hspace*{.5pt}/or a fee. Request permissions from
1222 | permissions@acm.org.
1223 | \or % cagovmixed
1224 | ACM acknowledges that this contribution was co-authored by an
1225 | affiliate of the national government of Canada. As such, the Crown
1226 | in Right of Canada retains an equal interest in the copyright.
1227 | Reprints must include clear attribution to ACM and the author's
1228 | government agency affiliation. Permission to make digital or hard
1229 | copies for personal or classroom use is granted. Copies must bear
1230 | this notice and the full citation on the first page. Copyrights for
1231 | components of this work owned by others than ACM must be honored.
1232 | To copy otherwise, distribute, republish, or post, requires prior
1233 | specific permission and\hspace*{.5pt}/or a fee. Request permissions
1234 | from permissions@acm.org.
1235 | \or % licensedusgovmixed
1236 | Publication rights licensed to ACM\@. ACM acknowledges that this
1237 | contribution was authored or co-authored by an employee, contractor
1238 | or affiliate of the United States government. As such, the
1239 | Government retains a nonexclusive, royalty-free right to publish or
1240 | reproduce this article, or to allow others to do so, for Government
1241 | purposes only.
1242 | \or % licensedcagovmixed
1243 | Publication rights licensed to ACM\@. ACM acknowledges that this
1244 | contribution was authored or co-authored by an employee, contractor
1245 | or affiliate of the national government of Canada. As such, the
1246 | Government retains a nonexclusive, royalty-free right to publish or
1247 | reproduce this article, or to allow others to do so, for Government
1248 | purposes only.
1249 | \or % othergov
1250 | ACM acknowledges that this contribution was authored or co-authored
1251 | by an employee, contractor or affiliate of a national government. As
1252 | such, the Government retains a nonexclusive, royalty-free right to
1253 | publish or reproduce this article, or to allow others to do so, for
1254 | Government purposes only.
1255 | \or % licensedothergov
1256 | Publication rights licensed to ACM\@. ACM acknowledges that this
1257 | contribution was authored or co-authored by an employee, contractor
1258 | or affiliate of a national government. As such, the Government
1259 | retains a nonexclusive, royalty-free right to publish or reproduce
1260 | this article, or to allow others to do so, for Government purposes
1261 | only.
1262 | \fi}
1263 | \def\copyrightyear#1{\def\@copyrightyear{#1}}
1264 | \copyrightyear{\@acmYear}
1265 | \def\@teaserfigures{}
1266 | \newenvironment{teaserfigure}{\Collect@Body\@saveteaser}{}
1267 | \long\def\@saveteaser#1{\g@addto@macro\@teaserfigures{\@teaser{#1}}}
1268 | \renewcommand{\thanks}[1]{%
1269 | \@ifnotempty{#1}{%
1270 | \if@ACM@anonymous
1271 | \g@addto@macro\thankses{\thanks{A note}}%
1272 | \else
1273 | \g@addto@macro\thankses{\thanks{#1}}%
1274 | \fi}}
1275 | \newbox\mktitle@bx
1276 | \def\maketitle{%
1277 | \if@ACM@anonymous
1278 | % Anonymize omission of \author-s
1279 | \ifnum\num@authorgroups=0\author{}\fi
1280 | \fi
1281 | \begingroup
1282 | \let\@footnotemark\@footnotemark@nolink
1283 | \let\@footnotetext\@footnotetext@nolink
1284 | \renewcommand\thefootnote{\@fnsymbol\c@footnote}%
1285 | \@topnum\z@ % this prevents figures from falling at the top of page
1286 | % 1
1287 | \hsize=\textwidth
1288 | \def\@makefnmark{\hbox{\@textsuperscript{\@thefnmark}}}%
1289 | \@mktitle\if@ACM@sigchiamode\else\@mkauthors\fi\@mkteasers
1290 | \@printtopmatter
1291 | \if@ACM@sigchiamode\@mkauthors\fi
1292 | \setcounter{footnote}{0}%
1293 | \def\@makefnmark{\hbox{\@textsuperscript{\normalfont\@thefnmark}}}%
1294 | \@titlenotes
1295 | \@subtitlenotes
1296 | \@authornotes
1297 | \let\@makefnmark\relax \let\@thefnmark\relax
1298 | \let\@makefntext\noindent
1299 | \ifx\@empty\thankses\else
1300 | \footnotetextcopyrightpermission{%
1301 | \def\par{\let\par\@par}\parindent\z@\@setthanks}%
1302 | \fi
1303 | \footnotetextcopyrightpermission{\parindent\z@\parskip0.1\baselineskip
1304 | \if@ACM@authorversion\else
1305 | \if@printpermission\@copyrightpermission\par\fi
1306 | \fi
1307 | \if@ACM@manuscript\else
1308 | \if@ACM@journal\else % Print the conference short name
1309 | {\itshape \acmConference@shortname, \acmConference@venue}\par
1310 | \fi
1311 | \fi
1312 | \if@printcopyright
1313 | \copyright\ \@copyrightyear\ \@copyrightowner\
1314 | \else
1315 | \@copyrightyear.\
1316 | \fi
1317 | \if@ACM@manuscript
1318 | Manuscript submitted to ACM\\
1319 | \else
1320 | \if@ACM@authorversion
1321 | This is the author's version of the work. It is posted here for
1322 | your personal use. Not for redistribution. The definitive Version
1323 | of Record was published in
1324 | \if@ACM@journal
1325 | \emph{\@journalName}%
1326 | \else
1327 | \emph{Proceedings of \acmConference@name, \acmConference@date}%
1328 | \fi
1329 | \ifx\@acmDOI\@empty
1330 | .
1331 | \else
1332 | , \@formatdoi{\@acmDOI}.
1333 | \fi\\
1334 | \else
1335 | \if@ACM@journal
1336 | \@permissionCodeOne/\@acmYear/\@acmMonth-ART\@acmArticle\
1337 | \$\@acmPrice\\
1338 | DOI: \nolinkurl{\@acmDOI}%
1339 | \else % Conference
1340 | \@acmISBN
1341 | \ifx\@acmPrice\@empty.\else\dots\$\@acmPrice\fi\\
1342 | DOI: \nolinkurl{\@acmDOI}%
1343 | \fi
1344 | \fi
1345 | \fi}%
1346 | \endgroup
1347 | \setcounter{footnote}{0}%
1348 | \@mkabstract
1349 | \if@ACM@printcss
1350 | \ifx\@concepts\@empty\else\bgroup
1351 | {\@specialsection{CCS Concepts}%
1352 | \@concepts\par}\egroup
1353 | \fi
1354 | \fi
1355 | \if\@terms\@empty\else\bgroup
1356 | {\@specialsection{General Terms}%
1357 | \@terms\par}\egroup
1358 | \fi
1359 | \ifx\@keywords\@empty\else\bgroup
1360 | {\if@ACM@journal
1361 | \@specialsection{Additional Key Words and Phrases}%
1362 | \else
1363 | \@specialsection{Keywords}%
1364 | \fi
1365 | \@keywords}\par\egroup
1366 | \fi
1367 | \andify\authors
1368 | \andify\shortauthors
1369 | \global\let\authors=\authors
1370 | \global\let\shortauthors=\shortauthors
1371 | \if@ACM@printacmref
1372 | \@mkbibcitation
1373 | \fi
1374 | \hypersetup{pdfauthor={\authors},
1375 | pdftitle={\@title}, pdfkeywords={\@concepts}}%
1376 | \@printendtopmatter
1377 | \@afterindentfalse
1378 | \@afterheading
1379 | }
1380 | \def\@specialsection#1{%
1381 | \ifcase\ACM@format@nr
1382 | \relax % manuscript
1383 | \par\medskip\small\noindent#1: %
1384 | \or % acmsmall
1385 | \par\medskip\small\noindent#1: %
1386 | \or % acmlarge
1387 | \par\medskip\small\noindent#1: %
1388 | \or % acmtog
1389 | \par\medskip\small\noindent#1: %
1390 | \or % sigconf
1391 | \section*{#1}%
1392 | \or % siggraph
1393 | \section*{#1}%
1394 | \or % sigplan
1395 | \paragraph*{#1}%
1396 | \or % sigchi
1397 | \section*{#1}%
1398 | \or % sigchi-a
1399 | \section*{#1}%
1400 | \fi}
1401 | \def\@printtopmatter{%
1402 | \ifx\@startPage\@empty
1403 | \gdef\@startPage{1}%
1404 | \else
1405 | \setcounter{page}{\@startPage}%
1406 | \fi
1407 | \thispagestyle{firstpagestyle}%
1408 | \noindent
1409 | \ifcase\ACM@format@nr
1410 | \relax % manuscript
1411 | \box\mktitle@bx\par
1412 | \noindent\hrulefill\par
1413 | \or % acmsmall
1414 | \box\mktitle@bx\par
1415 | \noindent\hrulefill\par
1416 | \or % acmlarge
1417 | \box\mktitle@bx\par
1418 | \noindent\hrulefill\par
1419 | \or % acmtog
1420 | \twocolumn[\box\mktitle@bx]%
1421 | \or % sigconf
1422 | \twocolumn[\box\mktitle@bx]%
1423 | \or % siggraph
1424 | \twocolumn[\box\mktitle@bx]%
1425 | \or % sigplan
1426 | \twocolumn[\box\mktitle@bx]%
1427 | \or % sigchi
1428 | \twocolumn[\box\mktitle@bx]%
1429 | \or % sigchi-a
1430 | \par\box\mktitle@bx\par\bigskip
1431 | \if@ACM@badge
1432 | \marginpar{\noindent
1433 | \ifx\@acmBadgeL@image\@empty\else
1434 | \href{\@acmBadgeL@url}{%
1435 | \includegraphics[width=\@ACM@badge@width]{\@acmBadgeL@image}}%
1436 | \hskip\@ACM@badge@skip
1437 | \fi
1438 | \ifx\@acmBadgeR@image\@empty\else
1439 | \href{\@acmBadgeR@url}{%
1440 | \includegraphics[width=\@ACM@badge@width]{\@acmBadgeR@image}}%
1441 | \fi}%
1442 | \fi
1443 | \fi
1444 | }
1445 | \def\@mktitle{%
1446 | \ifcase\ACM@format@nr
1447 | \relax % manuscript
1448 | \@mktitle@i
1449 | \or % acmsmall
1450 | \@mktitle@i
1451 | \or % acmlarge
1452 | \@mktitle@i
1453 | \or % acmtog
1454 | \@mktitle@i
1455 | \or % sigconf
1456 | \@mktitle@iii
1457 | \or % siggraph
1458 | \@mktitle@iii
1459 | \or % sigplan
1460 | \@mktitle@iii
1461 | \or % sigchi
1462 | \@mktitle@iii
1463 | \or % sigchi-a
1464 | \@mktitle@iv
1465 | \fi
1466 | }
1467 | \def\@titlefont{%
1468 | \ifcase\ACM@format@nr
1469 | \relax % manuscript
1470 | \LARGE\bfseries\sffamily
1471 | \or % acmsmall
1472 | \LARGE\bfseries\sffamily
1473 | \or % acmlarge
1474 | \LARGE\bfseries\sffamily
1475 | \or % acmtog
1476 | \Huge\sffamily
1477 | \or % sigconf
1478 | \Huge\sffamily\bfseries
1479 | \or % siggraph
1480 | \Huge\sffamily\bfseries
1481 | \or % sigplan
1482 | \Huge\bfseries
1483 | \or % sigchi
1484 | \Huge\sffamily\bfseries
1485 | \or % sigchi-a
1486 | \Huge\bfseries
1487 | \fi}
1488 | \def\@subtitlefont{%
1489 | \ifcase\ACM@format@nr
1490 | \relax % manuscript
1491 | \mdseries
1492 | \or % acmsmall
1493 | \mdseries
1494 | \or % acmlarge
1495 | \mdseries
1496 | \or % acmtog
1497 | \LARGE
1498 | \or % sigconf
1499 | \LARGE\mdseries
1500 | \or % siggraph
1501 | \LARGE\mdseries
1502 | \or % sigplan
1503 | \LARGE\mdseries
1504 | \or % sigchi
1505 | \LARGE\mdseries
1506 | \or % sigchi-a
1507 | \mdseries
1508 | \fi}
1509 | \def\@mktitle@i{\hsize=\textwidth
1510 | \@ACM@title@width=\hsize
1511 | \ifx\@acmBadgeL@image\@empty\else
1512 | \advance\@ACM@title@width by -\@ACM@badge@width
1513 | \advance\@ACM@title@width by -\@ACM@badge@skip
1514 | \fi
1515 | \ifx\@acmBadgeR@image\@empty\else
1516 | \advance\@ACM@title@width by -\@ACM@badge@width
1517 | \advance\@ACM@title@width by -\@ACM@badge@skip
1518 | \fi
1519 | \setbox\mktitle@bx=\vbox{\noindent\@titlefont
1520 | \ifx\@acmBadgeL@image\@empty\else
1521 | \raisebox{-.5\baselineskip}[\z@][\z@]{\href{\@acmBadgeL@url}{%
1522 | \includegraphics[width=\@ACM@badge@width]{\@acmBadgeL@image}}}%
1523 | \hskip\@ACM@badge@skip
1524 | \fi
1525 | \parbox[t]{\@ACM@title@width}{\raggedright
1526 | \@titlefont\noindent
1527 | \@title
1528 | \ifx\@subtitle\@empty\else
1529 | \par\noindent{\@subtitlefont\@subtitle}
1530 | \fi}%
1531 | \ifx\@acmBadgeR@image\@empty\else
1532 | \hskip\@ACM@badge@skip
1533 | \raisebox{-.5\baselineskip}[\z@][\z@]{\href{\@acmBadgeR@url}{%
1534 | \includegraphics[width=\@ACM@badge@width]{\@acmBadgeR@image}}}%
1535 | \fi
1536 | \par\bigskip}}%
1537 | \def\@mktitle@iii{\hsize=\textwidth
1538 | \setbox\mktitle@bx=\vbox{\@titlefont\centering
1539 | \@ACM@title@width=\hsize
1540 | \if@ACM@badge
1541 | \advance\@ACM@title@width by -2\@ACM@badge@width
1542 | \advance\@ACM@title@width by -2\@ACM@badge@skip
1543 | \parbox[b]{\@ACM@badge@width}{\strut
1544 | \ifx\@acmBadgeL@image\@empty\else
1545 | \raisebox{-.5\baselineskip}[\z@][\z@]{\href{\@acmBadgeL@url}{%
1546 | \includegraphics[width=\@ACM@badge@width]{\@acmBadgeL@image}}}%
1547 | \fi}%
1548 | \hskip\@ACM@badge@skip
1549 | \fi
1550 | \parbox[t]{\@ACM@title@width}{\centering\@titlefont
1551 | \@title
1552 | \ifx\@subtitle\@empty\else
1553 | \par\noindent{\@subtitlefont\@subtitle}
1554 | \fi
1555 | }%
1556 | \if@ACM@badge
1557 | \hskip\@ACM@badge@skip
1558 | \parbox[b]{\@ACM@badge@width}{\strut
1559 | \ifx\@acmBadgeR@image\@empty\else
1560 | \raisebox{-.5\baselineskip}[\z@][\z@]{\href{\@acmBadgeR@url}{%
1561 | \includegraphics[width=\@ACM@badge@width]{\@acmBadgeR@image}}}%
1562 | \fi}%
1563 | \fi
1564 | \par\bigskip}}%
1565 | \def\@mktitle@iv{\hsize=\textwidth
1566 | \setbox\mktitle@bx=\vbox{\raggedright\leftskip5pc\@titlefont
1567 | \noindent\leavevmode\leaders\hrule height 2pt\hfill\kern0pt\par
1568 | \noindent\@title
1569 | \ifx\@subtitle\@empty\else
1570 | \par\noindent\@subtitlefont\@subtitle
1571 | \fi
1572 | \par\bigskip}}%
1573 | \newbox\@ACM@commabox
1574 | \def\@ACM@addtoaddress#1{%
1575 | \ifvmode\else
1576 | \setbox\@ACM@commabox=\hbox{, }%
1577 | \unskip\cleaders\copy\@ACM@commabox\hskip\wd\@ACM@commabox
1578 | \fi
1579 | #1}
1580 | \if@ACM@journal
1581 | \let\position\@gobble
1582 | \def\institution#1{#1\ignorespaces}%
1583 | \let\department\@gobble
1584 | \let\streetaddress\@gobble
1585 | \let\city\@gobble
1586 | \let\state\@gobble
1587 | \let\postcode\@gobble
1588 | \let\country\@gobble
1589 | \else
1590 | \def\position#1{#1\par}%
1591 | \def\institution#1{#1\par}%
1592 | \def\department#1{#1\par}%
1593 | \def\streetaddress#1{#1\par}%
1594 | \let\city\@ACM@addtoaddress
1595 | \let\state\@ACM@addtoaddress
1596 | \def\postcode#1{\unskip\space#1}%
1597 | \let\country\@ACM@addtoaddress
1598 | \fi
1599 | \def\@mkauthors{\begingroup
1600 | \hsize=\textwidth
1601 | \ifcase\ACM@format@nr
1602 | \relax % manuscript
1603 | \@mkauthors@i
1604 | \or % acmsmall
1605 | \@mkauthors@i
1606 | \or % acmlarge
1607 | \@mkauthors@i
1608 | \or % acmtog
1609 | \@mkauthors@i
1610 | \or % sigconf
1611 | \@mkauthors@iii
1612 | \or % siggraph
1613 | \@mkauthors@iii
1614 | \or % sigplan
1615 | \@mkauthors@iii
1616 | \or % sigchi
1617 | \@mkauthors@iii
1618 | \or % sigchi-a
1619 | \@mkauthors@iv
1620 | \fi
1621 | \endgroup
1622 | }
1623 | \def\@authorfont{\Large\sffamily}
1624 | \def\@affiliationfont{\normalsize\normalfont}
1625 | \ifcase\ACM@format@nr
1626 | \relax % manuscript
1627 | \or % acmsmall
1628 | \def\@authorfont{\large\sffamily}
1629 | \def\@affiliationfont{\small\normalfont}
1630 | \or % acmlarge
1631 | \or % acmtog
1632 | \def\@authorfont{\LARGE\sffamily}
1633 | \def\@affiliationfont{\large}
1634 | \or % sigconf
1635 | \def\@authorfont{\LARGE}
1636 | \def\@affiliationfont{\large}
1637 | \or % siggraph
1638 | \def\@authorfont{\normalsize\normalfont}
1639 | \def\@affiliationfont{\normalsize\normalfont}
1640 | \or % sigplan
1641 | \def\@authorfont{\Large\normalfont}
1642 | \def\@affiliationfont{\normalsize\normalfont}
1643 | \or % sigchi
1644 | \def\@authorfont{\bfseries}
1645 | \def\@affiliationfont{\mdseries}
1646 | \or % sigchi-a
1647 | \def\@authorfont{\bfseries}
1648 | \def\@affiliationfont{\mdseries}
1649 | \fi
1650 | \def\@typeset@author@line{%
1651 | \andify\@currentauthors\par\noindent
1652 | \@currentauthors\def\@currentauthors{}%
1653 | \ifx\@currentaffiliations\@empty\else
1654 | \andify\@currentaffiliations
1655 | \unskip, {\@currentaffiliations}\par
1656 | \fi
1657 | \def\@currentaffiliations{}}
1658 | \def\@mkauthors@i{%
1659 | \def\@currentauthors{}%
1660 | \def\@currentaffiliations{}%
1661 | \global\let\and\@typeset@author@line
1662 | \def\@author##1{%
1663 | \ifx\@currentauthors\@empty
1664 | \gdef\@currentauthors{\@authorfont\MakeUppercase{##1}}%
1665 | \else
1666 | \g@addto@macro{\@currentauthors}{\and\MakeUppercase{##1}}%
1667 | \fi
1668 | \gdef\and{}}%
1669 | \def\email##1##2{}%
1670 | \def\affiliation##1##2{%
1671 | \def\@tempa{##2}\ifx\@tempa\@empty\else
1672 | \ifx\@currentaffiliations\@empty
1673 | \gdef\@currentaffiliations{\@affiliationfont##2}%
1674 | \else
1675 | \g@addto@macro{\@currentaffiliations}{\and##2}%
1676 | \fi
1677 | \fi
1678 | \global\let\and\@typeset@author@line}
1679 | \global\setbox\mktitle@bx=\vbox{\noindent\box\mktitle@bx\par\medskip
1680 | \noindent\addresses\@typeset@author@line
1681 | \par\medskip}%
1682 | }
1683 | \newbox\author@bx
1684 | \newdimen\author@bx@wd
1685 | \newskip\author@bx@sep
1686 | \author@bx@sep=1pc\relax
1687 | \def\@typeset@author@bx{\bgroup\hsize=\author@bx@wd\def\and{\par}%
1688 | \global\setbox\author@bx=\vtop{\if@ACM@sigchiamode\else\centering\fi
1689 | \@authorfont\@currentauthors\par\@affiliationfont
1690 | \@currentaffiliation}\egroup
1691 | \box\author@bx\hspace{\author@bx@sep}%
1692 | \gdef\@currentauthors{}%
1693 | \gdef\@currentaffiliation{}}
1694 | \def\@mkauthors@iii{%
1695 | \author@bx@wd=\textwidth\relax
1696 | \advance\author@bx@wd by -\author@bx@sep\relax
1697 | \ifcase\num@authorgroups
1698 | \relax % 0?
1699 | \or % 1=one author per row
1700 | \or % 2=two authors per row
1701 | \divide\author@bx@wd by \num@authorgroups\relax
1702 | \or % 3=three authors per row
1703 | \divide\author@bx@wd by \num@authorgroups\relax
1704 | \or % 4=two authors per row (!)
1705 | \divide\author@bx@wd by 2\relax
1706 | \else % three authors per row
1707 | \divide\author@bx@wd by 3\relax
1708 | \fi
1709 | \advance\author@bx@wd by -\author@bx@sep\relax
1710 | \gdef\@currentauthors{}%
1711 | \gdef\@currentaffiliation{}%
1712 | \def\@author##1{\ifx\@currentauthors\@empty
1713 | \gdef\@currentauthors{\par##1}%
1714 | \else
1715 | \g@addto@macro\@currentauthors{\par##1}%
1716 | \fi
1717 | \gdef\and{}}%
1718 | \def\email##1##2{\ifx\@currentaffiliation\@empty
1719 | \gdef\@currentaffiliation{\nolinkurl{##2}}%
1720 | \else
1721 | \g@addto@macro\@currentaffiliation{\par\nolinkurl{##2}}%
1722 | \fi}%
1723 | \def\affiliation##1##2{\ifx\@currentaffiliation\@empty
1724 | \gdef\@currentaffiliation{##2}%
1725 | \else
1726 | \g@addto@macro\@currentaffiliation{\par##2}%
1727 | \fi
1728 | \global\let\and\@typeset@author@bx
1729 | }%
1730 | \hsize=\textwidth
1731 | \global\setbox\mktitle@bx=\vbox{\noindent
1732 | \box\mktitle@bx\par\medskip\leavevmode
1733 | \lineskip=1pc\relax\centering\hspace*{-1em}%
1734 | \addresses\let\and\@typeset@author@bx\and\par\bigskip}}
1735 | \def\@mkauthors@iv{%
1736 | \author@bx@wd=\columnwidth\relax
1737 | \advance\author@bx@wd by -\author@bx@sep\relax
1738 | \ifcase\num@authorgroups
1739 | \relax % 0?
1740 | \or % 1=one author per row
1741 | \else % 2=two authors per row
1742 | \divide\author@bx@wd by 2\relax
1743 | \fi
1744 | \advance\author@bx@wd by -\author@bx@sep\relax
1745 | \gdef\@currentauthors{}%
1746 | \gdef\@currentaffiliation{}%
1747 | \def\@author##1{\ifx\@currentauthors\@empty
1748 | \gdef\@currentauthors{\par##1}%
1749 | \else
1750 | \g@addto@macro\@currentauthors{\par##1}%
1751 | \fi
1752 | \gdef\and{}}%
1753 | \def\email##1##2{\ifx\@currentaffiliation\@empty
1754 | \gdef\@currentaffiliation{\nolinkurl{##2}}%
1755 | \else
1756 | \g@addto@macro\@currentaffiliation{\par\nolinkurl{##2}}%
1757 | \fi}%
1758 | \def\affiliation##1##2{\ifx\@currentaffiliation\@empty
1759 | \gdef\@currentaffiliation{##2}%
1760 | \else
1761 | \g@addto@macro\@currentaffiliation{\par##2}%
1762 | \fi
1763 | \global\let\and\@typeset@author@bx}%
1764 | \bgroup\hsize=\columnwidth
1765 | \par\raggedright\leftskip=\z@
1766 | \lineskip=1pc\noindent
1767 | \addresses\let\and\@typeset@author@bx\and\par\bigskip\egroup}
1768 | \def\@authornotemark{\g@addto@macro\@currentauthors{\footnotemark}}
1769 | \def\@mkteasers{%
1770 | \ifx\@teaserfigures\@empty\else
1771 | \def\@teaser##1{\par\bigskip\bgroup
1772 | \captionsetup{type=figure}##1\egroup\par}
1773 | \global\setbox\mktitle@bx=\vbox{\noindent\box\mktitle@bx\par
1774 | \noindent\@teaserfigures\par\medskip}%
1775 | \fi}
1776 | \def\@setaddresses{}
1777 | \def\@mkabstract{\bgroup
1778 | \ifx\@abstract\@lempty\else
1779 | {\if@ACM@journal
1780 | \small\noindent
1781 | \else
1782 | \section*{Abstract}%
1783 | \fi
1784 | \phantomsection\addcontentsline{toc}{section}{Abstract}%
1785 | \ignorespaces\@abstract\par}%
1786 | \fi\egroup}
1787 | \def\@mkbibcitation{\bgroup
1788 | \def\footnotemark{}%
1789 | \par\medskip\small\noindent{\bfseries ACM Reference format:}\par\nobreak
1790 | \noindent\authors. \@acmYear. \@title.
1791 | \if@ACM@journal
1792 | \textit{\@journalNameShort}
1793 | \@acmVolume, \@acmNumber, Article~\@acmArticle\ (\@acmPubDate),
1794 | \ref{TotPages}~pages.
1795 | \else
1796 | In \textit{Proceedings of \acmConference@name, \acmConference@venue,
1797 | \acmConference@date
1798 | \ifx\acmConference@name\acmConference@shortname\else
1799 | \ (\acmConference@shortname)\fi
1800 | ,} \ref{TotPages}~pages.
1801 | \fi\par
1802 | \noindent DOI: \nolinkurl{\@acmDOI}
1803 | \par\egroup}
1804 | \def\@printendtopmatter{\par\medskip
1805 | \ifcase\ACM@format@nr
1806 | \relax % manuscript
1807 | \noindent\hrulefill\par\medskip
1808 | \or % acmsmall
1809 | \noindent\hrulefill\par\medskip
1810 | \or % acmlarge
1811 | \noindent\hrulefill\par\medskip
1812 | \or % acmtog
1813 | \par\bigskip
1814 | \or % sigconf
1815 | \par\bigskip
1816 | \or % siggraph
1817 | \par\bigskip
1818 | \or % sigplan
1819 | \par\bigskip
1820 | \or % sigchi
1821 | \par\bigskip
1822 | \or % sigchi-a
1823 | \fi
1824 | }
1825 | \def\@setthanks{\long\def\thanks##1{\par##1\@addpunct.}\thankses}
1826 | \RequirePackage{fancyhdr}
1827 | \if@ACM@review
1828 | \newsavebox{\ACM@linecount@bx}
1829 | \savebox{\ACM@linecount@bx}[4em][t]{\parbox[t]{4em}{%
1830 | \newlength\ACM@linecount@bxht\setlength{\ACM@linecount@bxht}{-\baselineskip}
1831 | \@tempcnta\@ne\relax
1832 | \loop{\color{ACMRed}\scriptsize\the\@tempcnta}\\
1833 | \advance\@tempcnta by \@ne
1834 | \addtolength{\ACM@linecount@bxht}{\baselineskip}
1835 | \ifdim\ACM@linecount@bxht<\textheight\repeat}}
1836 | \fi
1837 | \def\ACM@linecount{%
1838 | \if@ACM@review
1839 | \begin{picture}(0,0)%
1840 | \put(-26,-22){\usebox{\ACM@linecount@bx}}%
1841 | \end{picture}%
1842 | \fi}
1843 | \def\@shortauthors{\if@ACM@anonymous Anon.\else\shortauthors\fi}
1844 | \def\@headfootfont{%
1845 | \ifcase\ACM@format@nr
1846 | \relax % manuscript
1847 | \sffamily
1848 | \or % acmsmall
1849 | \sffamily
1850 | \or % acmlarge
1851 | \sffamily
1852 | \or % acmtog
1853 | \sffamily
1854 | \or % sigconf
1855 | \sffamily
1856 | \or % siggraph
1857 | \sffamily
1858 | \or % sigplan
1859 | \sffamily
1860 | \or % sigchi
1861 | \sffamily
1862 | \or % sigchi-a
1863 | \sffamily
1864 | \fi}
1865 | \fancypagestyle{standardpagestyle}{%
1866 | \fancyhf{}%
1867 | \renewcommand{\headrulewidth}{\z@}%
1868 | \renewcommand{\footrulewidth}{\z@}%
1869 | \ifcase\ACM@format@nr
1870 | \relax % manuscript
1871 | \fancyhead[LE]{\ACM@linecount\if@ACM@printfolios\thepage\fi}%
1872 | \fancyhead[RO]{\if@ACM@printfolios\thepage\fi}%
1873 | \fancyhead[RE]{\@shortauthors}%
1874 | \fancyhead[LO]{\ACM@linecount\shorttitle}%
1875 | \fancyfoot[RO,LE]{\footnotesize Manuscript submitted to ACM}%
1876 | \or % acmsmall
1877 | \fancyhead[LE]{\ACM@linecount\@headfootfont\@acmArticle\if@ACM@printfolios:\thepage\fi}%
1878 | \fancyhead[RO]{\@headfootfont\@acmArticle\if@ACM@printfolios:\thepage\fi}%
1879 | \fancyhead[RE]{\@headfootfont\@shortauthors}%
1880 | \fancyhead[LO]{\ACM@linecount\@headfootfont\shorttitle}%
1881 | \fancyfoot[RO,LE]{\footnotesize \@journalName, Vol. \@acmVolume, No.
1882 | \@acmNumber, Article \@acmArticle. Publication date: \@acmPubDate.}%
1883 | \or % acmlarge
1884 | \fancyhead[LE]{\ACM@linecount\@headfootfont
1885 | \@acmArticle:\if@ACM@printfolios\thepage\quad\textbullet\quad\fi\@shortauthors}%
1886 | \fancyhead[LO]{\ACM@linecount}%
1887 | \fancyhead[RO]{\@headfootfont
1888 | \shorttitle\quad\textbullet\quad\@acmArticle\if@ACM@printfolios:\thepage\fi}%
1889 | \fancyfoot[RO,LE]{\footnotesize \@journalName, Vol. \@acmVolume, No.
1890 | \@acmNumber, Article \@acmArticle. Publication date: \@acmPubDate.}%
1891 | \or % acmtog
1892 | \fancyhead[LE]{\ACM@linecount\@headfootfont
1893 | \@acmArticle:\if@ACM@printfolios\thepage\quad\textbullet\quad\fi\@shortauthors}%
1894 | \fancyhead[LO]{\ACM@linecount}%
1895 | \fancyhead[RO]{\@headfootfont
1896 | \shorttitle\quad\textbullet\quad\@acmArticle\if@ACM@printfolios:\thepage\fi}%
1897 | \fancyfoot[RO,LE]{\footnotesize \@journalName, Vol. \@acmVolume, No.
1898 | \@acmNumber, Article \@acmArticle. Publication date: \@acmPubDate.}%
1899 | \else % Proceedings
1900 | \fancyfoot[C]{\if@ACM@printfolios\footnotesize\thepage\fi}%
1901 | \fancyhead[LO]{\ACM@linecount\@headfootfont\shorttitle}%
1902 | \fancyhead[RE]{\@headfootfont\@shortauthors}%
1903 | \fancyhead[LE]{\ACM@linecount\@headfootfont\acmConference@shortname,
1904 | \acmConference@date, \acmConference@venue}%
1905 | \fancyhead[RO]{\@headfootfont\acmConference@shortname,
1906 | \acmConference@date, \acmConference@venue}%
1907 | \fi
1908 | \if@ACM@sigchiamode
1909 | \fancyheadoffset[L]{\dimexpr(\marginparsep+\marginparwidth)}%
1910 | \fi
1911 | }
1912 | \pagestyle{standardpagestyle}
1913 | \newdimen\@folio@wd
1914 | \@folio@wd=\z@
1915 | \newdimen\@folio@ht
1916 | \@folio@ht=\z@
1917 | \newdimen\@folio@voffset
1918 | \@folio@voffset=\z@
1919 | \def\@folio@max{1}
1920 | \ifcase\ACM@format@nr
1921 | \relax % manuscript
1922 | \or % acmsmall
1923 | \@folio@wd=45.75pt\relax
1924 | \@folio@ht=1.25in\relax
1925 | \@folio@voffset=.2in\relax
1926 | \def\@folio@max{8}
1927 | \or % acmlarge
1928 | \@folio@wd=43.25pt\relax
1929 | \@folio@ht=79pt\relax
1930 | \@folio@voffset=.55in\relax
1931 | \def\@folio@max{10}
1932 | \fi
1933 | \def\@folioblob{\@tempcnta=\@acmArticleSeq\relax
1934 | \loop
1935 | \ifnum\@tempcnta>\@folio@max\relax
1936 | \advance\@tempcnta by - \@folio@max
1937 | \repeat
1938 | \advance\@tempcnta by -1\relax
1939 | \@tempdima=\@folio@ht\relax
1940 | \multiply\@tempdima by \the\@tempcnta\relax
1941 | \advance\@tempdima by -\@folio@voffset\relax
1942 | \begin{picture}(0,0)
1943 | \makebox[\z@]{\raisebox{-\@tempdima}{%
1944 | \rlap{%
1945 | \raisebox{-0.45\@folio@ht}[\z@][\z@]{%
1946 | \rule{\@folio@wd}{\@folio@ht}}}%
1947 | \parbox{\@folio@wd}{%
1948 | \centering
1949 | \textcolor{white}{\LARGE\bfseries\sffamily\@acmArticle}}}}
1950 | \end{picture}}
1951 |
1952 | \fancypagestyle{firstpagestyle}{%
1953 | \fancyhf{}%
1954 | \renewcommand{\headrulewidth}{\z@}%
1955 | \renewcommand{\footrulewidth}{\z@}%
1956 | \ifcase\ACM@format@nr
1957 | \relax % manuscript
1958 | \fancyhead[L]{\ACM@linecount}%
1959 | \fancyfoot[RO,LE]{\if@ACM@printfolios\small\thepage\fi}%
1960 | \fancyfoot[RE,LO]{\footnotesize Manuscript submitted to ACM}%
1961 | \or % acmsmall
1962 | \fancyfoot[RO,LE]{\footnotesize \@journalName, Vol. \@acmVolume, No.
1963 | \@acmNumber, Article \@acmArticle. Publication date:
1964 | \@acmPubDate.}%
1965 | \fancyhead[LE]{\ACM@linecount\@folioblob}%
1966 | \fancyhead[LO]{\ACM@linecount}%
1967 | \fancyhead[RO]{\@folioblob}%
1968 | \fancyheadoffset[RO,LE]{0.6\@folio@wd}%
1969 | \or % acmlarge
1970 | \fancyfoot[RO,LE]{\footnotesize \@journalName, Vol. \@acmVolume, No.
1971 | \@acmNumber, Article \@acmArticle. Publication date:
1972 | \@acmPubDate.}%
1973 | \fancyhead[RO]{\@folioblob}%
1974 | \fancyhead[LE]{\ACM@linecount\@folioblob}%
1975 | \fancyhead[LO]{\ACM@linecount}%
1976 | \fancyheadoffset[RO,LE]{1.4\@folio@wd}%
1977 | \or % acmtog
1978 | \fancyfoot[RO,LE]{\footnotesize \@journalName, Vol. \@acmVolume, No.
1979 | \@acmNumber, Article \@acmArticle. Publication date:
1980 | \@acmPubDate.}%
1981 | \fancyhead[L]{\ACM@linecount}%
1982 | \else % Conference proceedings
1983 | \fancyhead[L]{\ACM@linecount}%
1984 | \fancyfoot[C]{\if@ACM@printfolios\footnotesize\thepage\fi}%
1985 | \fi
1986 | }
1987 | \renewcommand\section{\@startsection{section}{1}{\z@}%
1988 | {-.75\baselineskip \@plus -2\p@ \@minus -.2\p@}%
1989 | {.25\baselineskip}%
1990 | {\@secfont}}
1991 | \renewcommand\subsection{\@startsection{subsection}{2}{\z@}%
1992 | {-.75\baselineskip \@plus -2\p@ \@minus -.2\p@}%
1993 | {.25\baselineskip}%
1994 | {\@subsecfont}}
1995 | \renewcommand\subsubsection{\@startsection{subsubsection}{3}{10pt}%
1996 | {-.5\baselineskip \@plus -2\p@ \@minus -.2\p@}%
1997 | {-3.5\p@}%
1998 | {\@subsubsecfont\@adddotafter}}
1999 | \renewcommand\paragraph{\@startsection{paragraph}{4}{\parindent}%
2000 | {-.5\baselineskip \@plus -2\p@ \@minus -.2\p@}%
2001 | {-3.5\p@}%
2002 | {\@parfont\@adddotafter}}
2003 | \renewcommand\part{\@startsection{part}{9}{\z@}%
2004 | {-10\p@ \@plus -4\p@ \@minus -2\p@}%
2005 | {4\p@}%
2006 | {\@parfont}}
2007 | \def\section@raggedright{\@rightskip\@flushglue
2008 | \rightskip\@rightskip
2009 | \leftskip\z@skip
2010 | \parindent\z@}
2011 | \def\@secfont{\sffamily\bfseries\section@raggedright\MakeUppercase}
2012 | \def\@subsecfont{\sffamily\bfseries\section@raggedright}
2013 | \def\@subsubsecfont{\sffamily\itshape}
2014 | \def\@parfont{\itshape}
2015 | \setcounter{secnumdepth}{3}
2016 | \ifcase\ACM@format@nr
2017 | \relax % manuscript
2018 | \or % acmsmall
2019 | \or % acmlarge
2020 | \def\@secfont{\sffamily\large\section@raggedright\MakeUppercase}
2021 | \def\@subsecfont{\sffamily\large\section@raggedright}
2022 | \or % acmtog
2023 | \def\@secfont{\sffamily\large\section@raggedright\MakeUppercase}
2024 | \def\@subsecfont{\sffamily\large\section@raggedright}
2025 | \or % sigconf
2026 | \def\@secfont{\bfseries\Large\section@raggedright\MakeUppercase}
2027 | \def\@subsecfont{\bfseries\Large\section@raggedright}
2028 | \or % siggraph
2029 | \def\@secfont{\bfseries\sffamily\Large\section@raggedright\MakeUppercase}
2030 | \def\@subsecfont{\bfseries\sffamily\Large\section@raggedright}
2031 | \or % sigplan
2032 | \def\@secfont{\bfseries\Large\section@raggedright}
2033 | \def\@subsecfont{\bfseries\section@raggedright}
2034 | \renewcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}%
2035 | {-.75\baselineskip \@plus -2\p@ \@minus -.2\p@}%
2036 | {.25\baselineskip}%
2037 | {\@subsubsecfont}}
2038 | \def\@subsubsecfont{\bfseries\section@raggedright}
2039 | \renewcommand\paragraph{\@startsection{paragraph}{4}{\z@}%
2040 | {-.5\baselineskip \@plus -2\p@ \@minus -.2\p@}%
2041 | {-3.5\p@}%
2042 | {\@parfont\@addspaceafter}}
2043 | \def\@parfont{\bfseries\itshape}
2044 | \renewcommand\subparagraph{\@startsection{subparagraph}{5}{\z@}%
2045 | {-.5\baselineskip \@plus -2\p@ \@minus -.2\p@}%
2046 | {-3.5\p@}%
2047 | {\@subparfont\@addspaceafter}}
2048 | \def\@subparfont{\itshape}
2049 | \or % sigchi
2050 | \setcounter{secnumdepth}{1}
2051 | \def\@secfont{\bfseries\sffamily\section@raggedright\MakeUppercase}
2052 | \def\@subsecfont{\bfseries\sffamily\section@raggedright}
2053 | \or % sigchi-a
2054 | \setcounter{secnumdepth}{0}
2055 | \def\@secfont{\bfseries\sffamily\section@raggedright\MakeUppercase}
2056 | \def\@subsecfont{\bfseries\sffamily\section@raggedright}
2057 | \fi
2058 | \def\@adddotafter#1{#1\@addpunct{.}}
2059 | \def\@addspaceafter#1{#1\@addpunct{\enspace}}
2060 | \def\@acmplainbodyfont{\itshape}
2061 | \def\@acmplainindent{\parindent}
2062 | \def\@acmplainheadfont{\scshape}
2063 | \def\@acmplainnotefont{\@empty}
2064 | \ifcase\ACM@format@nr
2065 | \relax % manuscript
2066 | \or % acmsmall
2067 | \or % acmlarge
2068 | \or % acmtog
2069 | \or % sigconf
2070 | \or % siggraph
2071 | \or % sigplan
2072 | \def\@acmplainbodyfont{\itshape}
2073 | \def\@acmplainindent{\z@}
2074 | \def\@acmplainheadfont{\bfseries}
2075 | \def\@acmplainnotefont{\normalfont}
2076 | \or % sigchi
2077 | \or % sigchi-a
2078 | \fi
2079 | \newtheoremstyle{acmplain}%
2080 | {.5\baselineskip\@plus.2\baselineskip
2081 | \@minus.2\baselineskip}% space above
2082 | {.5\baselineskip\@plus.2\baselineskip
2083 | \@minus.2\baselineskip}% space below
2084 | {\@acmplainbodyfont}% body font
2085 | {\@acmplainindent}% indent amount
2086 | {\@acmplainheadfont}% head font
2087 | {.}% punctuation after head
2088 | {.5em}% spacing after head
2089 | {\thmname{#1}\thmnumber{ #2}\thmnote{ {\@acmplainnotefont(#3)}}}% head spec
2090 | \def\@acmdefinitionbodyfont{\normalfont}
2091 | \def\@acmdefinitionindent{\parindent}
2092 | \def\@acmdefinitionheadfont{\itshape}
2093 | \def\@acmdefinitionnotefont{\@empty}
2094 | \ifcase\ACM@format@nr
2095 | \relax % manuscript
2096 | \or % acmsmall
2097 | \or % acmlarge
2098 | \or % acmtog
2099 | \or % sigconf
2100 | \or % siggraph
2101 | \or % sigplan
2102 | \def\@acmdefinitionbodyfont{\normalfont}
2103 | \def\@acmdefinitionindent{\z@}
2104 | \def\@acmdefinitionheadfont{\bfseries}
2105 | \def\@acmdefinitionnotefont{\normalfont}
2106 | \or % sigchi
2107 | \or % sigchi-a
2108 | \fi
2109 | \newtheoremstyle{acmdefinition}%
2110 | {.5\baselineskip\@plus.2\baselineskip
2111 | \@minus.2\baselineskip}% space above
2112 | {.5\baselineskip\@plus.2\baselineskip
2113 | \@minus.2\baselineskip}% space below
2114 | {\@acmdefinitionbodyfont}% body font
2115 | {\@acmdefinitionindent}% indent amount
2116 | {\@acmdefinitionheadfont}% head font
2117 | {.}% punctuation after head
2118 | {.5em}% spacing after head
2119 | {\thmname{#1}\thmnumber{ #2}\thmnote{ {\@acmdefinitionnotefont(#3)}}}% head spec
2120 | \theoremstyle{acmplain}
2121 | \newtheorem{theorem}{Theorem}[section]
2122 | \newtheorem{conjecture}[theorem]{Conjecture}
2123 | \newtheorem{proposition}[theorem]{Proposition}
2124 | \newtheorem{lemma}[theorem]{Lemma}
2125 | \newtheorem{corollary}[theorem]{Corollary}
2126 | \theoremstyle{acmdefinition}
2127 | \newtheorem{example}[theorem]{Example}
2128 | \newtheorem{definition}[theorem]{Definition}
2129 | \theoremstyle{acmplain}
2130 | \def\@proofnamefont{\scshape}
2131 | \def\@proofindent{\indent}
2132 | \ifcase\ACM@format@nr
2133 | \relax % manuscript
2134 | \or % acmsmall
2135 | \or % acmlarge
2136 | \or % acmtog
2137 | \or % sigconf
2138 | \or % siggraph
2139 | \or % sigplan
2140 | \def\@proofnamefont{\itshape}
2141 | \def\@proofindent{\noindent}
2142 | \or % sigchi
2143 | \or % sigchi-a
2144 | \fi
2145 | \renewenvironment{proof}[1][\proofname]{\par
2146 | \pushQED{\qed}%
2147 | \normalfont \topsep6\p@\@plus6\p@\relax
2148 | \trivlist
2149 | \item[\@proofindent\hskip\labelsep
2150 | {\@proofnamefont #1\@addpunct{.}}]\ignorespaces
2151 | }{%
2152 | \popQED\endtrivlist\@endpefalse
2153 | }
2154 | \specialcomment{acks}{%
2155 | \begingroup
2156 | \section*{Acknowledgments}
2157 | \phantomsection\addcontentsline{toc}{section}{Acknowledgments}
2158 | }{%
2159 | \endgroup
2160 | }
2161 | \def\grantsponsor#1#2#3{#2}
2162 | \newcommand\grantnum[3][]{#3%
2163 | \def\@tempa{#1}\ifx\@tempa\@empty\else\space(\url{#1})\fi}
2164 | \if@ACM@screen
2165 | \includecomment{screenonly}
2166 | \excludecomment{printonly}
2167 | \else
2168 | \excludecomment{screenonly}
2169 | \includecomment{printonly}
2170 | \fi
2171 | \if@ACM@anonymous
2172 | \excludecomment{anonsuppress}
2173 | \excludecomment{acks}
2174 | \else
2175 | \includecomment{anonsuppress}
2176 | \fi
2177 | \newcommand\showeprint[2][arxiv]{%
2178 | \def\@tempa{#1}%
2179 | \ifx\@tempa\@empty\def\@tempa{arxiv}\fi
2180 | \def\@tempb{arxiv}%
2181 | \ifx\@tempa\@tempb
2182 | arXiv:\href{http://arxiv.org/abs/#2}{#2}\else arXiv:#2%
2183 | \fi}
2184 | \normalsize\normalfont
2185 | \endinput
2186 | %%
2187 | %% End of file `acmart.cls'.
2188 |
--------------------------------------------------------------------------------
/latexrun:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright (c) 2013, 2014 Austin Clements
4 |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 |
12 | # The above copyright notice and this permission notice shall be included in
13 | # all copies or substantial portions of the Software.
14 |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | # THE SOFTWARE.
22 |
23 | import sys
24 | import os
25 | import errno
26 | import argparse
27 | import shlex
28 | import json
29 | import subprocess
30 | import re
31 | import collections
32 | import hashlib
33 | import shutil
34 | import curses
35 | import filecmp
36 | import io
37 | import traceback
38 | import time
39 |
40 | try:
41 | import fcntl
42 | except ImportError:
43 | # Non-UNIX platform
44 | fcntl = None
45 |
46 | def debug(string, *args):
47 | if debug.enabled:
48 | print(string.format(*args), file=sys.stderr)
49 | debug.enabled = False
50 |
51 | def debug_exc():
52 | if debug.enabled:
53 | traceback.print_exc()
54 |
55 | def main():
56 | # Parse command-line
57 | arg_parser = argparse.ArgumentParser(
58 | description='''A 21st century LaTeX wrapper,
59 | %(prog)s runs latex (and bibtex) the right number of times so you
60 | don't have to,
61 | strips the log spew to make errors visible,
62 | and plays well with standard build tools.''')
63 | arg_parser.add_argument(
64 | '-o', metavar='FILE', dest='output', default=None,
65 | help='Output file name (default: derived from input file)')
66 | arg_parser.add_argument(
67 | '--latex-cmd', metavar='CMD', default='pdflatex',
68 | help='Latex command (default: %(default)s)')
69 | arg_parser.add_argument(
70 | '--latex-args', metavar='ARGS', type=arg_parser_shlex,
71 | help='Additional command-line arguments for latex.'
72 | ' This will be parsed and split using POSIX shell rules.')
73 | arg_parser.add_argument(
74 | '--bibtex-cmd', metavar='CMD', default='bibtex',
75 | help='Bibtex command (default: %(default)s)')
76 | arg_parser.add_argument(
77 | '--bibtex-args', metavar='ARGS', type=arg_parser_shlex,
78 | help='Additional command-line arguments for bibtex')
79 | arg_parser.add_argument(
80 | '--max-iterations', metavar='N', type=int, default=10,
81 | help='Max number of times to run latex before giving up'
82 | ' (default: %(default)s)')
83 | arg_parser.add_argument(
84 | '-W', metavar='(no-)CLASS',
85 | action=ArgParserWarnAction, dest='nowarns', default=set(['underfull']),
86 | help='Enable/disable warning from CLASS, which can be any package name, '
87 | 'LaTeX warning class (e.g., font), bad box type '
88 | '(underfull, overfull, loose, tight), or "all"')
89 | arg_parser.add_argument(
90 | '-O', metavar='DIR', dest='obj_dir', default='latex.out',
91 | help='Directory for intermediate files and control database '
92 | '(default: %(default)s)')
93 | arg_parser.add_argument(
94 | '--color', choices=('auto', 'always', 'never'), default='auto',
95 | help='When to colorize messages')
96 | arg_parser.add_argument(
97 | '--verbose-cmds', action='store_true', default=False,
98 | help='Print commands as they are executed')
99 | arg_parser.add_argument(
100 | '--debug', action='store_true',
101 | help='Enable detailed debug output')
102 | actions = arg_parser.add_argument_group('actions')
103 | actions.add_argument(
104 | '--clean-all', action='store_true', help='Delete output files')
105 | actions.add_argument(
106 | 'file', nargs='?', help='.tex file to compile')
107 | args = arg_parser.parse_args()
108 | if not any([args.clean_all, args.file]):
109 | arg_parser.error('at least one action is required')
110 | args.latex_args = args.latex_args or []
111 | args.bibtex_args = args.bibtex_args or []
112 |
113 | verbose_cmd.enabled = args.verbose_cmds
114 | debug.enabled = args.debug
115 |
116 | # A note about encodings: POSIX encoding is a mess; TeX encoding
117 | # is a disaster. Our goal is to make things no worse, so we want
118 | # byte-accurate round-tripping of TeX messages. Since TeX
119 | # messages are *basically* text, we use strings and
120 | # surrogateescape'ing for both input and output. I'm not fond of
121 | # setting surrogateescape globally, but it's far easier than
122 | # dealing with every place we pass TeX output through.
123 | # Conveniently, JSON can round-trip surrogateescape'd strings, so
124 | # our control database doesn't need special handling.
125 | sys.stdout = io.TextIOWrapper(
126 | sys.stdout.buffer, encoding=sys.stdout.encoding,
127 | errors='surrogateescape', line_buffering=sys.stdout.line_buffering)
128 | sys.stderr = io.TextIOWrapper(
129 | sys.stderr.buffer, encoding=sys.stderr.encoding,
130 | errors='surrogateescape', line_buffering=sys.stderr.line_buffering)
131 |
132 | Message.setup_color(args.color)
133 |
134 | # Open control database.
135 | dbpath = os.path.join(args.obj_dir, '.latexrun.db')
136 | if not os.path.exists(dbpath) and os.path.exists('.latexrun.db'):
137 | # The control database used to live in the source directory.
138 | # Support this for backwards compatibility.
139 | dbpath = '.latexrun.db'
140 | try:
141 | db = DB(dbpath)
142 | except (ValueError, OSError) as e:
143 | print('error opening {}: {}'.format(e.filename if hasattr(e, 'filename')
144 | else dbpath, e),
145 | file=sys.stderr)
146 | debug_exc()
147 | sys.exit(1)
148 |
149 | # Clean
150 | if args.clean_all:
151 | try:
152 | db.do_clean(args.obj_dir)
153 | except OSError as e:
154 | print(e, file=sys.stderr)
155 | debug_exc()
156 | sys.exit(1)
157 |
158 | # Build
159 | if not args.file:
160 | return
161 | task_commit = None
162 | try:
163 | task_latex = LaTeX(db, args.file, args.latex_cmd, args.latex_args,
164 | args.obj_dir, args.nowarns)
165 | task_commit = LaTeXCommit(db, task_latex, args.output)
166 | task_bibtex = BibTeX(db, task_latex, args.bibtex_cmd, args.bibtex_args,
167 | args.nowarns, args.obj_dir)
168 | tasks = [task_latex, task_commit, task_bibtex]
169 | stable = run_tasks(tasks, args.max_iterations)
170 |
171 | # Print final task output and gather exit status
172 | status = 0
173 | for task in tasks:
174 | status = max(task.report(), status)
175 |
176 | if not stable:
177 | print('error: files are still changing after {} iterations; giving up'
178 | .format(args.max_iterations), file=sys.stderr)
179 | status = max(status, 1)
180 | except TaskError as e:
181 | print(str(e), file=sys.stderr)
182 | debug_exc()
183 | status = 1
184 |
185 | # Report final status, if interesting
186 | fstatus = 'There were errors' if task_commit is None else task_commit.status
187 | if fstatus:
188 | output = args.output
189 | if output is None:
190 | if task_latex.get_outname() is not None:
191 | output = os.path.basename(task_latex.get_outname())
192 | else:
193 | output = 'output'
194 | if Message._color:
195 | terminfo.send('bold', ('setaf', 1))
196 | print('{}; {} not updated'.format(fstatus, output))
197 | if Message._color:
198 | terminfo.send('sgr0')
199 | sys.exit(status)
200 |
201 | def arg_parser_shlex(string):
202 | """Argument parser for shell token lists."""
203 | try:
204 | return shlex.split(string)
205 | except ValueError as e:
206 | raise argparse.ArgumentTypeError(str(e)) from None
207 |
208 | class ArgParserWarnAction(argparse.Action):
209 | def __call__(self, parser, namespace, value, option_string=None):
210 | nowarn = getattr(namespace, self.dest)
211 | if value == 'all':
212 | nowarn.clear()
213 | elif value.startswith('no-'):
214 | nowarn.add(value[3:])
215 | else:
216 | nowarn.discard(value)
217 | setattr(namespace, self.dest, nowarn)
218 |
219 | def verbose_cmd(args, cwd=None, env=None):
220 | if verbose_cmd.enabled:
221 | cmd = ' '.join(map(shlex.quote, args))
222 | if cwd is not None:
223 | cmd = '(cd {} && {})'.format(shlex.quote(cwd), cmd)
224 | if env is not None:
225 | for k, v in env.items():
226 | if os.environ.get(k) != v:
227 | cmd = '{}={} {}'.format(k, shlex.quote(v), cmd)
228 | print(cmd, file=sys.stderr)
229 | verbose_cmd.enabled = False
230 |
231 | def mkdir_p(path):
232 | try:
233 | os.makedirs(path)
234 | except OSError as exc:
235 | if exc.errno == errno.EEXIST and os.path.isdir(path):
236 | pass
237 | else: raise
238 |
239 | class DB:
240 | """A latexrun control database."""
241 |
242 | _VERSION = 'latexrun-db-v2'
243 |
244 | def __init__(self, filename):
245 | self.__filename = filename
246 |
247 | # Make sure database directory exists
248 | if os.path.dirname(self.__filename):
249 | os.makedirs(os.path.dirname(self.__filename), exist_ok=True)
250 |
251 | # Lock the database if possible. We don't release this lock
252 | # until the process exits.
253 | lockpath = self.__filename + '.lock'
254 | if fcntl is not None:
255 | lockfd = os.open(lockpath, os.O_CREAT|os.O_WRONLY|os.O_CLOEXEC, 0o666)
256 | # Note that this is actually an fcntl lock, not a lockf
257 | # lock. Don't be fooled.
258 | fcntl.lockf(lockfd, fcntl.LOCK_EX, 1)
259 |
260 | try:
261 | fp = open(filename, 'r')
262 | except FileNotFoundError:
263 | debug('creating new database')
264 | self.__val = {'version': DB._VERSION}
265 | else:
266 | debug('loading database')
267 | self.__val = json.load(fp)
268 | if 'version' not in self.__val:
269 | raise ValueError('file exists, but does not appear to be a latexrun database'.format(filename))
270 | if self.__val['version'] != DB._VERSION:
271 | raise ValueError('unknown database version {!r}'
272 | .format(self.__val['version']))
273 |
274 | def commit(self):
275 | debug('committing database')
276 | # Atomically commit database
277 | tmp_filename = self.__filename + '.tmp'
278 | with open(tmp_filename, 'w') as fp:
279 | json.dump(self.__val, fp, indent=2, separators=(',', ': '))
280 | fp.flush()
281 | os.fsync(fp.fileno())
282 | os.rename(tmp_filename, self.__filename)
283 |
284 | def get_summary(self, task_id):
285 | """Return the recorded summary for the given task or None."""
286 | return self.__val.get('tasks', {}).get(task_id)
287 |
288 | def set_summary(self, task_id, summary):
289 | """Set the summary for the given task."""
290 | self.__val.setdefault('tasks', {})[task_id] = summary
291 |
292 | def add_clean(self, filename):
293 | """Add an output file to be cleaned.
294 |
295 | Unlike the output files recorded in the task summaries,
296 | cleanable files strictly accumulate until a clean is
297 | performed.
298 | """
299 | self.__val.setdefault('clean', {})[filename] = hash_cache.get(filename)
300 |
301 | def do_clean(self, obj_dir=None):
302 | """Remove output files and delete database.
303 |
304 | If obj_dir is not None and it is empty after all files are
305 | removed, it will also be removed.
306 | """
307 |
308 | for f, want_hash in self.__val.get('clean', {}).items():
309 | have_hash = hash_cache.get(f)
310 | if have_hash is not None:
311 | if want_hash == have_hash:
312 | debug('unlinking {}', f)
313 | hash_cache.invalidate(f)
314 | os.unlink(f)
315 | else:
316 | print('warning: {} has changed; not removing'.format(f),
317 | file=sys.stderr)
318 | self.__val = {'version': DB._VERSION}
319 | try:
320 | os.unlink(self.__filename)
321 | except FileNotFoundError:
322 | pass
323 | if obj_dir is not None:
324 | try:
325 | os.rmdir(obj_dir)
326 | except OSError:
327 | pass
328 |
329 | class HashCache:
330 | """Cache of file hashes.
331 |
332 | As latexrun reaches fixed-point, it hashes the same files over and
333 | over, many of which never change. Since hashing is somewhat
334 | expensive, we keep a simple cache of these hashes.
335 | """
336 |
337 | def __init__(self):
338 | self.__cache = {}
339 |
340 | def get(self, filename):
341 | """Return the hash of filename, or * if it was clobbered."""
342 | try:
343 | with open(filename, 'rb') as fp:
344 | st = os.fstat(fp.fileno())
345 | key = (st.st_dev, st.st_ino)
346 | if key in self.__cache:
347 | return self.__cache[key]
348 |
349 | debug('hashing {}', filename)
350 | h = hashlib.sha256()
351 | while True:
352 | block = fp.read(256*1024)
353 | if not len(block):
354 | break
355 | h.update(block)
356 | self.__cache[key] = h.hexdigest()
357 | return self.__cache[key]
358 | except (FileNotFoundError, IsADirectoryError):
359 | return None
360 |
361 | def clobber(self, filename):
362 | """If filename's hash is not known, record an invalid hash.
363 |
364 | This can be used when filename was overwritten before we were
365 | necessarily able to obtain its hash. filename must exist.
366 | """
367 | st = os.stat(filename)
368 | key = (st.st_dev, st.st_ino)
369 | if key not in self.__cache:
370 | self.__cache[key] = '*'
371 |
372 | def invalidate(self, filename):
373 | try:
374 | st = os.stat(filename)
375 | except OSError as e:
376 | # Pessimistically wipe the whole cache
377 | debug('wiping hash cache ({})', e)
378 | self.__cache.clear()
379 | else:
380 | key = (st.st_dev, st.st_ino)
381 | if key in self.__cache:
382 | del self.__cache[key]
383 | hash_cache = HashCache()
384 |
385 | class _Terminfo:
386 | def __init__(self):
387 | self.__tty = os.isatty(sys.stdout.fileno())
388 | if self.__tty:
389 | curses.setupterm()
390 | self.__ti = {}
391 |
392 | def __ensure(self, cap):
393 | if cap not in self.__ti:
394 | if not self.__tty:
395 | string = None
396 | else:
397 | string = curses.tigetstr(cap)
398 | if string is None or b'$<' in string:
399 | # Don't have this capability or it has a pause
400 | string = None
401 | self.__ti[cap] = string
402 | return self.__ti[cap]
403 |
404 | def has(self, *caps):
405 | return all(self.__ensure(cap) is not None for cap in caps)
406 |
407 | def send(self, *caps):
408 | # Flush TextIOWrapper to the binary IO buffer
409 | sys.stdout.flush()
410 | for cap in caps:
411 | # We should use curses.putp here, but it's broken in
412 | # Python3 because it writes directly to C's buffered
413 | # stdout and there's no way to flush that.
414 | if isinstance(cap, tuple):
415 | s = curses.tparm(self.__ensure(cap[0]), *cap[1:])
416 | else:
417 | s = self.__ensure(cap)
418 | sys.stdout.buffer.write(s)
419 | terminfo = _Terminfo()
420 |
421 | class Progress:
422 | _enabled = None
423 |
424 | def __init__(self, prefix):
425 | self.__prefix = prefix
426 | if Progress._enabled is None:
427 | Progress._enabled = (not debug.enabled) and \
428 | terminfo.has('cr', 'el', 'rmam', 'smam')
429 |
430 | def __enter__(self):
431 | self.last = ''
432 | self.update('')
433 | return self
434 |
435 | def __exit__(self, typ, value, traceback):
436 | if Progress._enabled:
437 | # Beginning of line and clear
438 | terminfo.send('cr', 'el')
439 | sys.stdout.flush()
440 |
441 | def update(self, msg):
442 | if not Progress._enabled:
443 | return
444 | out = '[' + self.__prefix + ']'
445 | if msg:
446 | out += ' ' + msg
447 | if out != self.last:
448 | # Beginning of line, clear line, disable wrap
449 | terminfo.send('cr', 'el', 'rmam')
450 | sys.stdout.write(out)
451 | # Enable wrap
452 | terminfo.send('smam')
453 | self.last = out
454 | sys.stdout.flush()
455 |
456 | class Message(collections.namedtuple(
457 | 'Message', 'typ filename lineno msg')):
458 | def emit(self):
459 | if self.filename:
460 | if self.filename.startswith('./'):
461 | finfo = self.filename[2:]
462 | else:
463 | finfo = self.filename
464 | else:
465 | finfo = ''
466 | if self.lineno is not None:
467 | finfo += ':' + str(self.lineno)
468 | finfo += ': '
469 | if self._color:
470 | terminfo.send('bold')
471 | sys.stdout.write(finfo)
472 |
473 | if self.typ != 'info':
474 | if self._color:
475 | terminfo.send(('setaf', 5 if self.typ == 'warning' else 1))
476 | sys.stdout.write(self.typ + ': ')
477 | if self._color:
478 | terminfo.send('sgr0')
479 | sys.stdout.write(self.msg + '\n')
480 |
481 | @classmethod
482 | def setup_color(cls, state):
483 | if state == 'never':
484 | cls._color = False
485 | elif state == 'always':
486 | cls._color = True
487 | elif state == 'auto':
488 | cls._color = terminfo.has('setaf', 'bold', 'sgr0')
489 | else:
490 | raise ValueError('Illegal color state {:r}'.format(state))
491 |
492 |
493 | ##################################################################
494 | # Task framework
495 | #
496 |
497 | terminate_task_loop = False
498 | start_time = time.time()
499 |
500 | def run_tasks(tasks, max_iterations):
501 | """Execute tasks in round-robin order until all are stable.
502 |
503 | This will also exit if terminate_task_loop is true. Tasks may use
504 | this to terminate after a fatal error (even if that fatal error
505 | doesn't necessarily indicate stability; as long as re-running the
506 | task will never eliminate the fatal error).
507 |
508 | Return True if fixed-point is reached or terminate_task_loop is
509 | set within max_iterations iterations.
510 | """
511 |
512 | global terminate_task_loop
513 | terminate_task_loop = False
514 |
515 | nstable = 0
516 | for iteration in range(max_iterations):
517 | for task in tasks:
518 | if task.stable():
519 | nstable += 1
520 | if nstable == len(tasks):
521 | debug('fixed-point reached')
522 | return True
523 | else:
524 | task.run()
525 | nstable = 0
526 | if terminate_task_loop:
527 | debug('terminate_task_loop set')
528 | return True
529 | debug('fixed-point not reached')
530 | return False
531 |
532 | class TaskError(Exception):
533 | pass
534 |
535 | class Task:
536 | """A deterministic computation whose inputs and outputs can be captured."""
537 |
538 | def __init__(self, db, task_id):
539 | self.__db = db
540 | self.__task_id = task_id
541 |
542 | def __debug(self, string, *args):
543 | if debug.enabled:
544 | debug('task {}: {}', self.__task_id, string.format(*args))
545 |
546 | def stable(self):
547 | """Return True if running this task will not affect system state.
548 |
549 | Functionally, let f be the task, and s be the system state.
550 | Then s' = f(s). If it must be that s' == s (that is, f has
551 | reached a fixed point), then this function must return True.
552 | """
553 | last_summary = self.__db.get_summary(self.__task_id)
554 | if last_summary is None:
555 | # Task has never run, so running it will modify system
556 | # state
557 | changed = 'never run'
558 | else:
559 | # If any of the inputs have changed since the last run of
560 | # this task, the result may change, so re-run the task.
561 | # Also, it's possible something else changed an output
562 | # file, in which case we also want to re-run the task, so
563 | # check the outputs, too.
564 | changed = self.__summary_changed(last_summary)
565 |
566 | if changed:
567 | self.__debug('unstable (changed: {})', changed)
568 | return False
569 | else:
570 | self.__debug('stable')
571 | return True
572 |
573 | def __summary_changed(self, summary):
574 | """Test if any inputs changed from summary.
575 |
576 | Returns a string describing the changed input, or None.
577 | """
578 | for dep in summary['deps']:
579 | fn, args, val = dep
580 | method = getattr(self, '_input_' + fn, None)
581 | if method is None:
582 | return 'unknown dependency method {}'.format(fn)
583 | if method == self._input_unstable or method(*args) != val:
584 | return '{}{}'.format(fn, tuple(args))
585 | return None
586 |
587 | def _input(self, name, *args):
588 | """Register an input for this run.
589 |
590 | This calls self._input_(*args) to get the value of this
591 | input. This function should run quickly and return some
592 | projection of system state that affects the result of this
593 | computation.
594 |
595 | Both args and the return value must be JSON serializable.
596 | """
597 | method = getattr(self, '_input_' + name)
598 | val = method(*args)
599 | if [name, args, val] not in self.__deps:
600 | self.__deps.append([name, args, val])
601 | return val
602 |
603 | def run(self):
604 | # Before we run the task, pre-hash any files that were output
605 | # files in the last run. These may be input by this run and
606 | # then clobbered, at which point it will be too late to get an
607 | # input hash. Ideally we would only hash files that were
608 | # *both* input and output files, but latex doesn't tell us
609 | # about input files that didn't exist, so if we start from a
610 | # clean slate, we often require an extra run because we don't
611 | # know a file is input/output until after the second run.
612 | last_summary = self.__db.get_summary(self.__task_id)
613 | if last_summary is not None:
614 | for io_filename in last_summary['output_files']:
615 | self.__debug('pre-hashing {}', io_filename)
616 | hash_cache.get(io_filename)
617 |
618 | # Run the task
619 | self.__debug('running')
620 | self.__deps = []
621 | result = self._execute()
622 |
623 | # Clear cached output file hashes
624 | for filename in result.output_filenames:
625 | hash_cache.invalidate(filename)
626 |
627 | # If the output files change, then the computation needs to be
628 | # re-run, so record them as inputs
629 | for filename in result.output_filenames:
630 | self._input('file', filename)
631 |
632 | # Update task summary in database
633 | self.__db.set_summary(self.__task_id,
634 | self.__make_summary(self.__deps, result))
635 | del self.__deps
636 |
637 | # Add output files to be cleaned
638 | for f in result.output_filenames:
639 | self.__db.add_clean(f)
640 |
641 | try:
642 | self.__db.commit()
643 | except OSError as e:
644 | raise TaskError('error committing control database {}: {}'.format(
645 | getattr(e, 'filename', ''), e)) from e
646 |
647 | def __make_summary(self, deps, run_result):
648 | """Construct a new task summary."""
649 | return {
650 | 'deps': deps,
651 | 'output_files': {f: hash_cache.get(f)
652 | for f in run_result.output_filenames},
653 | 'extra': run_result.extra,
654 | }
655 |
656 | def _execute(self):
657 | """Abstract: Execute this task.
658 |
659 | Subclasses should implement this method to execute this task.
660 | This method must return a RunResult giving the inputs that
661 | were used by the task and the outputs it produced.
662 | """
663 | raise NotImplementedError('Task._execute is abstract')
664 |
665 | def _get_result_extra(self):
666 | """Return the 'extra' result from the previous run, or None."""
667 | summary = self.__db.get_summary(self.__task_id)
668 | if summary is None:
669 | return None
670 | return summary['extra']
671 |
672 | def report(self):
673 | """Report the task's results to stdout and return exit status.
674 |
675 | This may be called when the task has never executed.
676 | Subclasses should override this. The default implementation
677 | reports nothing and returns 0.
678 | """
679 | return 0
680 |
681 | # Standard input functions
682 |
683 | def _input_env(self, var):
684 | return os.environ.get(var)
685 |
686 | def _input_file(self, path):
687 | return hash_cache.get(path)
688 |
689 | def _input_unstable(self):
690 | """Mark this run as unstable, regardless of other inputs."""
691 | return None
692 |
693 | def _input_unknown_input(self):
694 | """An unknown input that may change after latexrun exits.
695 |
696 | This conservatively marks some unknown input that definitely
697 | won't change while latexrun is running, but may change before
698 | the user next runs latexrun. This allows the task to
699 | stabilize during this invocation, but will cause the task to
700 | re-run on the next invocation.
701 | """
702 | return start_time
703 |
704 | class RunResult(collections.namedtuple(
705 | 'RunResult', 'output_filenames extra')):
706 | """The result of a single task execution.
707 |
708 | This captures all files written by the task, and task-specific
709 | results that need to be persisted between runs (for example, to
710 | enable reporting of a task's results).
711 | """
712 | pass
713 |
714 | ##################################################################
715 | # LaTeX task
716 | #
717 |
718 | def normalize_input_path(path):
719 | # Resolve the directory of the input path, but leave the file
720 | # component alone because it affects TeX's behavior.
721 | head, tail = os.path.split(path)
722 | npath = os.path.join(os.path.realpath(head), tail)
723 | return os.path.relpath(path)
724 |
725 | class LaTeX(Task):
726 | def __init__(self, db, tex_filename, cmd, cmd_args, obj_dir, nowarns):
727 | super().__init__(db, 'latex::' + normalize_input_path(tex_filename))
728 | self.__tex_filename = tex_filename
729 | self.__cmd = cmd
730 | self.__cmd_args = cmd_args
731 | self.__obj_dir = obj_dir
732 | self.__nowarns = nowarns
733 |
734 | self.__pass = 0
735 |
736 | def _input_args(self):
737 | # If filename starts with a character the tex command-line
738 | # treats specially, then tweak it so it doesn't.
739 | filename = self.__tex_filename
740 | if filename.startswith(('-', '&', '\\')):
741 | filename = './' + filename
742 | # XXX Put these at the beginning in case the provided
743 | # arguments are malformed. Might want to do a best-effort
744 | # check for incompatible user-provided arguments (note:
745 | # arguments can be given with one or two dashes and those with
746 | # values can use an equals or a space).
747 | return [self.__cmd] + self.__cmd_args + \
748 | ['-interaction', 'nonstopmode', '-recorder',
749 | '-output-directory', self.__obj_dir, filename]
750 |
751 | def _execute(self):
752 | # Run latex
753 | self.__pass += 1
754 | args = self._input('args')
755 | debug('running {}', args)
756 | try:
757 | os.makedirs(self.__obj_dir, exist_ok=True)
758 | except OSError as e:
759 | raise TaskError('failed to create %s: ' % self.__obj_dir + str(e)) \
760 | from e
761 | try:
762 | verbose_cmd(args)
763 | p = subprocess.Popen(args,
764 | stdin=subprocess.DEVNULL,
765 | stdout=subprocess.PIPE,
766 | stderr=subprocess.STDOUT)
767 | stdout, has_errors, missing_includes = self.__feed_terminal(p.stdout)
768 | status = p.wait()
769 | except OSError as e:
770 | raise TaskError('failed to execute latex task: ' + str(e)) from e
771 |
772 | # Register environment variable inputs
773 | for env_var in ['TEXMFOUTPUT', 'TEXINPUTS', 'TEXFORMATS', 'TEXPOOL',
774 | 'TFMFONTS', 'PATH']:
775 | self._input('env', env_var)
776 |
777 | jobname, outname = self.__parse_jobname(stdout)
778 | inputs, outputs = self.__parse_recorder(jobname)
779 |
780 | # LaTeX overwrites its own inputs. Mark its output files as
781 | # clobbered before we hash its input files.
782 | for path in outputs:
783 | # In some abort cases (e.g., >=100 errors), LaTeX claims
784 | # output files that don't actually exist.
785 | if os.path.exists(path):
786 | hash_cache.clobber(path)
787 | # Depend on input files. Task.run pre-hashed outputs from the
788 | # previous run, so if this isn't the first run and as long as
789 | # the set of outputs didn't change, we'll be able to get the
790 | # input hashes, even if they were clobbered.
791 | for path in inputs:
792 | self._input('file', path)
793 |
794 | if missing_includes:
795 | # Missing \includes are tricky. Ideally we'd depend on
796 | # the absence of some file, but in fact we'd have to
797 | # depend on the failure of a whole kpathsea lookup.
798 | # Rather than try to be clever, just mark this as an
799 | # unknown input so we'll run at least once on the next
800 | # invocation.
801 | self._input('unknown_input')
802 |
803 | if not self.__create_outdirs(stdout) and has_errors:
804 | # LaTeX reported unrecoverable errors (other than output
805 | # directory errors, which we just fixed). We could
806 | # continue to stabilize the document, which may change
807 | # some of the other problems reported (but not the
808 | # unrecoverable errors), or we can just abort now and get
809 | # back to the user quickly with the major errors. We opt
810 | # for the latter.
811 | global terminate_task_loop
812 | terminate_task_loop = True
813 | # This error could depend on something we failed to track.
814 | # It would be really confusing if we continued to report
815 | # the error after the user fixed it, so be conservative
816 | # and force a re-run next time.
817 | self._input('unknown_input')
818 |
819 | return RunResult(outputs,
820 | {'jobname': jobname, 'outname': outname,
821 | 'status': status})
822 |
823 | def __feed_terminal(self, stdout):
824 | prefix = 'latex'
825 | if self.__pass > 1:
826 | prefix += ' ({})'.format(self.__pass)
827 | with Progress(prefix) as progress:
828 | buf = []
829 | filt = LaTeXFilter()
830 | while True:
831 | # Use os.read to read only what's available on the pipe,
832 | # without waiting to fill a buffer
833 | data = os.read(stdout.fileno(), 4096)
834 | if not data:
835 | break
836 | # See "A note about encoding" above
837 | data = data.decode('ascii', errors='surrogateescape')
838 | buf.append(data)
839 | filt.feed(data)
840 | file_stack = filt.get_file_stack()
841 | if file_stack:
842 | tos = file_stack[-1]
843 | if tos.startswith('./'):
844 | tos = tos[2:]
845 | progress.update('>' * len(file_stack) + ' ' + tos)
846 | else:
847 | progress.update('')
848 |
849 | # Were there unrecoverable errors?
850 | has_errors = any(msg.typ == 'error' for msg in filt.get_messages())
851 |
852 | return ''.join(buf), has_errors, filt.has_missing_includes()
853 |
854 | def __parse_jobname(self, stdout):
855 | """Extract the job name and output name from latex's output.
856 |
857 | We get these from latex because they depend on complicated
858 | file name parsing rules, are affected by arguments like
859 | -output-directory, and may be just "texput" if things fail
860 | really early. The output name may be None if there were no
861 | pages of output.
862 | """
863 | jobname = outname = None
864 | for m in re.finditer(r'^Transcript written on "?(.*)\.log"?\.$', stdout,
865 | re.MULTILINE | re.DOTALL):
866 | jobname = m.group(1).replace('\n', '')
867 | if jobname is None:
868 | print(stdout, file=sys.stderr)
869 | raise TaskError('failed to extract job name from latex log')
870 | for m in re.finditer(r'^Output written on "?(.*\.[^ ."]+)"? \([0-9]+ page',
871 | stdout, re.MULTILINE | re.DOTALL):
872 | outname = m.group(1).replace('\n', '')
873 | if outname is None and not \
874 | re.search(r'^No pages of output\.$|^! Emergency stop\.$'
875 | r'|^! ==> Fatal error occurred, no output PDF file produced!$',
876 | stdout, re.MULTILINE):
877 | print(stdout, file=sys.stderr)
878 | raise TaskError('failed to extract output name from latex log')
879 |
880 | # LuaTeX (0.76.0) doesn't include the output directory in the
881 | # logged transcript or output file name.
882 | if os.path.basename(jobname) == jobname and \
883 | os.path.exists(os.path.join(self.__obj_dir, jobname + '.log')):
884 | jobname = os.path.join(self.__obj_dir, jobname)
885 | if outname is not None:
886 | outname = os.path.join(self.__obj_dir, outname)
887 |
888 | return jobname, outname
889 |
890 | def __parse_recorder(self, jobname):
891 | """Parse file recorder output."""
892 | # XXX If latex fails because a file isn't found, that doesn't
893 | # go into the .fls file, but creating that file will affect
894 | # the computation, so it should be included as an input.
895 | # Though it's generally true that files can be added earlier
896 | # in search paths and will affect the output without us knowing.
897 | #
898 | # XXX This is a serious problem for bibtex, since the first
899 | # run won't depend on the .bbl file! But maybe the .aux file
900 | # will always cause a re-run, at which point the .bbl will
901 | # exist?
902 | filename = jobname + '.fls'
903 | try:
904 | recorder = open(filename)
905 | except OSError as e:
906 | raise TaskError('failed to open file recorder output: ' + str(e)) \
907 | from e
908 | pwd, inputs, outputs = '', set(), set()
909 | for linenum, line in enumerate(recorder):
910 | parts = line.rstrip('\n').split(' ', 1)
911 | if parts[0] == 'PWD':
912 | pwd = parts[1]
913 | elif parts[0] in ('INPUT', 'OUTPUT'):
914 | if parts[1].startswith('/'):
915 | path = parts[1]
916 | else:
917 | # Try to make "nice" paths, especially for clean
918 | path = os.path.relpath(os.path.join(pwd, parts[1]))
919 | if parts[0] == 'INPUT':
920 | inputs.add(path)
921 | else:
922 | outputs.add(path)
923 | else:
924 | raise TaskError('syntax error on line {} of {}'
925 | .format(linenum, filename))
926 | # Ironically, latex omits the .fls file itself
927 | outputs.add(filename)
928 | return inputs, outputs
929 |
930 | def __create_outdirs(self, stdout):
931 | # In some cases, such as \include'ing a file from a
932 | # subdirectory, TeX will attempt to create files in
933 | # subdirectories of the output directory that don't exist.
934 | # Detect this, create the output directory, and re-run.
935 | m = re.search('^! I can\'t write on file `(.*)\'\\.$', stdout, re.M)
936 | if m and m.group(1).find('/') > 0 and '../' not in m.group(1):
937 | debug('considering creating output sub-directory for {}'.
938 | format(m.group(1)))
939 | subdir = os.path.dirname(m.group(1))
940 | newdir = os.path.join(self.__obj_dir, subdir)
941 | if os.path.isdir(subdir) and not os.path.isdir(newdir):
942 | debug('creating output subdirectory {}'.format(newdir))
943 | try:
944 | mkdir_p(newdir)
945 | except OSError as e:
946 | raise TaskError('failed to create output subdirectory: ' +
947 | str(e)) from e
948 | self._input('unstable')
949 | return True
950 |
951 | def report(self):
952 | extra = self._get_result_extra()
953 | if extra is None:
954 | return 0
955 |
956 | # Parse the log
957 | logfile = open(extra['jobname'] + '.log', 'rt', errors='surrogateescape')
958 | for msg in self.__clean_messages(
959 | LaTeXFilter(self.__nowarns).feed(
960 | logfile.read(), True).get_messages()):
961 | msg.emit()
962 |
963 | # Return LaTeX's exit status
964 | return extra['status']
965 |
966 | def __clean_messages(self, msgs):
967 | """Make some standard log messages more user-friendly."""
968 | have_undefined_reference = False
969 | for msg in msgs:
970 | if msg.msg == '==> Fatal error occurred, no output PDF file produced!':
971 | msg = msg._replace(typ='info',
972 | msg='Fatal error (no output file produced)')
973 | if msg.msg.startswith('[LaTeX] '):
974 | # Strip unnecessary package name
975 | msg = msg._replace(msg=msg.msg.split(' ', 1)[1])
976 | if re.match(r'Reference .* undefined', msg.msg):
977 | have_undefined_reference = True
978 | if have_undefined_reference and \
979 | re.match(r'There were undefined references', msg.msg):
980 | # LaTeX prints this at the end so the user knows it's
981 | # worthwhile looking back at the log. Since latexrun
982 | # makes the earlier messages obvious, this is
983 | # redundant.
984 | continue
985 | yield msg
986 |
987 | def get_tex_filename(self):
988 | return self.__tex_filename
989 |
990 | def get_jobname(self):
991 | extra = self._get_result_extra()
992 | if extra is None:
993 | return None
994 | return extra['jobname']
995 |
996 | def get_outname(self):
997 | extra = self._get_result_extra()
998 | if extra is None:
999 | return None
1000 | return extra['outname']
1001 |
1002 | def get_status(self):
1003 | extra = self._get_result_extra()
1004 | if extra is None:
1005 | return None
1006 | return extra['status']
1007 |
1008 | class LaTeXCommit(Task):
1009 | def __init__(self, db, latex_task, output_path):
1010 | super().__init__(db, 'latex_commit::' +
1011 | normalize_input_path(latex_task.get_tex_filename()))
1012 | self.__latex_task = latex_task
1013 | self.__output_path = output_path
1014 | self.status = 'There were errors'
1015 |
1016 | def _input_latex(self):
1017 | return self.__latex_task.get_status(), self.__latex_task.get_outname()
1018 |
1019 | def _execute(self):
1020 | self.status = 'There were errors'
1021 |
1022 | # If latex succeeded with output, atomically commit the output
1023 | status, outname = self._input('latex')
1024 | if status != 0 or outname is None:
1025 | debug('not committing (status {}, outname {})', status, outname)
1026 | if outname is None:
1027 | self.status = 'No pages of output'
1028 | return RunResult([], None)
1029 |
1030 | commit = self.__output_path or os.path.basename(outname)
1031 | if os.path.abspath(commit) == os.path.abspath(outname):
1032 | debug('skipping commit (outname is commit name)')
1033 | self.status = None
1034 | return RunResult([], None)
1035 |
1036 | try:
1037 | if os.path.exists(commit) and filecmp.cmp(outname, commit):
1038 | debug('skipping commit ({} and {} are identical)',
1039 | outname, commit)
1040 | # To avoid confusion, touch the output file
1041 | open(outname, 'r+b').close()
1042 | else:
1043 | debug('commiting {} to {}', outname, commit)
1044 | shutil.copy(outname, outname + '~')
1045 | os.rename(outname + '~', commit)
1046 | except OSError as e:
1047 | raise TaskError('error committing latex output: {}'.format(e)) from e
1048 | self._input('file', outname)
1049 | self.status = None
1050 | return RunResult([commit], None)
1051 |
1052 | class LaTeXFilter:
1053 | TRACE = False # Set to enable detailed parse tracing
1054 |
1055 | def __init__(self, nowarns=[]):
1056 | self.__data = ''
1057 | self.__restart_pos = 0
1058 | self.__restart_file_stack = []
1059 | self.__restart_messages_len = 0
1060 | self.__messages = []
1061 | self.__first_file = None
1062 | self.__fatal_error = False
1063 | self.__missing_includes = False
1064 | self.__pageno = 1
1065 | self.__restart_pageno = 1
1066 |
1067 | self.__suppress = {cls: 0 for cls in nowarns}
1068 |
1069 | def feed(self, data, eof=False):
1070 | """Feed LaTeX log data to the parser.
1071 |
1072 | The log data can be from LaTeX's standard output, or from the
1073 | log file. If there will be no more data, set eof to True.
1074 | """
1075 |
1076 | self.__data += data
1077 | self.__data_complete = eof
1078 |
1079 | # Reset to last known-good restart point
1080 | self.__pos = self.__restart_pos
1081 | self.__file_stack = self.__restart_file_stack.copy()
1082 | self.__messages = self.__messages[:self.__restart_messages_len]
1083 | self.__lstart = self.__lend = -1
1084 | self.__pageno = self.__restart_pageno
1085 |
1086 | # Parse forward
1087 | while self.__pos < len(self.__data):
1088 | self.__noise()
1089 |
1090 | # Handle suppressed warnings
1091 | if eof:
1092 | msgs = ['%d %s warning%s' % (count, cls, "s" if count > 1 else "")
1093 | for cls, count in self.__suppress.items() if count]
1094 | if msgs:
1095 | self.__message('info', None,
1096 | '%s not shown (use -Wall to show them)' %
1097 | ', '.join(msgs), filename=self.__first_file)
1098 |
1099 | if eof and len(self.__file_stack) and not self.__fatal_error:
1100 | # Fatal errors generally cause TeX to "succumb" without
1101 | # closing the file stack, so don't complain in that case.
1102 | self.__message('warning', None,
1103 | "unbalanced `(' in log; file names may be wrong")
1104 | return self
1105 |
1106 | def get_messages(self):
1107 | """Return a list of warning and error Messages."""
1108 | return self.__messages
1109 |
1110 | def get_file_stack(self):
1111 | """Return the file stack for the data that has been parsed.
1112 |
1113 | This results a list from outermost file to innermost file.
1114 | The list may be empty.
1115 | """
1116 |
1117 | return self.__file_stack
1118 |
1119 | def has_missing_includes(self):
1120 | """Return True if the log reported missing \\include files."""
1121 | return self.__missing_includes
1122 |
1123 | def __save_restart_point(self):
1124 | """Save the current state as a known-good restart point.
1125 |
1126 | On the next call to feed, the parser will reset to this point.
1127 | """
1128 | self.__restart_pos = self.__pos
1129 | self.__restart_file_stack = self.__file_stack.copy()
1130 | self.__restart_messages_len = len(self.__messages)
1131 | self.__restart_pageno = self.__pageno
1132 |
1133 | def __message(self, typ, lineno, msg, cls=None, filename=None):
1134 | if cls is not None and cls in self.__suppress:
1135 | self.__suppress[cls] += 1
1136 | return
1137 | filename = filename or (self.__file_stack[-1] if self.__file_stack
1138 | else self.__first_file)
1139 | self.__messages.append(Message(typ, filename, lineno, msg))
1140 |
1141 | def __ensure_line(self):
1142 | """Update lstart and lend."""
1143 | if self.__lstart <= self.__pos < self.__lend:
1144 | return
1145 | self.__lstart = self.__data.rfind('\n', 0, self.__pos) + 1
1146 | self.__lend = self.__data.find('\n', self.__pos) + 1
1147 | if self.__lend == 0:
1148 | self.__lend = len(self.__data)
1149 |
1150 | @property
1151 | def __col(self):
1152 | """The 0-based column number of __pos."""
1153 | self.__ensure_line()
1154 | return self.__pos - self.__lstart
1155 |
1156 | @property
1157 | def __avail(self):
1158 | return self.__pos < len(self.__data)
1159 |
1160 | def __lookingat(self, needle):
1161 | return self.__data.startswith(needle, self.__pos)
1162 |
1163 | def __lookingatre(self, regexp, flags=0):
1164 | return re.compile(regexp, flags=flags).match(self.__data, self.__pos)
1165 |
1166 | def __skip_line(self):
1167 | self.__ensure_line()
1168 | self.__pos = self.__lend
1169 |
1170 | def __consume_line(self, unwrap=False):
1171 | self.__ensure_line()
1172 | data = self.__data[self.__pos:self.__lend]
1173 | self.__pos = self.__lend
1174 | if unwrap:
1175 | # TeX helpfully wraps all terminal output at 79 columns
1176 | # (max_print_line). If requested, unwrap it. There's
1177 | # simply no way to do this perfectly, since there could be
1178 | # a line that happens to be 79 columns.
1179 | #
1180 | # We check for >=80 because a bug in LuaTeX causes it to
1181 | # wrap at 80 columns instead of 79 (LuaTeX #900).
1182 | while self.__lend - self.__lstart >= 80:
1183 | if self.TRACE: print('<{}> wrapping'.format(self.__pos))
1184 | self.__ensure_line()
1185 | data = data[:-1] + self.__data[self.__pos:self.__lend]
1186 | self.__pos = self.__lend
1187 | return data
1188 |
1189 | # Parser productions
1190 |
1191 | def __noise(self):
1192 | # Most of TeX's output is line noise that combines error
1193 | # messages, warnings, file names, user errors and warnings,
1194 | # and echos of token lists and other input. This attempts to
1195 | # tease these apart, paying particular attention to all of the
1196 | # places where TeX echos input so that parens in the input do
1197 | # not confuse the file name scanner. There are three
1198 | # functions in TeX that echo input: show_token_list (used by
1199 | # runaway and show_context, which is used by print_err),
1200 | # short_display (used by overfull/etc h/vbox), and show_print
1201 | # (used in issue_message and the same places as
1202 | # show_token_list).
1203 | lookingat, lookingatre = self.__lookingat, self.__lookingatre
1204 | if self.__col == 0:
1205 | # The following messages are always preceded by a newline
1206 | if lookingat('! '):
1207 | return self.__errmessage()
1208 | if lookingat('!pdfTeX error: '):
1209 | return self.__pdftex_fail()
1210 | if lookingat('Runaway '):
1211 | return self.__runaway()
1212 | if lookingatre(r'(Overfull|Underfull|Loose|Tight) \\[hv]box \('):
1213 | return self.__bad_box()
1214 | if lookingatre('(Package |Class |LaTeX |pdfTeX )?(\w+ )?warning: ', re.I):
1215 | return self.__generic_warning()
1216 | if lookingatre('No file .*\\.tex\\.$', re.M):
1217 | # This happens with \includes of missing files. For
1218 | # whatever reason, LaTeX doesn't consider this even
1219 | # worth a warning, but I do!
1220 | self.__message('warning', None,
1221 | self.__simplify_message(
1222 | self.__consume_line(unwrap=True).strip()))
1223 | self.__missing_includes = True
1224 | return
1225 | # Other things that are common and irrelevant
1226 | if lookingatre(r'(Package|Class|LaTeX) (\w+ )?info: ', re.I):
1227 | return self.__generic_info()
1228 | if lookingatre(r'(Document Class|File|Package): '):
1229 | # Output from "\ProvidesX"
1230 | return self.__consume_line(unwrap=True)
1231 | if lookingatre(r'\\\w+=\\[a-z]+\d+\n'):
1232 | # Output from "\new{count,dimen,skip,...}"
1233 | return self.__consume_line(unwrap=True)
1234 |
1235 | # print(self.__data[self.__lstart:self.__lend].rstrip())
1236 | # self.__pos = self.__lend
1237 | # return
1238 |
1239 | # Now that we've substantially reduced the spew and hopefully
1240 | # eliminated all input echoing, we're left with the file name
1241 | # stack, page outs, and random other messages from both TeX
1242 | # and various packages. We'll assume at this point that all
1243 | # parentheses belong to the file name stack or, if they're in
1244 | # random other messages, they're at least balanced and nothing
1245 | # interesting happens between them. For page outs, ship_out
1246 | # prints a space if not at the beginning of a line, then a
1247 | # "[", then the page number being shipped out (this is
1248 | # usually, but not always, followed by "]").
1249 | m = re.compile(r'[(){}\n]|(?<=[\n ])\[\d+', re.M).\
1250 | search(self.__data, self.__pos)
1251 | if m is None:
1252 | self.__pos = len(self.__data)
1253 | return
1254 | self.__pos = m.start() + 1
1255 | ch = self.__data[m.start()]
1256 | if ch == '\n':
1257 | # Save this as a known-good restart point for incremental
1258 | # parsing, since we definitely didn't match any of the
1259 | # known message types above.
1260 | self.__save_restart_point()
1261 | elif ch == '[':
1262 | # This is printed at the end of a page, so we're beginning
1263 | # page n+1.
1264 | self.__pageno = int(self.__lookingatre(r'\d+').group(0)) + 1
1265 | elif ((self.__data.startswith('`', m.start() - 1) or
1266 | self.__data.startswith('`\\', m.start() - 2)) and
1267 | self.__data.startswith('\'', m.start() + 1)):
1268 | # (, ), {, and } sometimes appear in TeX's error
1269 | # descriptions, but they're always in `'s (and sometimes
1270 | # backslashed)
1271 | return
1272 | elif ch == '(':
1273 | # XXX Check that the stack doesn't drop to empty and then re-grow
1274 | first = self.__first_file is None and self.__col == 1
1275 | filename = self.__filename()
1276 | self.__file_stack.append(filename)
1277 | if first:
1278 | self.__first_file = filename
1279 | if self.TRACE:
1280 | print('<{}>{}enter {}'.format(
1281 | m.start(), ' '*len(self.__file_stack), filename))
1282 | elif ch == ')':
1283 | if len(self.__file_stack):
1284 | if self.TRACE:
1285 | print('<{}>{}exit {}'.format(
1286 | m.start(), ' '*len(self.__file_stack),
1287 | self.__file_stack[-1]))
1288 | self.__file_stack.pop()
1289 | else:
1290 | self.__message('warning', None,
1291 | "extra `)' in log; file names may be wrong ")
1292 | elif ch == '{':
1293 | # TeX uses this for various things we want to ignore, like
1294 | # file names and print_mark. Consume up to the '}'
1295 | epos = self.__data.find('}', self.__pos)
1296 | if epos != -1:
1297 | self.__pos = epos + 1
1298 | else:
1299 | self.__message('warning', None,
1300 | "unbalanced `{' in log; file names may be wrong")
1301 | elif ch == '}':
1302 | self.__message('warning', None,
1303 | "extra `}' in log; file names may be wrong")
1304 |
1305 | def __filename(self):
1306 | initcol = self.__col
1307 | first = True
1308 | name = ''
1309 | # File names may wrap, but if they do, TeX will always print a
1310 | # newline before the open paren
1311 | while first or (initcol == 1 and self.__lookingat('\n')
1312 | and self.__col >= 79):
1313 | if not first:
1314 | self.__pos += 1
1315 | m = self.__lookingatre(r'[^(){} \n]*')
1316 | name += m.group()
1317 | self.__pos = m.end()
1318 | first = False
1319 | return name
1320 |
1321 | def __simplify_message(self, msg):
1322 | msg = re.sub(r'^(?:Package |Class |LaTeX |pdfTeX )?([^ ]+) (?:Error|Warning): ',
1323 | r'[\1] ', msg, flags=re.I)
1324 | msg = re.sub(r'\.$', '', msg)
1325 | msg = re.sub(r'has occurred (while \\output is active)', r'\1', msg)
1326 | return msg
1327 |
1328 | def __errmessage(self):
1329 | # Procedure print_err (including \errmessage, itself used by
1330 | # LaTeX's \GenericError and all of its callers), as well as
1331 | # fatal_error. Prints "\n! " followed by error text
1332 | # ("Emergency stop" in the case of fatal_error). print_err is
1333 | # always followed by a call to error, which prints a period,
1334 | # and a newline...
1335 | msg = self.__consume_line(unwrap=True)[1:].strip()
1336 | is_fatal_error = (msg == 'Emergency stop.')
1337 | msg = self.__simplify_message(msg)
1338 | # ... and then calls show_context, which prints the input
1339 | # stack as pairs of lines giving the context. These context
1340 | # lines are truncated so they never wrap. Each pair of lines
1341 | # will start with either " " if the context is a
1342 | # token list, "<*> " for terminal input (or command line),
1343 | # "" for stream reads, something like "\macroname
1344 | # #1->" for macros (though everything after \macroname is
1345 | # subject to being elided as "..."), or "l.[0-9]+ " if it's a
1346 | # file. This is followed by the errant input with a line
1347 | # break where the error occurred.
1348 | lineno = None
1349 | found_context = False
1350 | stack = []
1351 | while self.__avail:
1352 | m1 = self.__lookingatre(r'<([a-z ]+|\*|read [^ >]*)> |\\.*(->|...)')
1353 | m2 = self.__lookingatre('l\.[0-9]+ ')
1354 | if m1:
1355 | found_context = True
1356 | pre = self.__consume_line().rstrip('\n')
1357 | stack.append(pre)
1358 | elif m2:
1359 | found_context = True
1360 | pre = self.__consume_line().rstrip('\n')
1361 | info, rest = pre.split(' ', 1)
1362 | lineno = int(info[2:])
1363 | stack.append(rest)
1364 | elif found_context:
1365 | # Done with context
1366 | break
1367 | if found_context:
1368 | # Consume the second context line
1369 | post = self.__consume_line().rstrip('\n')
1370 | # Clean up goofy trailing ^^M TeX sometimes includes
1371 | post = re.sub(r'\^\^M$', '', post)
1372 | if post[:len(pre)].isspace() and not post.isspace():
1373 | stack.append(len(stack[-1]))
1374 | stack[-2] += post[len(pre):]
1375 | else:
1376 | # If we haven't found the context, skip the line.
1377 | self.__skip_line()
1378 | stack_msg = ''
1379 | for i, trace in enumerate(stack):
1380 | stack_msg += ('\n ' + (' ' * trace) + '^'
1381 | if isinstance(trace, int) else
1382 | '\n at ' + trace.rstrip() if i == 0 else
1383 | '\n from ' + trace.rstrip())
1384 |
1385 | if is_fatal_error:
1386 | # fatal_error always prints one additional line of message
1387 | info = self.__consume_line().strip()
1388 | if info.startswith('*** '):
1389 | info = info[4:]
1390 | msg += ': ' + info.lstrip('(').rstrip(')')
1391 |
1392 | self.__message('error', lineno, msg + stack_msg)
1393 | self.__fatal_error = True
1394 |
1395 | def __pdftex_fail(self):
1396 | # Procedure pdftex_fail. Prints "\n!pdfTeX error: ", the
1397 | # message, and a newline. Unlike print_err, there's never
1398 | # context.
1399 | msg = self.__consume_line(unwrap=True)[1:].strip()
1400 | msg = self.__simplify_message(msg)
1401 | self.__message('error', None, msg)
1402 |
1403 | def __runaway(self):
1404 | # Procedure runaway. Prints "\nRunaway ...\n" possibly
1405 | # followed by token list (user text). Always followed by a
1406 | # call to print_err, so skip lines until we see the print_err.
1407 | self.__skip_line() # Skip "Runaway ...\n"
1408 | if not self.__lookingat('! ') and self.__avail:
1409 | # Skip token list, which is limited to one line
1410 | self.__skip_line()
1411 |
1412 | def __bad_box(self):
1413 | # Function hpack and vpack. hpack prints a warning, a
1414 | # newline, then a short_display of the offending text.
1415 | # Unfortunately, there's nothing indicating the end of the
1416 | # offending text, but it should be on one (possible wrapped)
1417 | # line. vpack prints a warning and then, *unless output is
1418 | # active*, a newline. The missing newline is probably a bug,
1419 | # but it sure makes our lives harder.
1420 | origpos = self.__pos
1421 | msg = self.__consume_line()
1422 | m = re.search(r' in (?:paragraph|alignment) at lines ([0-9]+)--([0-9]+)', msg) or \
1423 | re.search(r' detected at line ([0-9]+)', msg)
1424 | if m:
1425 | # Sometimes TeX prints crazy line ranges like "at lines
1426 | # 8500--250". The lower number seems roughly sane, so use
1427 | # that. I'm not sure what causes this, but it may be
1428 | # related to shipout routines messing up line registers.
1429 | lineno = min(int(m.group(1)), int(m.groups()[-1]))
1430 | msg = msg[:m.start()]
1431 | else:
1432 | m = re.search(r' while \\output is active', msg)
1433 | if m:
1434 | lineno = None
1435 | msg = msg[:m.end()]
1436 | else:
1437 | self.__message('warning', None,
1438 | 'malformed bad box message in log')
1439 | return
1440 | # Back up to the end of the known message text
1441 | self.__pos = origpos + m.end()
1442 | if self.__lookingat('\n'):
1443 | # We have a newline, so consume it and look for the
1444 | # offending text.
1445 | self.__pos += 1
1446 | # If there is offending text, it will start with a font
1447 | # name, which will start with a \.
1448 | if 'hbox' in msg and self.__lookingat('\\'):
1449 | self.__consume_line(unwrap=True)
1450 | msg = self.__simplify_message(msg) + ' (page {})'.format(self.__pageno)
1451 | cls = msg.split(None, 1)[0].lower()
1452 | self.__message('warning', lineno, msg, cls=cls)
1453 |
1454 | def __generic_warning(self):
1455 | # Warnings produced by LaTeX's \GenericWarning (which is
1456 | # called by \{Package,Class}Warning and \@latex@warning),
1457 | # warnings produced by pdftex_warn, and other random warnings.
1458 | msg, cls = self.__generic_info()
1459 | # Most warnings include an input line emitted by \on@line
1460 | m = re.search(' on input line ([0-9]+)', msg)
1461 | if m:
1462 | lineno = int(m.group(1))
1463 | msg = msg[:m.start()]
1464 | else:
1465 | lineno = None
1466 | msg = self.__simplify_message(msg)
1467 | self.__message('warning', lineno, msg, cls=cls)
1468 |
1469 | def __generic_info(self):
1470 | # Messages produced by LaTeX's \Generic{Error,Warning,Info}
1471 | # and things that look like them
1472 | msg = self.__consume_line(unwrap=True).strip()
1473 | # Package and class messages are continued with lines
1474 | # containing '(package name) '
1475 | pkg_name = msg.split(' ', 2)[1]
1476 | prefix = '(' + pkg_name + ') '
1477 | while self.__lookingat(prefix):
1478 | # Collect extra lines. It's important that we keep these
1479 | # because they may contain context information like line
1480 | # numbers.
1481 | extra = self.__consume_line(unwrap=True)
1482 | msg += ' ' + extra[len(prefix):].strip()
1483 | return msg, pkg_name.lower()
1484 |
1485 | ##################################################################
1486 | # BibTeX task
1487 | #
1488 |
1489 | class BibTeX(Task):
1490 | def __init__(self, db, latex_task, cmd, cmd_args, nowarns, obj_dir):
1491 | super().__init__(db, 'bibtex::' + normalize_input_path(
1492 | latex_task.get_tex_filename()))
1493 | self.__latex_task = latex_task
1494 | self.__cmd = cmd
1495 | self.__cmd_args = cmd_args
1496 | self.__obj_dir = obj_dir
1497 |
1498 | def stable(self):
1499 | # If bibtex doesn't have its inputs, then it's stable because
1500 | # it has no effect on system state.
1501 | jobname = self.__latex_task.get_jobname()
1502 | if jobname is None:
1503 | # We don't know where the .aux file is until latex has run
1504 | return True
1505 | if not os.path.exists(jobname + '.aux'):
1506 | # Input isn't ready, so bibtex will simply fail without
1507 | # affecting system state. Hence, this task is trivially
1508 | # stable.
1509 | return True
1510 | if not self.__find_bib_cmds(os.path.dirname(jobname), jobname + '.aux'):
1511 | # The tex file doesn't refer to any bibliographic data, so
1512 | # don't run bibtex.
1513 | return True
1514 |
1515 | return super().stable()
1516 |
1517 | def __find_bib_cmds(self, basedir, auxname, stack=()):
1518 | debug('scanning for bib commands in {}'.format(auxname))
1519 | if auxname in stack:
1520 | raise TaskError('.aux file loop')
1521 | stack = stack + (auxname,)
1522 |
1523 | try:
1524 | aux_data = open(auxname, errors='surrogateescape').read()
1525 | except FileNotFoundError:
1526 | # The aux file may not exist if latex aborted
1527 | return False
1528 | if re.search(r'^\\bibstyle\{', aux_data, flags=re.M) or \
1529 | re.search(r'^\\bibdata\{', aux_data, flags=re.M):
1530 | return True
1531 |
1532 | if re.search(r'^\\abx@aux@cite\{', aux_data, flags=re.M):
1533 | # biber citation
1534 | return True
1535 |
1536 | # Recurse into included aux files (see aux_input_command), in
1537 | # case \bibliography appears in an \included file.
1538 | for m in re.finditer(r'^\\@input\{([^}]*)\}', aux_data, flags=re.M):
1539 | if self.__find_bib_cmds(basedir, os.path.join(basedir, m.group(1)),
1540 | stack):
1541 | return True
1542 |
1543 | return False
1544 |
1545 | def _input_args(self):
1546 | if self.__is_biber():
1547 | aux_name = os.path.basename(self.__latex_task.get_jobname())
1548 | else:
1549 | aux_name = os.path.basename(self.__latex_task.get_jobname()) + '.aux'
1550 | return [self.__cmd] + self.__cmd_args + [aux_name]
1551 |
1552 | def _input_cwd(self):
1553 | return os.path.dirname(self.__latex_task.get_jobname())
1554 |
1555 | def _input_auxfile(self, auxname):
1556 | # We don't consider the .aux files regular inputs.
1557 | # Instead, we extract just the bit that BibTeX cares about
1558 | # and depend on that. See get_aux_command_and_process in
1559 | # bibtex.web.
1560 | debug('hashing filtered aux file {}', auxname)
1561 | try:
1562 | with open(auxname, 'rb') as aux:
1563 | h = hashlib.sha256()
1564 | for line in aux:
1565 | if line.startswith((b'\\citation{', b'\\bibdata{',
1566 | b'\\bibstyle{', b'\\@input{',
1567 | b'\\abx@aux@cite{')):
1568 | h.update(line)
1569 | return h.hexdigest()
1570 | except FileNotFoundError:
1571 | debug('{} does not exist', auxname)
1572 | return None
1573 |
1574 | def __path_join(self, first, rest):
1575 | if rest is None:
1576 | # Append ':' to keep the default search path
1577 | return first + ':'
1578 | return first + ':' + rest
1579 |
1580 | def __is_biber(self):
1581 | return "biber" in self.__cmd
1582 |
1583 | def _execute(self):
1584 | # This gets complicated when \include is involved. \include
1585 | # switches to a different aux file and records its path in the
1586 | # main aux file. However, BibTeX does not consider this path
1587 | # to be relative to the location of the main aux file, so we
1588 | # have to run BibTeX *in the output directory* for it to
1589 | # follow these includes (there's no way to tell BibTeX other
1590 | # locations to search). Unfortunately, this means BibTeX will
1591 | # no longer be able to find local bib or bst files, but so we
1592 | # tell it where to look by setting BIBINPUTS and BSTINPUTS
1593 | # (luckily we can control this search). We have to pass this
1594 | # same environment down to Kpathsea when we resolve the paths
1595 | # in BibTeX's log.
1596 | args, cwd = self._input('args'), self._input('cwd')
1597 | debug('running {} in {}', args, cwd)
1598 |
1599 | env = os.environ.copy()
1600 | env['BIBINPUTS'] = self.__path_join(os.getcwd(), env.get('BIBINPUTS'))
1601 | env['BSTINPUTS'] = self.__path_join(os.getcwd(), env.get('BSTINPUTS'))
1602 |
1603 | try:
1604 | verbose_cmd(args, cwd, env)
1605 | p = subprocess.Popen(args, cwd=cwd, env=env,
1606 | stdin=subprocess.DEVNULL,
1607 | stdout=subprocess.PIPE,
1608 | stderr=subprocess.STDOUT)
1609 | stdout = self.__feed_terminal(p.stdout)
1610 | status = p.wait()
1611 | except OSError as e:
1612 | raise TaskError('failed to execute bibtex task: ' + str(e)) from e
1613 |
1614 | inputs, auxnames, outbase = self.__parse_inputs(stdout, cwd, env)
1615 | if not inputs and not auxnames:
1616 | # BibTeX failed catastrophically.
1617 | print(stdout, file=sys.stderr)
1618 | raise TaskError('failed to execute bibtex task')
1619 |
1620 | # Register environment variable inputs
1621 | for env_var in ['TEXMFOUTPUT', 'BSTINPUTS', 'BIBINPUTS', 'PATH']:
1622 | self._input('env', env_var)
1623 |
1624 | # Register file inputs
1625 | for path in auxnames:
1626 | self._input('auxfile', path)
1627 | for path in inputs:
1628 | self._input('file', path)
1629 |
1630 | if self.__is_biber():
1631 | outbase = os.path.join(cwd, outbase)
1632 | outputs = [outbase + '.bbl', outbase + '.blg']
1633 | return RunResult(outputs, {'outbase': outbase, 'status': status,
1634 | 'inputs': inputs})
1635 |
1636 | def __feed_terminal(self, stdout):
1637 | with Progress('bibtex') as progress:
1638 | buf, linebuf = [], ''
1639 | while True:
1640 | data = os.read(stdout.fileno(), 4096)
1641 | if not data:
1642 | break
1643 | # See "A note about encoding" above
1644 | data = data.decode('ascii', errors='surrogateescape')
1645 | buf.append(data)
1646 | linebuf += data
1647 | while '\n' in linebuf:
1648 | line, _, linebuf = linebuf.partition('\n')
1649 | if line.startswith('Database file'):
1650 | progress.update(line.split(': ', 1)[1])
1651 | return ''.join(buf)
1652 |
1653 | def __parse_inputs(self, log, cwd, env):
1654 | # BibTeX conveniently logs every file that it opens, and its
1655 | # log is actually sensible (see calls to a_open_in in
1656 | # bibtex.web.) The only trick is that these file names are
1657 | # pre-kpathsea lookup and may be relative to the directory we
1658 | # ran BibTeX in.
1659 | #
1660 | # Because BibTeX actually depends on very little in the .aux
1661 | # file (and it's likely other things will change in the .aux
1662 | # file), we don't count the whole .aux file as an input, but
1663 | # instead depend only on the lines that matter to BibTeX.
1664 | kpathsea = Kpathsea('bibtex')
1665 | inputs = []
1666 | auxnames = []
1667 | outbase = None
1668 | for line in log.splitlines():
1669 | m = re.match('(?:The top-level auxiliary file:'
1670 | '|A level-[0-9]+ auxiliary file:) (.*)', line)
1671 | if m:
1672 | auxnames.append(os.path.join(cwd, m.group(1)))
1673 | continue
1674 | m = re.match('(?:(The style file:)|(Database file #[0-9]+:)) (.*)',
1675 | line)
1676 | if m:
1677 | filename = m.group(3)
1678 | if m.group(1):
1679 | filename = kpathsea.find_file(filename, 'bst', cwd, env)
1680 | elif m.group(2):
1681 | filename = kpathsea.find_file(filename, 'bib', cwd, env)
1682 |
1683 | # If this path is relative to the source directory,
1684 | # clean it up for error reporting and portability of
1685 | # the dependency DB
1686 | if filename.startswith('/'):
1687 | relname = os.path.relpath(filename)
1688 | if '../' not in relname:
1689 | filename = relname
1690 |
1691 | inputs.append(filename)
1692 |
1693 | # biber output
1694 | m = re.search("Found BibTeX data source '(.*?)'",
1695 | line)
1696 | if m:
1697 | filename = m.group(1)
1698 | inputs.append(filename)
1699 |
1700 | m = re.search("Logfile is '(.*?)'", line)
1701 | if m:
1702 | outbase = m.group(1)[:-4]
1703 |
1704 | if outbase is None:
1705 | outbase = auxnames[0][:-4]
1706 |
1707 | return inputs, auxnames, outbase
1708 |
1709 | def report(self):
1710 | extra = self._get_result_extra()
1711 | if extra is None:
1712 | return 0
1713 |
1714 | # Parse and pretty-print the log
1715 | log = open(extra['outbase'] + '.blg', 'rt').read()
1716 | inputs = extra['inputs']
1717 | for msg in BibTeXFilter(log, inputs).get_messages():
1718 | msg.emit()
1719 |
1720 | # BibTeX exits with 1 if there are warnings, 2 if there are
1721 | # errors, and 3 if there are fatal errors (sysdep.h).
1722 | # Translate to a normal UNIX exit status.
1723 | if extra['status'] >= 2:
1724 | return 1
1725 | return 0
1726 |
1727 | class BibTeXFilter:
1728 | def __init__(self, data, inputs):
1729 | self.__inputs = inputs
1730 | self.__key_locs = None
1731 |
1732 | self.__messages = []
1733 |
1734 | prev_line = ''
1735 | for line in data.splitlines():
1736 | msg = self.__process_line(prev_line, line)
1737 | if msg is not None:
1738 | self.__messages.append(Message(*msg))
1739 | prev_line = line
1740 |
1741 | def get_messages(self):
1742 | """Return a list of warning and error Messages."""
1743 | # BibTeX reports most errors in no particular order. Sort by
1744 | # file and line.
1745 | return sorted(self.__messages,
1746 | key=lambda msg: (msg.filename or '', msg.lineno or 0))
1747 |
1748 | def __process_line(self, prev_line, line):
1749 | m = None
1750 | def match(regexp):
1751 | nonlocal m
1752 | m = re.match(regexp, line)
1753 | return m
1754 |
1755 | # BibTeX has many error paths, but luckily the set is closed,
1756 | # so we can find all of them. This first case is the
1757 | # workhorse format.
1758 | #
1759 | # AUX errors: aux_err/aux_err_return/aux_err_print
1760 | #
1761 | # BST errors: bst_ln_num_print/bst_err/
1762 | # bst_err_print_and_look_for_blank_line_return/
1763 | # bst_warn_print/bst_warn/
1764 | # skip_token/skip_token_print/
1765 | # bst_ext_warn/bst_ext_warn_print/
1766 | # bst_ex_warn/bst_ex_warn_print/
1767 | # bst_mild_ex_warn/bst_mild_ex_warn_print/
1768 | # bst_string_size_exceeded
1769 | #
1770 | # BIB errors: bib_ln_num_print/
1771 | # bib_err_print/bib_err/
1772 | # bib_warn_print/bib_warn/
1773 | # bib_one_of_two_expected_err/macro_name_warning/
1774 | if match('(.*?)---?line ([0-9]+) of file (.*)'):
1775 | # Sometimes the real error is printed on the previous line
1776 | if m.group(1) == 'while executing':
1777 | # bst_ex_warn. The real message is on the previous line
1778 | text = prev_line
1779 | else:
1780 | text = m.group(1) or prev_line
1781 | typ, msg = self.__canonicalize(text)
1782 | return (typ, m.group(3), int(m.group(2)), msg)
1783 |
1784 | # overflow/print_overflow
1785 | if match('Sorry---you\'ve exceeded BibTeX\'s (.*)'):
1786 | return ('error', None, None, 'capacity exceeded: ' + m.group(1))
1787 | # confusion/print_confusion
1788 | if match('(.*)---this can\'t happen$'):
1789 | return ('error', None, None, 'internal error: ' + m.group(1))
1790 | # aux_end_err
1791 | if match('I found (no .*)---while reading file (.*)'):
1792 | return ('error', m.group(2), None, m.group(1))
1793 | # bad_cross_reference_print/
1794 | # nonexistent_cross_reference_error/
1795 | # @
1796 | #
1797 | # This is split across two lines. Match the second.
1798 | if match('^refers to entry "'):
1799 | typ, msg = self.__canonicalize(prev_line + ' ' + line)
1800 | msg = re.sub('^a (bad cross reference)', '\\1', msg)
1801 | # Try to give this key a location
1802 | filename = lineno = None
1803 | m2 = re.search(r'--entry "[^"]"', prev_line)
1804 | if m2:
1805 | filename, lineno = self.__find_key(m2.group(1))
1806 | return (typ, filename, lineno, msg)
1807 | # print_missing_entry
1808 | if match('Warning--I didn\'t find a database entry for (".*")'):
1809 | return ('warning', None, None,
1810 | 'no database entry for ' + m.group(1))
1811 | # x_warning
1812 | if match('Warning--(.*)'):
1813 | # Most formats give warnings about "something in ".
1814 | # Try to match it up.
1815 | filename = lineno = None
1816 | for m2 in reversed(list(re.finditer(r' in ([^, \t\n]+)\b', line))):
1817 | if m2:
1818 | filename, lineno = self.__find_key(m2.group(1))
1819 | if filename:
1820 | break
1821 | return ('warning', filename, lineno, m.group(1))
1822 | # @
1823 | if match('Aborted at line ([0-9]+) of file (.*)'):
1824 | return ('info', m.group(2), int(m.group(1)), 'aborted')
1825 |
1826 | # biber type errors
1827 | if match('^.*> WARN - (.*)$'):
1828 | print ('warning', None, None, m.group(1))
1829 | m2 = re.match("(.*) in file '(.*?)', skipping ...", m.group(1))
1830 | if m2:
1831 | return ('warning', m2.group(2), "0", m2.group(1))
1832 | return ('warning', None, None, m.group(1))
1833 |
1834 | if match('^.*> ERROR - (.*)$'):
1835 | m2 = re.match("BibTeX subsystem: (.*?), line (\d+), (.*)$", m.group(1))
1836 | if m2:
1837 | return ('error', m2.group(1), m2.group(2), m2.group(3))
1838 | return ('error', None, None, m.group(1))
1839 |
1840 |
1841 | def __canonicalize(self, msg):
1842 | if msg.startswith('Warning'):
1843 | msg = re.sub('^Warning-*', '', msg)
1844 | typ = 'warning'
1845 | else:
1846 | typ = 'error'
1847 | msg = re.sub('^I(\'m| was)? ', '', msg)
1848 | msg = msg[:1].lower() + msg[1:]
1849 | return typ, msg
1850 |
1851 | def __find_key(self, key):
1852 | if self.__key_locs is None:
1853 | p = BibTeXKeyParser()
1854 | self.__key_locs = {}
1855 | for filename in self.__inputs:
1856 | data = open(filename, 'rt', errors='surrogateescape').read()
1857 | for pkey, lineno in p.parse(data):
1858 | self.__key_locs.setdefault(pkey, (filename, lineno))
1859 | return self.__key_locs.get(key, (None, None))
1860 |
1861 | class BibTeXKeyParser:
1862 | """Just enough of a BibTeX parser to find keys."""
1863 |
1864 | def parse(self, data):
1865 | IDENT_RE = '(?![0-9])([^\x00-\x20\x80-\xff \t"#%\'(),={}]+)'
1866 | self.__pos, self.__data = 0, data
1867 | # Find the next entry
1868 | while self.__consume('[^@]*@[ \t\n]*'):
1869 | # What type of entry?
1870 | if not self.__consume(IDENT_RE + '[ \t\n]*'):
1871 | continue
1872 | typ = self.__m.group(1)
1873 | if typ == 'comment':
1874 | continue
1875 | start = self.__pos
1876 | if not self.__consume('([{(])[ \t\n]*'):
1877 | continue
1878 | closing, key_re = {'{' : ('}', '([^, \t\n}]*)'),
1879 | '(' : (')', '([^, \t\n]*)')}[self.__m.group(1)]
1880 | if typ not in ('preamble', 'string'):
1881 | # Regular entry; get key
1882 | if self.__consume(key_re):
1883 | yield self.__m.group(1), self.__lineno()
1884 | # Consume body of entry
1885 | self.__pos = start
1886 | self.__balanced(closing)
1887 |
1888 | def __consume(self, regexp):
1889 | self.__m = re.compile(regexp).match(self.__data, self.__pos)
1890 | if self.__m:
1891 | self.__pos = self.__m.end()
1892 | return self.__m
1893 |
1894 | def __lineno(self):
1895 | return self.__data.count('\n', 0, self.__pos) + 1
1896 |
1897 | def __balanced(self, closing):
1898 | self.__pos += 1
1899 | level = 0
1900 | skip = re.compile('[{}' + closing + ']')
1901 | while True:
1902 | m = skip.search(self.__data, self.__pos)
1903 | if not m:
1904 | break
1905 | self.__pos = m.end()
1906 | ch = m.group(0)
1907 | if level == 0 and ch == closing:
1908 | break
1909 | elif ch == '{':
1910 | level += 1
1911 | elif ch == '}':
1912 | level -= 1
1913 |
1914 | class Kpathsea:
1915 | def __init__(self, program_name):
1916 | self.__progname = program_name
1917 |
1918 | def find_file(self, name, format, cwd=None, env=None):
1919 | """Return the resolved path of 'name' or None."""
1920 |
1921 | args = ['kpsewhich', '-progname', self.__progname, '-format', format,
1922 | name]
1923 | try:
1924 | verbose_cmd(args, cwd, env)
1925 | path = subprocess.check_output(
1926 | args, cwd=cwd, env=env, universal_newlines=True).strip()
1927 | except subprocess.CalledProcessError as e:
1928 | if e.returncode != 1:
1929 | raise
1930 | return None
1931 | if cwd is None:
1932 | return path
1933 | return os.path.join(cwd, path)
1934 |
1935 | if __name__ == "__main__":
1936 | main()
1937 |
--------------------------------------------------------------------------------