Skip to content

Commit 16ca675

Browse files
committed
Add filename normalization and better temp-folder managment
1 parent c296e3b commit 16ca675

File tree

3 files changed

+108
-31
lines changed

3 files changed

+108
-31
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ require (
66
github.com/fsnotify/fsnotify v1.4.9
77
github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae // indirect
88
github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c
9+
golang.org/x/text v0.3.4
910
)

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c h1:zqmyTlQyufRC65JnI
88
github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
99
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
1010
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
11+
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
12+
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
13+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

latex-fast-compile.go

Lines changed: 104 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"errors"
66
"fmt"
7+
"io"
78
"io/ioutil"
89
"os"
910
"os/exec"
@@ -13,6 +14,10 @@ import (
1314
"strings"
1415
"syscall"
1516
"time"
17+
"unicode"
18+
19+
"golang.org/x/text/transform"
20+
"golang.org/x/text/unicode/norm"
1621

1722
"github.com/fsnotify/fsnotify"
1823
flag "github.com/spf13/pflag"
@@ -110,9 +115,11 @@ var (
110115
clearFlag string
111116
mustClear bool
112117
auxExtensions string
118+
mustNoNormalize bool
113119
// global variables
114120
texDistro string
115121
texVersionStr string
122+
inBaseOriginal string
116123
inBase string
117124
outBase string
118125
isRecompiling bool
@@ -160,6 +167,19 @@ func setDistro() {
160167
compileOptions = []string{"-interaction=batchmode", "-halt-on-error"}
161168
}
162169

170+
// used in normalizeName
171+
func isMn(r rune) bool {
172+
return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks
173+
}
174+
175+
// normalizeName remove accents and spaces
176+
// borrowed from https://stackoverflow.com/a/26722698
177+
func normalizeName(fileName string) string {
178+
t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC)
179+
result, _, _ := transform.String(t, fileName)
180+
return strings.ReplaceAll(result, " ", "")
181+
}
182+
163183
// Set the configuration variables from the command line flags
164184
func SetParameters() {
165185
// set the distro based on the pdftex version
@@ -173,12 +193,10 @@ func SetParameters() {
173193
flag.StringVar(&infoLevelFlag, "info", "actions", "The info level [no|errors|errors+log|actions|debug].")
174194
flag.StringVar(&logSanitize, "log-sanitize", `(?m)^(?:! |l\.|<recently read> ).*$`, "Match the log against this regex before display, or display all if empty.\n")
175195
flag.StringVar(&splitPattern, "split", `(?m)^\s*(?:%\s*end\s*preamble|\\begin{document})`, "Match the log against this regex before display, or display all if empty.\n")
176-
if texDistro == "miktex" {
177-
tempFolderName = "temp_files"
178-
}
179-
flag.StringVar(&tempFolderName, "temp-folder", tempFolderName, "Folder to store all temp files, .fmt included.")
196+
flag.StringVar(&tempFolderName, "temp-folder", "", "Folder to store all temp files, .fmt included.")
180197
flag.StringVar(&clearFlag, "clear", "auto", "Clear auxiliary files and .fmt at end [auto|yes|no].\n When watching auto=true, else auto=false.\nIn debug mode clear is false.")
181198
flag.StringVar(&auxExtensions, "aux-extensions", "aux,bbl,blg,fmt,fff,glg,glo,gls,idx,ilg,ind,lof,lot,nav,out,ptc,snm,sta,stp,toc", "Extensions to remove in clear at the end procedure.\n")
199+
flag.BoolVar(&mustNoNormalize, "no-normalize", false, "Keep accents and spaces in intermediate files.")
182200
flag.BoolVarP(&mustShowVersion, "version", "v", false, "Print the version number.")
183201
flag.BoolVarP(&mustShowHelp, "help", "h", false, "Print this help message.")
184202
// keep the flags order
@@ -211,7 +229,12 @@ func SetParameters() {
211229
check(errors.New("You should provide a .tex file to compile."))
212230
}
213231

214-
inBase = strings.TrimSuffix(flag.Arg(0), ".tex")
232+
inBaseOriginal = strings.TrimSuffix(flag.Arg(0), ".tex")
233+
if mustNoNormalize {
234+
inBase = inBaseOriginal
235+
} else {
236+
inBase = normalizeName(inBaseOriginal)
237+
}
215238

216239
// synctex or not?
217240
if !mustNotSync {
@@ -252,28 +275,30 @@ func SetParameters() {
252275
mustCompileAll = true
253276
}
254277
// set temp folder?
255-
outBase = inBase
278+
if !mustNoNormalize {
279+
tempFolderName = normalizeName(tempFolderName)
280+
}
256281
if len(tempFolderName) > 0 {
257-
if texDistro == "miktex" {
258-
compileOptions = append(compileOptions, "-aux-directory="+tempFolderName)
282+
if inBase == inBaseOriginal && texDistro == "miktex" {
259283
precompileOptions = append(precompileOptions, "-aux-directory="+tempFolderName)
284+
compileOptions = append(compileOptions, "-aux-directory="+tempFolderName)
260285
} else {
261-
compileOptions = append(compileOptions, "-output-directory="+tempFolderName)
262286
precompileOptions = append(precompileOptions, "-output-directory="+tempFolderName)
287+
compileOptions = append(compileOptions, "-output-directory="+tempFolderName)
263288
}
264-
265289
outBase = filepath.Join(tempFolderName, inBase)
290+
} else {
291+
outBase = inBase
266292
}
267293

268294
// set the source filename
269-
var compileName string
295+
precompileName := "&pdflatex " + inBase + ".preamble.tex"
296+
precompileOptions = append(precompileOptions, "-jobname="+inBase, precompileName)
297+
compileName := "&" + inBase + " " + inBase + ".body.tex"
270298
if mustCompileAll {
271-
compileName = inBase + ".tex"
272-
} else {
273-
compileName = "&" + inBase + " " + inBase + ".body.tex"
299+
compileName = "&pdflatex " + inBase + ".tex"
274300
}
275301
compileOptions = append(compileOptions, "-jobname="+inBase, compileName)
276-
precompileOptions = append(precompileOptions, "-jobname="+inBase, "&pdflatex "+inBase+".preamble.tex")
277302

278303
// clear or not
279304
mustClear = (infoLevel < infoDebug) && (clearFlag == "yes" || clearFlag == "auto" && !mustNoWatch)
@@ -337,7 +362,7 @@ func run(info, command string, args ...string) {
337362
// print action?
338363
if infoLevel >= infoActions {
339364
startTime = time.Now()
340-
fmt.Print(info + "...")
365+
fmt.Print("::::::: ", info+"...")
341366
}
342367
// run command
343368
err = cmd.Run()
@@ -365,17 +390,57 @@ func info(message ...interface{}) {
365390
}
366391
}
367392

393+
// Borrowed from https://stackoverflow.com/a/21067803
394+
func copyFile(src, dst string) (ok bool) {
395+
defer func() {
396+
if err == nil {
397+
ok = true
398+
} else {
399+
check(errors.New("Error while copy " + src + " to " + dst + "."))
400+
}
401+
}()
402+
403+
info(" copy", src, "to", dst)
404+
405+
in, err := os.Open(src)
406+
if err != nil {
407+
return
408+
}
409+
defer in.Close()
410+
out, err := os.Create(dst)
411+
if err != nil {
412+
return
413+
}
414+
defer func() {
415+
cerr := out.Close()
416+
if err == nil {
417+
err = cerr
418+
}
419+
}()
420+
if _, err = io.Copy(out, in); err != nil {
421+
return
422+
}
423+
err = out.Sync()
424+
return
425+
}
426+
368427
// splitTeX split the `.tex` file to two files `.preamble.tex` and `.body.tex`.
369428
// it also append `\dump` to the preamble and perpend `%&...` to the body.
370429
// both files are saved in the same folder (not in the temporary one) as the original source.
371430
func splitTeX() (ok bool) {
372-
sourceName := inBase + ".tex"
373-
// do I have to do something?
431+
sourceName := inBaseOriginal + ".tex"
432+
if isFileMissing(sourceName) {
433+
check(errors.New("File " + sourceName + " is missing."))
434+
}
435+
// we hope that...
436+
ok = true
437+
// copy the original?
438+
if mustCompileAll && inBaseOriginal != inBase {
439+
ok = copyFile(inBaseOriginal+".tex", inBase+".tex")
440+
}
441+
// is the split necessary?
374442
if !mustBuildFormat && mustCompileAll {
375-
if isFileMissing(sourceName) {
376-
check(errors.New("File " + sourceName + " is missing."))
377-
}
378-
return true
443+
return
379444
}
380445
// read the file
381446
var texdata []byte
@@ -402,12 +467,14 @@ func splitTeX() (ok bool) {
402467
}
403468
// important to first get body because textdata is polluted by \dump in the next line
404469
bodyName := inBase + ".body.tex"
470+
info(" create", bodyName)
405471
texBody := append([]byte("\n%&"+inBase+"\n"), texdata[loc[0]:]...)
406472
err = ioutil.WriteFile(bodyName, texBody, 0644)
407473
check(err, "Problem while writing", bodyName)
408474
ok = (err == nil)
409475

410476
preambleName := inBase + ".preamble.tex"
477+
info(" create", preambleName)
411478
texPreamble := append(texdata[:loc[0]], []byte("\\dump")...)
412479
err = ioutil.WriteFile(preambleName, texPreamble, 0644)
413480
check(err, "Problem while writing", preambleName)
@@ -425,7 +492,7 @@ func clearFiles(base, extensions string) {
425492
continue
426493
}
427494
if infoLevel >= infoActions {
428-
fmt.Println("Remove", fileToDelete)
495+
info(" remove", fileToDelete)
429496
}
430497
os.Remove(fileToDelete)
431498
}
@@ -446,6 +513,8 @@ func precompile() {
446513
if mustBuildFormat || !mustCompileAll && isFileMissing(outBase+".fmt") {
447514
run("Precompile", "pdftex", precompileOptions...)
448515
}
516+
// we tel to splitTex that the preamble is not needed any more
517+
mustBuildFormat = false
449518
}
450519

451520
// compile produce the `.pdf` file based on the `.body.tex` part.
@@ -465,14 +534,18 @@ func compile(draft bool) {
465534
} else {
466535
run(msg, "pdftex", compileOptions...)
467536
}
468-
if len(tempFolderName) > 0 && texDistro != "miktex" {
537+
// move/rename .pdf and .synctex to the original source
538+
if !draft && inBaseOriginal != outBase && (texDistro != "miktex" || inBaseOriginal != inBase) {
469539
if !isFileMissing(outBase + ".pdf") {
470-
info("Move pdf from temp folder.")
471-
os.Rename(outBase+".pdf", inBase+".pdf")
540+
if copyFile(outBase+".pdf", inBaseOriginal+".pdf") {
541+
info(" delete", outBase+".pdf")
542+
os.Remove(outBase + ".pdf")
543+
}
472544
}
473545
if !mustNotSync && !isFileMissing(outBase+".synctex") {
474-
info("Move synctex from temp folder.")
475-
os.Rename(outBase+".synctex", inBase+".synctex")
546+
info(" move", outBase+".synctex", "to", inBaseOriginal+".synctex")
547+
err = os.Rename(outBase+".synctex", inBaseOriginal+".synctex")
548+
check(err, "Error while copy "+outBase+".synctex to "+inBaseOriginal+".synctex.")
476549
}
477550
}
478551

@@ -540,7 +613,7 @@ func main() {
540613
}
541614
// watching ?
542615
if !mustNoWatch {
543-
info("Watching for files changes...(to exit press Ctrl/Cmd-C).")
616+
info("Watching for file changes...(to exit press Ctrl/Cmd-C).")
544617
// creates a new file watcher
545618
watcher, err := fsnotify.NewWatcher()
546619
check(err, "Problem creating the file watcher")
@@ -577,8 +650,8 @@ func main() {
577650
}()
578651

579652
// out of the box fsnotify can watch a single file, or a single directory
580-
err = watcher.Add(inBase + ".tex")
581-
check(err, "Problem watching", inBase+".tex")
653+
err = watcher.Add(inBaseOriginal + ".tex")
654+
check(err, "Problem watching", inBaseOriginal+".tex")
582655

583656
<-done
584657
}

0 commit comments

Comments
 (0)