├── 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 | --------------------------------------------------------------------------------