*! latexlog 0.5.0 26aug2026 // Author: Johannes F. Schmieder // Department of Economics, Boston University // latexlog.ado /*-------------------------------------------------------*/ /* Latex Log File */ /*-------------------------------------------------------*/ capture program drop latexlog program define latexlog version 18 // Parse arguments for name of logfile capt _on_colon_parse `0' if !_rc { local 0 `"`s(after)'"' local logfile `"`s(before)'"' } local logfile = ustrrtrim(`"`logfile'"') // On Windows, _on_colon_parse initially sees the drive-letter colon in // paths such as C:\reports\results.tex. Re-parse the remaining text at // the actual command separator and restore the drive prefix. if "`c(os)'" == "Windows" & strlen("`logfile'") == 1 & /// regexm("`logfile'", "^[A-Za-z]$") & /// regexm(`"`0'"', "^[\\/].*:") { capture _on_colon_parse `0' if !_rc { local logfile `"`logfile':`s(before)'"' local logfile = ustrrtrim(`"`logfile'"') local 0 `"`s(after)'"' } } // Parse arguments for latexlog command syntax anything, * tokenize `"`anything'"' local command `1' local line `"`2'"' // Put remaining arguments back into local 0 in order to parse /// // it via syntax command later local 0 , `options' // File handle: tempname f // ---- Individual latexlog commands ---- if "`command'" == "open" { syntax [, REPlace APPend Geometry(str) PREDOCOpen(str) POSTDOCOpen(str) ] if "`geometry'"=="" local geometry "lmargin=2.5cm,tmargin=2.5cm" if "`replace'" != "" & "`append'" != "" { di as error "options replace and append may not be combined" exit 198 } splitpath "`logfile'" local path = r(path) capture noisily _latexlog_mkdir_p `"`path'"' if _rc { di as error "could not create output directory `path'" exit 693 } if `"`append'"' == "" local replace replace file open `f' using `"`logfile'"', write `replace' `append' file write `f' "% `c(current_date)' `c(current_time)'" _n file write `f' "\documentclass{article}" _n file write `f' "\usepackage{geometry,booktabs,longtable,pdflscape,rotating,threeparttable,subcaption,graphicx,float}" _n file write `f' "\usepackage{tabularx,colortbl}" _n file write `f' "\usepackage[table]{xcolor}" _n file write `f' "\usepackage{hyperref}" _n file write `f' "\hypersetup{ " file write `f' " colorlinks=true, " file write `f' " linkcolor={blue!50!black}, " file write `f' " filecolor={blue!50!black}, " file write `f' " urlcolor={blue!80!black}, " file write `f' " } " file write `f' "\date{}" _n file write `f' "\geometry{verbose,letterpaper,`geometry'}" _n file write `f' "`predocopen'" _n file write `f' "\begin{document}" _n file write `f' "`postdocopen'" _n file close `f' } else if "`command'" == "close" { syntax [, PREDOCClose(str)] file open `f' using `"`logfile'"', write append file write `f' "`predocclose'" _n file write `f' "\end{document}" _n file close `f' } else if "`command'" == "title" { file open `f' using `"`logfile'"', write append file write `f' `"\title{`line'}"' _n file write `f' `"\maketitle"' _n file close `f' } else if "`command'" == "section" { file open `f' using `"`logfile'"', write append file write `f' `"\section{`line'}"' _n file close `f' } else if "`command'" == "sections" { file open `f' using `"`logfile'"', write append file write `f' `"\section*{`line'}"' _n file close `f' } else if "`command'" == "subsection" { file open `f' using `"`logfile'"', write append file write `f' `"\subsection{`line'}"' _n file close `f' } else if "`command'" == "writeln" { file open `f' using `"`logfile'"', write append file write `f' `"`line'"' _n file close `f' } else if "`command'" == "addfig" { syntax , FILEname(str) [float TITle(str asis) NOTES(str asis) WIdth(real 0.9) eol] if "`float'" == "" & (`"`title'"' != "" | `"`notes'"' != "") { di as error "title() and notes() require the float option" exit 198 } if "`float'" != "" & "`eol'" != "" { di as error "eol may not be combined with float" exit 198 } if `width' <= 0 { di as error "width() must be greater than zero" exit 198 } // Parse title sub-options local title_in_tabular "" if `"`title'"' != "" { _parse comma title_text title_opts : title if `"`title_opts'"' != "" { local 0 `title_opts' syntax [, tabular] local title_in_tabular `tabular' } local title `title_text' } // Parse notes sub-options local notes_center "" if `"`notes'"' != "" { _parse comma notes_text notes_opts : notes if `"`notes_opts'"' != "" { local 0 `notes_opts' syntax [, center] local notes_center `center' } local notes `notes_text' } splitpath "`logfile'" local path = r(path) splitpath "`filename'" local figpath = r(path) local fileend = r(fileend) if "`fileend'" == "" { di as error "filename() must include a file extension" exit 198 } if substr(`"`filename'"', 1, 1) == "/" | /// regexm(`"`filename'"', "^[A-Za-z]:[\\/]") { di as error "filename() must be relative to the LaTeX log file" exit 198 } // Strip leading ./ from figpath for Windows compatibility if substr("`figpath'", 1, 2) == "./" { local figpath = substr("`figpath'", 3, .) } local targetdir `"`path'`figpath'"' capture noisily _latexlog_mkdir_p `"`targetdir'"' mata: st_local("dir_exists", strofreal(direxists(st_local("targetdir")))) if !`dir_exists' { di as error "could not create directory `targetdir'" exit 693 } capture noisily graph export `"`path'`filename'"', replace local graph_rc = _rc if `graph_rc' { di as error "could not export graph to `path'`filename'" if `graph_rc' == 198 { di as error "no graph in memory; create a graph before using addfig" } exit `graph_rc' } file open `f' using `"`logfile'"', write append local texfilename = subinstr(`"`filename'"', "\", "/", .) if "`float'"=="" { if "`eol'"=="eol" local eol \\ else local eol file write `f' `"\includegraphics[width = `width'\textwidth]{`texfilename'} `eol' "' _n } else { file write `f' `"\begin{figure}[H] "' _n if "`title'"!="" { if "`title_in_tabular'"=="tabular" { file write `f' `"\centering "' _n file write `f' `"\begin{tabular}{p{6in}}"' _n file write `f' `"\caption{`title'} "' _n file write `f' `"\end{tabular}"' _n } else { file write `f' `"\centering "' _n file write `f' `"\caption{`title'} "' _n } } file write `f' `"\includegraphics[width = `width'\textwidth]{`texfilename'} \\ "' _n if "`notes'"!="" { file write `f' `"\begin{tabular}{p{6in}} "' _n file write `f' `"\footnotesize \vspace{2pt} "' _n if "`notes_center'"=="center" /// file write `f' `" \centering \textbf{Notes:} `notes' "' _n else /// file write `f' `" \textbf{Notes:} `notes' "' _n file write `f' `"\end{tabular} "' _n } file write `f' `"\end{figure} "' _n } file close `f' } else if "`command'" == "subfigure" { syntax , [open addfig close FILEname(str) caption(str) /// TITle(str asis) NOTES(str asis) WIdth(real 0.9) eol] local mode_count = ("`open'" != "") + ("`addfig'" != "") + /// ("`close'" != "") if `mode_count' != 1 { di as error "specify exactly one of open, addfig, or close" exit 198 } if "`addfig'" == "" & `"`filename'"' != "" { di as error "filename() may be used only with subfigure, addfig" exit 198 } if "`addfig'" != "" & `"`filename'"' == "" { di as error "subfigure, addfig requires filename()" exit 198 } if "`open'" == "" & `"`title'"' != "" { di as error "title() may be used only with subfigure, open" exit 198 } if "`close'" == "" & `"`notes'"' != "" { di as error "notes() may be used only with subfigure, close" exit 198 } if "`addfig'" == "" & ("`eol'" != "" | `"`caption'"' != "") { di as error "caption() and eol may be used only with subfigure, addfig" exit 198 } if `width' <= 0 { di as error "width() must be greater than zero" exit 198 } // Parse title sub-options local title_in_tabular "" if `"`title'"' != "" { _parse comma title_text title_opts : title if `"`title_opts'"' != "" { local 0 `title_opts' syntax [, tabular] local title_in_tabular `tabular' } local title `title_text' } // Parse notes sub-options local notes_center "" if `"`notes'"' != "" { _parse comma notes_text notes_opts : notes if `"`notes_opts'"' != "" { local 0 `notes_opts' syntax [, center] local notes_center `center' } local notes `notes_text' } if "`open'"=="open" { file open `f' using `"`logfile'"', write append file write `f' `"\begin{figure}[H] "' _n if "`title'"!=`""' { file write `f' `"\centering "' _n if "`title_in_tabular'"=="tabular" { file write `f' `"\begin{tabular}{p{6in}}"' _n file write `f' `" \caption{`title'} "' _n file write `f' `"\end{tabular}"' _n } else { file write `f' `" \caption{`title'} "' _n } } file close `f' } if "`addfig'"=="addfig" { splitpath "`logfile'" local path = r(path) splitpath "`filename'" local figpath = r(path) local fileend = r(fileend) if "`fileend'" == "" { di as error "filename() must include a file extension" exit 198 } if substr(`"`filename'"', 1, 1) == "/" | /// regexm(`"`filename'"', "^[A-Za-z]:[\\/]") { di as error "filename() must be relative to the LaTeX log file" exit 198 } // Strip leading ./ from figpath for Windows compatibility if substr("`figpath'", 1, 2) == "./" { local figpath = substr("`figpath'", 3, .) } local targetdir `"`path'`figpath'"' capture noisily _latexlog_mkdir_p `"`targetdir'"' mata: st_local("dir_exists", strofreal(direxists(st_local("targetdir")))) if !`dir_exists' { di as error "could not create directory `targetdir'" exit 693 } capture noisily graph export `"`path'`filename'"', replace local graph_rc = _rc if `graph_rc' { di as error "could not export graph to `path'`filename'" if `graph_rc' == 198 { di as error "no graph in memory; create a graph before using subfigure" } exit `graph_rc' } local texfilename = subinstr(`"`filename'"', "\", "/", .) file open `f' using `"`logfile'"', write append file write `f' `"\begin{subfigure}{`width'\textwidth}"' _n file write `f' `" \includegraphics[width = 1.00\textwidth]{`texfilename'} "' _n if "`caption'"!="" { file write `f' `" \caption{`caption'}"' _n } file write `f' `"\end{subfigure}"' _n if "`eol'" == "eol" file write `f' "\\" _n file close `f' } if "`close'"=="close" { file open `f' using `"`logfile'"', write append if "`notes'"!="" { file write `f' `"\begin{tabular}{p{6in}} "' _n file write `f' `" \footnotesize "' _n if "`notes_center'"=="center" /// file write `f' `" \centering \textbf{Notes:} `notes' "' _n else /// file write `f' `" \textbf{Notes:} `notes' "' _n file write `f' `"\end{tabular} "' _n } file write `f' `"\end{figure} "' _n file close `f' } } else if "`command'" == "collect" { syntax [, /// TITle(str) /// NOTES(str) /// BOOKTabs /// NOVERTicallines /// THREEparttable /// FONTsize(str) /// LANDscape /// TABularx(str asis) /// ] // Parse tabularx sub-options: tabularx(col_numbers, width(width_spec)) local tabularx_width "\textwidth" local tabularx_cols "" if `"`tabularx'"' != "" { _parse comma tabularx_cols tabularx_opts : tabularx if `"`tabularx_opts'"' != "" { local 0 `tabularx_opts' syntax [, width(str)] if "`width'" != "" local tabularx_width "`width'" } } // Export the table to a temporary file tempfile collectfile collect export `collectfile', tableonly replace as(tex) tempname table file open `f' using `"`logfile'"', write append if "`landscape'"!="" { file write `f' `"\begin{landscape}"' _n } file write `f' `"\begin{table}[htbp] "' _n file write `f' `"\centering "' _n if "`threeparttable'"=="threeparttable" { file write `f' `"\begin{threeparttable} "' _n } if "`title'"!="" { file write `f' `"\caption{`title'} "' _n } if "`fontsize'"!="" { file write `f' `"\fontsize{`fontsize'}{`fontsize'}\selectfont "' _n } // Count how many vertical lines to determine when to use toprule, midrule and bottomrule if "`booktabs'"=="booktabs"{ file open `table' using `collectfile', read file read `table' line local Ncline 0 while r(eof)==0 { if strmatch("`line'","\cline*") local Ncline = `Ncline'+1 file read `table' line } file close `table' } // Write table to output file file open `table' using `collectfile', read file read `table' line local counter 1 while r(eof)==0 { if strmatch("`line'","\begin{table}*") | strmatch("`line'","\end{table}") { local line "" } if "`booktabs'"=="booktabs"{ if strmatch("`line'","\cline*") { if `counter'==1 local line "\toprule" if `counter'>1 & `counter'<`Ncline' local line "\midrule" if `counter'==`Ncline' local line "\bottomrule" local counter = `counter'+1 } } if "`noverticallines'"=="noverticallines" { local line = subinstr("`line'","|","",.) } // Convert tabular to tabularx if option specified if `"`tabularx'"' != "" { // Replace \begin{tabular} with \begin{tabularx}{width} if strmatch("`line'", "*\begin{tabular}*") { // Extract column spec from \begin{tabular}{colspec} local colstart = strpos("`line'", "\begin{tabular}{") + 16 local colend = strpos(substr("`line'", `colstart', .), "}") - 1 local colspec = substr("`line'", `colstart', `colend') // Build new column spec: replace l/c/r with X based on tabularx_cols local newcolspec "" local colnum 1 forvalues i = 1/`=strlen("`colspec'")' { local ch = substr("`colspec'", `i', 1) // Check if this is a column type character if inlist("`ch'", "l", "c", "r") { // Replace with X if tabularx_cols is empty or colnum is in tabularx_cols local replace_col 0 if "`tabularx_cols'" == "" { local replace_col 1 } else { foreach col of local tabularx_cols { if `col' == `colnum' { local replace_col 1 } } } if `replace_col' { local newcolspec "`newcolspec'>{\centering\arraybackslash}X" } else { local newcolspec "`newcolspec'`ch'" } local colnum = `colnum' + 1 } else { // Keep other characters (|, @{}, etc.) as-is local newcolspec "`newcolspec'`ch'" } } // Replace the tabular with tabularx and new column spec local before = substr("`line'", 1, strpos("`line'", "\begin{tabular}") - 1) local after = substr("`line'", `colstart' + `colend' + 1, .) local line "`before'\begin{tabularx}{`tabularx_width'}{`newcolspec'}`after'" } // Replace \end{tabular} with \end{tabularx} if strmatch("`line'", "*\end{tabular}*") { local line = subinstr("`line'", "\end{tabular}", "\end{tabularx}", .) } // Strip \multicolumn{1}{l/c/r}{content} wrappers, keeping just content // Need to handle nested braces in content (e.g., \hspace{1em}) while strmatch(`"`line'"', `"*\multicolumn{1}{*"') { local mcpos = strpos(`"`line'"', "\multicolumn{1}{") local rest = substr(`"`line'"', `mcpos' + 16, .) local alignend = strpos("`rest'", "}") local rest2 = substr("`rest'", `alignend' + 1, .) // Find matching } by counting brace depth local depth 0 local clen 0 forvalues i = 1/`=strlen(`"`rest2'"')' { local ch = substr(`"`rest2'"', `i', 1) if "`ch'" == "{" local depth = `depth' + 1 if "`ch'" == "}" local depth = `depth' - 1 if `depth' == 0 { local clen = `i' continue, break } } if `clen' > 0 { local content = substr(`"`rest2'"', 2, `clen' - 2) local before = substr(`"`line'"', 1, `mcpos' - 1) local after = substr(`"`rest2'"', `clen' + 1, .) local line `"`before'`content'`after'"' } else { continue, break } } // Preserve line break marker \\ before word processing local has_linebreak 0 local linebreak_suffix "" if strmatch(`"`line'"', `"*\\"') { local has_linebreak 1 local linebreak_suffix " \\" // Remove trailing \\ (and preceding space if any) local line = regexr(`"`line'"', `"[ ]*\\\\\\\\$"', "") } // Auto-hyphenate long words (>10 chars) for better column wrapping // Process each word between spaces, ampersands, and slashes local newline "" local remaining `"`line'"' while `"`remaining'"' != "" { // Find next delimiter (space, &, or /) local spacepos = strpos(`"`remaining'"', " ") local amppos = strpos(`"`remaining'"', "&") local slashpos = strpos(`"`remaining'"', "/") local delim_pos 0 local delim "" // Find the earliest delimiter foreach d in space amp slash { if ``d'pos' > 0 & (`delim_pos' == 0 | ``d'pos' < `delim_pos') { local delim_pos = ``d'pos' if "`d'" == "space" local delim " " else if "`d'" == "amp" local delim "&" else if "`d'" == "slash" local delim "\slash " } } if `delim_pos' > 0 { local word = substr(`"`remaining'"', 1, `delim_pos' - 1) local remaining = substr(`"`remaining'"', `delim_pos' + 1, .) } else { local word `"`remaining'"' local remaining "" local delim "" } // Hyphenate long words (>10 chars) that don't contain backslash local wordlen = strlen(`"`word'"') if `wordlen' > 10 & strpos(`"`word'"', "\") == 0 { local hyphenated "" forvalues i = 1/`wordlen' { local ch = substr(`"`word'"', `i', 1) local hyphenated "`hyphenated'`ch'" // Insert \- every 5 characters (but not at end) if mod(`i', 5) == 0 & `i' < `wordlen' { local hyphenated "`hyphenated'\-" } } local word "`hyphenated'" } local newline `"`newline'`word'`delim'"' } local line `"`newline'`linebreak_suffix'"' } file write `f' "`line'" _n file read `table' line } file close `table' if `"`notes'"'!="" { if "`threeparttable'"=="" /// di in red "WARNING: 'notes' option should only be used together with 'threeparttable' " file write `f' `" \footnotesize "' _n file write `f' `"\textbf{Notes:} `notes' "' _n } if "`threeparttable'"=="threeparttable" { file write `f' `"\end{threeparttable} "' _n } file write `f' `"\end{table}"' _n if "`landscape'"!="" { file write `f' `"\end{landscape}"' _n } file close `f' } else if "`command'" == "pdf" { syntax [, view SHELLescape PDFLATEX(string)] splitpath "`logfile'" local path = r(path) local filestub = r(filestub) local fileend = r(fileend) local filename = r(filename) if lower("`fileend'") != "tex" { di as error "pdf requires a .tex logfile" exit 198 } capture confirm file `"`logfile'"' if _rc { di as error "LaTeX source file not found: `logfile'" exit 601 } local unsafe_logfile = strpos(`"`logfile'"', char(36)) | /// strpos(`"`logfile'"', char(96)) | strpos(`"`logfile'"', char(34)) | /// strpos(`"`logfile'"', char(10)) | strpos(`"`logfile'"', char(13)) if "`=c(os)'" == "Windows" { local unsafe_logfile = `unsafe_logfile' | /// strpos(`"`logfile'"', "%") } if `unsafe_logfile' { di as error "the PDF path contains unsupported shell-expansion characters" exit 198 } local pwd = c(pwd) capture noisily cd `"`path'"' if _rc { di as error "could not change to LaTeX directory: `path'" exit 170 } tempname buildstub local shell_escape_arg if "`shellescape'" != "" local shell_escape_arg "-shell-escape" local compiler `"`pdflatex'"' if `"`compiler'"' == "" { local compiler "pdflatex" if "`=c(os)'" == "MacOSX" | /// ("`c(os)'" == "Unix" & strmatch("`c(machine_type)'", "Mac*")) { capture confirm file "/Library/TeX/texbin/pdflatex" if !_rc local compiler "/Library/TeX/texbin/pdflatex" } } else if strpos(`"`compiler'"', "/") | strpos(`"`compiler'"', "\") { capture confirm file `"`compiler'"' if _rc { cd `"`pwd'"' di as error "pdflatex executable not found: `compiler'" exit 693 } } local unsafe_compiler = strpos(`"`compiler'"', char(36)) | /// strpos(`"`compiler'"', char(96)) | strpos(`"`compiler'"', char(34)) | /// strpos(`"`compiler'"', char(10)) | strpos(`"`compiler'"', char(13)) if "`=c(os)'" == "Windows" { local unsafe_compiler = `unsafe_compiler' | /// strpos(`"`compiler'"', "%") } if `unsafe_compiler' { cd `"`pwd'"' di as error "pdflatex() contains unsupported shell-expansion characters" exit 198 } cap erase `"`buildstub'.aux"' cap erase `"`buildstub'.log"' cap erase `"`buildstub'.out"' cap erase `"`buildstub'.pdf"' capture noisily shell "`compiler'" -halt-on-error /// -interaction=nonstopmode `shell_escape_arg' /// -jobname="`buildstub'" "`filename'" local compile_ok 0 local latex_error 0 local have_compiler_log 0 capture confirm file `"`buildstub'.log"' if !_rc { local have_compiler_log 1 tempname latexlog_fh file open `latexlog_fh' using `"`buildstub'.log"', read text file read `latexlog_fh' latexline while r(eof) == 0 { if strpos(`"`latexline'"', "Output written on") { local compile_ok 1 } if substr(strtrim(`"`latexline'"'), 1, 1) == "!" | /// strpos(`"`latexline'"', "Fatal error occurred") { local latex_error 1 } file read `latexlog_fh' latexline } file close `latexlog_fh' } local pdfbytes 0 capture confirm file `"`buildstub'.pdf"' if !_rc { mata: st_local("pdfbytes", strofreal(_latexlog_filesize(st_local("buildstub") + ".pdf"))) } if !`compile_ok' | `latex_error' | `pdfbytes' <= 0 { if `have_compiler_log' { capture copy `"`buildstub'.log"' `"`filestub'.log"', replace } cap erase `"`buildstub'.aux"' cap erase `"`buildstub'.out"' cap erase `"`buildstub'.pdf"' cap erase `"`buildstub'.log"' cd `"`pwd'"' if `have_compiler_log' { di as error "pdflatex failed; diagnostics retained in `path'`filestub'.log" } else { di as error "pdflatex failed before producing a compiler log" } exit 693 } capture copy `"`buildstub'.pdf"' `"`filestub'.pdf"', replace local copy_rc = _rc if `copy_rc' { capture copy `"`buildstub'.log"' `"`filestub'.log"', replace cap erase `"`buildstub'.aux"' cap erase `"`buildstub'.out"' cap erase `"`buildstub'.pdf"' cap erase `"`buildstub'.log"' cd `"`pwd'"' di as error "could not replace PDF file: `path'`filestub'.pdf" exit `copy_rc' } cap erase `"`buildstub'.aux"' cap erase `"`buildstub'.log"' cap erase `"`buildstub'.out"' cap erase `"`buildstub'.pdf"' cap erase `"`filestub'.aux"' cap erase `"`filestub'.log"' cap erase `"`filestub'.out"' cap erase `"`filestub'.synctex.gz"' cd `"`pwd'"' if "`=c(os)'"=="Windows" { local viewcommand start } else if "`c(os)'" == "MacOSX" | ( "`c(os)'" == "Unix" & strmatch("`c(machine_type)'", "Mac*") ) { local viewcommand open } else if "`=c(os)'"=="Unix" { local viewcommand xdg-open } if "`view'" != "" { if "`=c(os)'" == "Windows" { shell start "" "`path'`filestub'.pdf" } else shell `viewcommand' "`path'`filestub'.pdf" } exit 0 } else { di as error "Unknown latexlog subcommand: `command'" exit 198 } end // latexlog /*-------------------------------------------------------*/ /* Unpack filepath into filename, fileending and path */ /*-------------------------------------------------------*/ cap program drop splitpath program define splitpath, rclass local fullpath `0' // di regexm("`fullpath'","^(([A-Z]:)?[\.]?[\\{1,2}/]?.*[\\{1,2}/])*(.+)\.(.+)") local matched = regexm("`fullpath'", "^(.*[\\/])*(.+)\.(.+)") if `matched' { local path = regexs(1) local filestub = regexs(2) local fileend = regexs(3) } if "`path'"=="" return local path "./" else return local path "`path'" return local filestub "`filestub'" return local fileend "`fileend'" return local filename "`filestub'.`fileend'" end // splitpath /*-------------------------------------------------------*/ /* Create a directory and any missing parents */ /*-------------------------------------------------------*/ capture program drop _latexlog_mkdir_p program define _latexlog_mkdir_p version 18 args path capture mata: _latexlog_mkdir_p_mata(st_local("path")) if _rc exit _rc end capture mata: mata drop _latexlog_mkdir_p_mata() capture mata: mata drop _latexlog_filesize() mata: void _latexlog_mkdir_p_mata(string scalar path) { string scalar parent real scalar pos path = subinstr(path, "\\", "/") while (strlen(path) > 1 & substr(path, -1, 1) == "/") { path = substr(path, 1, strlen(path) - 1) } if (path == "" | direxists(path)) return pos = strrpos(path, "/") if (pos > 1) { parent = substr(path, 1, pos - 1) if (!direxists(parent)) _latexlog_mkdir_p_mata(parent) } mkdir(path) } real scalar _latexlog_filesize(string scalar path) { real scalar fh, bytes fh = fopen(path, "r") fseek(fh, 0, 1) bytes = ftell(fh) fclose(fh) return(bytes) } end