go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tools/cmd/luapp/main.go (about) 1 // Copyright 2022 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Command luapp is a very simple preprocessor for .lua files which inlines 16 // "loadfile" statements. 17 // 18 // This is intended to allow normal-looking collections of .lua files to be used 19 // effectively as Redis scripts, which only allow, essentially, a single module 20 // (there are undocumented hacks where you can call other pre-loaded scripts by 21 // their SHA1, but this is fragile at best, and still requires an apparatus to 22 // load all necessary scripts in order to compute their hashes). 23 // 24 // This is designed to be run with a go:generate statement via `go run` on 25 // a single .lua file, which will then output an expanded equivalent with the 26 // .gen.lua extension. 27 package main 28 29 import ( 30 "bufio" 31 "fmt" 32 "io" 33 "os" 34 "regexp" 35 "strings" 36 ) 37 38 func check(err error) { 39 if err != nil { 40 panic(err) 41 } 42 } 43 44 func checkWrite(_ int, err error) { 45 if err != nil { 46 panic(err) 47 } 48 } 49 50 var comment = regexp.MustCompile(`^\s*--`) 51 var pattern = regexp.MustCompile(`(.*)loadfile\(['"]([^)]+)['"]\)(.*)`) 52 53 func process(filename string) { 54 ifile, err := os.Open(filename) 55 check(err) 56 defer func() { 57 check(ifile.Close()) 58 }() 59 60 destPath := strings.Replace(filename, ".lua", ".gen.lua", 1) 61 62 tfile, err := os.Create(destPath + ".tmp") 63 check(err) 64 65 checkWrite(tfile.WriteString("-- Code generated by luapp. DO NOT EDIT.\n\n")) 66 67 scn := bufio.NewScanner(ifile) 68 for scn.Scan() { 69 line := scn.Bytes() 70 71 if comment.Match(line) { 72 continue 73 } 74 75 if m := pattern.FindSubmatch(line); len(m) > 0 { 76 lhs, filename, rhs := m[1], m[2], m[3] 77 checkWrite(tfile.Write(lhs)) 78 checkWrite(tfile.WriteString("(function(...)\n")) 79 80 importName := string(filename) 81 fmt.Println(" --import", importName) 82 toImport, err := os.Open(importName) 83 check(err) 84 defer func() { 85 check(toImport.Close()) 86 }() 87 88 reader := bufio.NewReader(toImport) 89 for { 90 line, err := reader.ReadString('\n') 91 if err == io.EOF { 92 checkWrite(tfile.WriteString(line)) 93 break 94 } 95 check(err) 96 97 if comment.MatchString(line) { 98 continue 99 } 100 checkWrite(tfile.WriteString(line)) 101 } 102 103 checkWrite(tfile.WriteString("end)")) 104 checkWrite(tfile.Write(rhs)) 105 checkWrite(tfile.WriteString("\n")) 106 } else { 107 checkWrite(tfile.Write(line)) 108 checkWrite(tfile.WriteString("\n")) 109 } 110 } 111 112 check(tfile.Close()) 113 114 check(os.Rename(tfile.Name(), destPath)) 115 } 116 117 func main() { 118 if len(os.Args) != 2 { 119 fmt.Fprintln(os.Stderr, "expected exactly one .lua file as an argument") 120 os.Exit(1) 121 } 122 file := os.Args[1] 123 fmt.Println("processing", file) 124 process(file) 125 }