userland: add archive tools and pinned GoAWK, esbuild, and QuickJS
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
// Package parseutil contains various utilities for parsing GoAWK source code.
|
||||
package parseutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// FileReader serves two purposes:
|
||||
// 1. read input sources and join them into a single source (slice of bytes)
|
||||
// 2. track the lines counts of each input source
|
||||
type FileReader struct {
|
||||
files []file
|
||||
source bytes.Buffer
|
||||
}
|
||||
|
||||
type file struct {
|
||||
path string
|
||||
lines int
|
||||
}
|
||||
|
||||
// AddFile adds a single source file.
|
||||
func (fr *FileReader) AddFile(path string, source io.Reader) error {
|
||||
curLen := fr.source.Len()
|
||||
_, err := fr.source.ReadFrom(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.HasSuffix(fr.source.Bytes(), []byte("\n")) {
|
||||
// Append newline to file in case it doesn't end with one
|
||||
fr.source.WriteByte('\n')
|
||||
}
|
||||
content := fr.source.Bytes()[curLen:]
|
||||
lines := bytes.Count(content, []byte("\n"))
|
||||
fr.files = append(fr.files, file{path, lines})
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileLine resolves an overall line number from the concatenated source code
|
||||
// to the local line number in that source file (identified by path).
|
||||
func (fr *FileReader) FileLine(line int) (path string, fileLine int) {
|
||||
startLine := 1
|
||||
for _, f := range fr.files {
|
||||
if line >= startLine && line < startLine+f.lines {
|
||||
return f.path, line - startLine + 1
|
||||
}
|
||||
startLine += f.lines
|
||||
}
|
||||
return "", 0
|
||||
}
|
||||
|
||||
// Source returns the concatenated source code from all files added.
|
||||
func (fr *FileReader) Source() []byte {
|
||||
return fr.source.Bytes()
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package parseutil_test
|
||||
|
||||
import (
|
||||
. "github.com/benhoyt/goawk/internal/parseutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testFile struct{ name, source string }
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
// input:
|
||||
files []testFile
|
||||
line int
|
||||
// expected:
|
||||
path string
|
||||
fileLine int
|
||||
}
|
||||
|
||||
func TestFileReader(t *testing.T) {
|
||||
fileSetNoNewline := []testFile{
|
||||
{"file1", `BEGIN {
|
||||
print f(1)
|
||||
}`},
|
||||
{"file2", `function f(x) {
|
||||
print x
|
||||
}`},
|
||||
}
|
||||
fileSetWithNewline := []testFile{
|
||||
{"file1", `BEGIN {
|
||||
print f(1)
|
||||
}
|
||||
`},
|
||||
{"file2", `function f(x) {
|
||||
print x
|
||||
}
|
||||
`},
|
||||
}
|
||||
tests := []test{
|
||||
{
|
||||
"TestInFirstFile",
|
||||
fileSetNoNewline,
|
||||
2,
|
||||
"file1",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"TestInSecondFile",
|
||||
fileSetNoNewline,
|
||||
5,
|
||||
"file2",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"TestInFirstFileWithNewline",
|
||||
fileSetWithNewline,
|
||||
2,
|
||||
"file1",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"TestInSecondFileWithNewline",
|
||||
fileSetWithNewline,
|
||||
5,
|
||||
"file2",
|
||||
2,
|
||||
},
|
||||
{
|
||||
"TestOutside",
|
||||
fileSetNoNewline,
|
||||
100,
|
||||
"",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"TestOutsideNegative",
|
||||
fileSetNoNewline,
|
||||
-100,
|
||||
"",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"TestNoFiles",
|
||||
[]testFile{},
|
||||
1,
|
||||
"",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"TestZeroLenFiles",
|
||||
[]testFile{
|
||||
{"file1", ""},
|
||||
},
|
||||
1,
|
||||
"file1",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"TestZeroLenFiles1",
|
||||
[]testFile{
|
||||
{"file1", ""},
|
||||
},
|
||||
2,
|
||||
"",
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tst := range tests {
|
||||
t.Run(tst.name, func(t *testing.T) {
|
||||
|
||||
fr := &FileReader{}
|
||||
|
||||
for _, file := range tst.files {
|
||||
if nil != fr.AddFile(file.name, strings.NewReader(file.source)) {
|
||||
panic("should not happen")
|
||||
}
|
||||
}
|
||||
|
||||
path, fileLine := fr.FileLine(tst.line)
|
||||
if path != tst.path {
|
||||
t.Errorf("expected path: %v, got: %v", tst.path, path)
|
||||
}
|
||||
if fileLine != tst.fileLine {
|
||||
t.Errorf("expected fileLine: %v, got: %v", tst.fileLine, fileLine)
|
||||
}
|
||||
|
||||
// test result source
|
||||
source := string(fr.Source())
|
||||
for _, file := range tst.files {
|
||||
if !strings.Contains(source, file.source) {
|
||||
t.Errorf("Source() is incorrect")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user