diff --git a/cmd/arrai/bundle.go b/cmd/arrai/bundle.go index 902764de..5e16b8af 100644 --- a/cmd/arrai/bundle.go +++ b/cmd/arrai/bundle.go @@ -6,6 +6,8 @@ import ( "os" "path/filepath" + "github.com/spf13/afero" + "github.com/arr-ai/arrai/pkg/arraictx" "github.com/arr-ai/arrai/pkg/ctxfs" "github.com/arr-ai/arrai/syntax" @@ -53,7 +55,7 @@ func bundleFilesTo(ctx context.Context, path string, w io.Writer, out string) (e w = f } - buf, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), path) + buf, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), path) if err != nil { return err } diff --git a/cmd/arrai/compile.go b/cmd/arrai/compile.go index 124ca052..01892458 100644 --- a/cmd/arrai/compile.go +++ b/cmd/arrai/compile.go @@ -151,7 +151,7 @@ func buildBinary(ctx context.Context, bundledScripts []byte, out afero.File) err } } - file, err := ctxfs.ReadFile(fs, filepath.Join(buildDir, "main")) + file, err := afero.ReadFile(fs, filepath.Join(buildDir, "main")) if err != nil { return err } diff --git a/cmd/arrai/run.go b/cmd/arrai/run.go index ff7aab57..d4ef64bc 100644 --- a/cmd/arrai/run.go +++ b/cmd/arrai/run.go @@ -8,6 +8,8 @@ import ( "path/filepath" "strings" + "github.com/spf13/afero" + "github.com/arr-ai/arrai/pkg/arrai" "github.com/arr-ai/arrai/pkg/arraictx" "github.com/arr-ai/arrai/pkg/ctxfs" @@ -37,7 +39,7 @@ func evalFile(ctx context.Context, path string, w io.Writer, out string) error { if err := fileExists(ctx, path); err != nil { return err } - buf, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), path) + buf, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), path) if err != nil { return err } diff --git a/cmd/arrai/sync_other.go b/cmd/arrai/sync_other.go index 05983fa0..8112a2f3 100644 --- a/cmd/arrai/sync_other.go +++ b/cmd/arrai/sync_other.go @@ -21,6 +21,7 @@ import ( "github.com/go-errors/errors" "github.com/rjeczalik/notify" log "github.com/sirupsen/logrus" + "github.com/spf13/afero" "github.com/urfave/cli/v2" "google.golang.org/grpc" ) @@ -123,7 +124,7 @@ func buildTree(ctx context.Context, root string) (map[string]interface{}, error) if err != nil { log.Printf("ERROR WALKING TO %s: %s", path, err) } else if !info.IsDir() { - data, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), path) + data, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), path) if err != nil { return errors.WrapPrefix(err, "reading "+path, 0) } diff --git a/cmd/arrai/test.go b/cmd/arrai/test.go index 5b279388..d1edac34 100644 --- a/cmd/arrai/test.go +++ b/cmd/arrai/test.go @@ -2,10 +2,7 @@ package main import ( "context" - "fmt" - "io" "os" - "strings" "github.com/arr-ai/arrai/internal/test" "github.com/arr-ai/arrai/pkg/arraictx" @@ -20,32 +17,8 @@ var testCommand = &cli.Command{ } func doTest(c *cli.Context) error { - file := c.Args().Get(0) + path := c.Args().Get(0) ctx := arraictx.InitCliCtx(context.Background(), c) - return testPath(ctx, file, os.Stdout) -} - -// testPath runs and reports on all tests in the subtree of the given path. -func testPath(ctx context.Context, path string, w io.Writer) error { - if path == "" { - path = "." - } - if _, err := os.Stat(path); os.IsNotExist(err) { - if !strings.Contains(path, string([]rune{os.PathSeparator})) { - return fmt.Errorf(`"%s": not a command and not found as a file in the current directory`, path) - } - return fmt.Errorf(`"%s": file not found`, path) - } - - results, err := test.Test(ctx, w, path) - if err != nil { - return err - } - - err = test.Report(w, results) - if err != nil { - return err - } - return nil + return test.RunTests(ctx, os.Stdout, path) } diff --git a/cmd/arrai/test_test.go b/cmd/arrai/test_test.go deleted file mode 100644 index a3b7d72b..00000000 --- a/cmd/arrai/test_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "context" - "strings" - "testing" - - "github.com/arr-ai/arrai/pkg/arraictx" - "github.com/stretchr/testify/require" -) - -func TestTest(t *testing.T) { - t.Parallel() - - var s strings.Builder - err := testPath(arraictx.InitRunCtx(context.Background()), "./../../examples/test", &s) - require.Nil(t, err) - windowsOsStr := `Tests: -..\..\examples\test\multiple_cases_test.arrai -..\..\examples\test\single_case_test.arrai -all tests passed -` - linuxOsStr := `Tests: -../../examples/test/multiple_cases_test.arrai -../../examples/test/single_case_test.arrai -all tests passed -` - require.True(t, (windowsOsStr == s.String()) || (linuxOsStr == s.String())) -} diff --git a/contrib/table_test.arrai b/contrib/table_test.arrai index 48cb302f..ada96285 100644 --- a/contrib/table_test.arrai +++ b/contrib/table_test.arrai @@ -10,14 +10,16 @@ let multipleHtml = b[1, 2, 3] '; +let remWhitespace = \s //re.compile('\\s').sub('', s); + ( markdown: ( - empty: markdown({}) = '|Key|Value|\n', - single: markdown(single) = '|Key|Value|\n|a|1|', - multiple: markdown(multiple) = '|Key|Value|\n|a|1|\n|b|[1, 2, 3]|', + empty: remWhitespace(markdown({})) = '|Key|Value|', + single: remWhitespace(markdown(single)) = '|Key|Value||a|1|', + multiple: remWhitespace(markdown(multiple)) = '|Key|Value||a|1||b|[1,2,3]|', ), html: ( - empty: html({}) = '\n \n
KeyValue
', - multiple: html(multiple) = multipleHtml, + empty: remWhitespace(html({})) = '
KeyValue
', + multiple: remWhitespace(html(multiple)) = remWhitespace(multipleHtml), ), ) diff --git a/examples/test/README.md b/examples/test/README.md index 2b411871..f8a4f994 100644 --- a/examples/test/README.md +++ b/examples/test/README.md @@ -1,5 +1,5 @@ # Test case examples -This directory has the following examples of arr.ai test cases: +This directory has the following examples of arr.ai test cases which uses the new leaf-true structure: - **[Single test case in one test file](single_case_test.arrai)**: Only one test case in an arr.ai test file. - **[Multiple test cases in one test file](multiple_cases_test.arrai):** Multiple test cases in an arr.ai test file. diff --git a/examples/test/multiple_cases_test.arrai b/examples/test/multiple_cases_test.arrai index 4f91e431..a48dc4e0 100644 --- a/examples/test/multiple_cases_test.arrai +++ b/examples/test/multiple_cases_test.arrai @@ -1,9 +1,31 @@ -# 5 test cases +let str = "Hello, arrai!"; ( - # Added expression let to clarify the actual result can be a complex expression - testCase1: //test.assert.equal("Hello, arrai!", let words = ["Hello", "arrai!"];//seq.join(", ", words)), - testCase2: //test.assert.unequal("Hello, arrai!", //seq.join(", ", ["Nihao", "arrai!"])), - testCase3: //test.assert.size(2, {"Hello", "arrai!"}), - testCase4: //test.assert.true({"Hello", "arrai!"}), - testCase5: //test.assert.false({}), + tuple: + ( + testCase1: str = let words = ["Hello", "arrai!"];//seq.join(", ", words), + testCase2: str != //seq.join(", ", ["Nihao", "arrai!"]), + # notATest1: 5, + # notATest2: { 1 = 1, 2 = 1 }, + #_ignoredTest: 1 = 2, + ), + #_ignoredContainer: + #( + # testCase1: 1 = 2, + # _testCase2: 1 = 2, + #), + testCase3: 2 = {"Hello", "arrai!"} count, + dict: + { + # "testCase4": 4 = 7, + "testCase5": (a: 5 = 5, b: 6 = 6), + }, + array: + [ + 7 = 7, + [8 = 8, (a: 9 = 9, b: 10 = 10)], + let value = {str}; cond {value: true} = true, + let value = {}; cond {value: true} = false, + #true = false, + ], + deep: (deeper: (deepest: 9 = 9)), ) diff --git a/examples/test/single_case_test.arrai b/examples/test/single_case_test.arrai index 7cde448a..db4ace17 100644 --- a/examples/test/single_case_test.arrai +++ b/examples/test/single_case_test.arrai @@ -1 +1 @@ -//test.assert.equal("Hello", //str.title("hello")) +"Hello" = //str.title("hello") diff --git a/examples/test_old/README.md b/examples/test_old/README.md new file mode 100644 index 00000000..5937642c --- /dev/null +++ b/examples/test_old/README.md @@ -0,0 +1,5 @@ +# Test case examples + +This directory has the following examples of arr.ai test cases using the obsolete assert functions: +- **[Single test case in one test file](single_case_test.arrai)**: Only one test case in an arr.ai test file. +- **[Multiple test cases in one test file](multiple_cases_test.arrai):** Multiple test cases in an arr.ai test file. diff --git a/examples/test_old/multiple_cases_test.arrai b/examples/test_old/multiple_cases_test.arrai new file mode 100644 index 00000000..4f91e431 --- /dev/null +++ b/examples/test_old/multiple_cases_test.arrai @@ -0,0 +1,9 @@ +# 5 test cases +( + # Added expression let to clarify the actual result can be a complex expression + testCase1: //test.assert.equal("Hello, arrai!", let words = ["Hello", "arrai!"];//seq.join(", ", words)), + testCase2: //test.assert.unequal("Hello, arrai!", //seq.join(", ", ["Nihao", "arrai!"])), + testCase3: //test.assert.size(2, {"Hello", "arrai!"}), + testCase4: //test.assert.true({"Hello", "arrai!"}), + testCase5: //test.assert.false({}), +) diff --git a/examples/test_old/single_case_test.arrai b/examples/test_old/single_case_test.arrai new file mode 100644 index 00000000..7cde448a --- /dev/null +++ b/examples/test_old/single_case_test.arrai @@ -0,0 +1 @@ +//test.assert.equal("Hello", //str.title("hello")) diff --git a/go.mod b/go.mod index b061a68f..28f2342d 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect + golang.org/x/text v0.3.3 google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect google.golang.org/grpc v1.31.1 google.golang.org/protobuf v1.25.0 diff --git a/internal/test/entities.go b/internal/test/entities.go new file mode 100644 index 00000000..dd824e63 --- /dev/null +++ b/internal/test/entities.go @@ -0,0 +1,25 @@ +package test + +import "time" + +type testFile struct { + path string + source string + wallTime time.Duration + results []testResult +} + +type testResult struct { + name string + outcome testOutcome + message string +} + +type testOutcome int + +const ( + Failed testOutcome = iota + Invalid + Ignored + Passed +) diff --git a/internal/test/report.go b/internal/test/report.go index c8c43efa..88a5d7dc 100644 --- a/internal/test/report.go +++ b/internal/test/report.go @@ -3,28 +3,109 @@ package test import ( "fmt" "io" + "os" + "path/filepath" + "sort" + "strings" + "unicode/utf8" + + "golang.org/x/text/language" + "golang.org/x/text/message" ) -// Report prints the results of the testing to the IO writer. -func Report(w io.Writer, rs Results) error { - if rs.AllPassed() { - _, err := fmt.Fprintln(w, "all tests passed") - if err != nil { - return err - } - } else { - for _, r := range rs.results { - if !r.pass { - _, err := fmt.Fprintf(w, "%s failed\n", r.file) - if err != nil { - return err - } +// Report writes a formatted output of all the test files and their test results, and returns and error if the test run +// failed. +func Report(w io.Writer, testFiles []testFile) error { + stats := calcStats(testFiles) + + for _, testFile := range testFiles { + reportFile(w, testFile, stats.maxNameLen) + } + + reportStats(w, stats) + + if stats.runFailed { + return fmt.Errorf("test run "+color, 41, "FAILED") + } + + return nil +} + +const color = "\033[38;5;255;%d;1m%s\033[0m" + +// reportFile writes a formatted output of a file and all its test results, ordered by outcome. The maxName parameter +// is used to aligned the results, and should contain the length of the longest testResult.name inside testFile.results. +func reportFile(w io.Writer, testFile testFile, maxName int) { + results := testFile.results + + // Sort test results by outcome then by test name + sort.Slice(results, func(i, j int) bool { + left, right := results[i], results[j] + if left.outcome == right.outcome { + if left.name == right.name { + return true } + return left.name < right.name } - _, err := fmt.Fprintf(w, "%d/%d tests passed\n", rs.CountPassed(), rs.Count()) - if err != nil { - return err - } + return left.outcome > right.outcome + }) + + message.NewPrinter(language.English). + Fprintf(w, "\n======= %s (%dms)\n", relPath(testFile.path), testFile.wallTime.Milliseconds()) + for _, result := range results { + reportTest(w, result, maxName) } - return nil +} + +// relPath makes a best-effort attempt to compute the relative path of the given absolute path. If it fails, it returns +// the absolute path untouched. +func relPath(absPath string) string { + cwd, wdErr := os.Getwd() + relPath, relErr := filepath.Rel(cwd, absPath) + + if wdErr != nil || relErr != nil { + return absPath + } + + return relPath +} + +// reportTest writes a formatted output of a single test result (PASS/FAIL/SKIP/??) with the optional included message. +func reportTest(w io.Writer, test testResult, maxName int) { + switch test.outcome { + case Failed: + fmt.Fprintf(w, color, 41, "FAIL") + case Invalid: + fmt.Fprintf(w, color, 31, " ?? ") + case Ignored: + fmt.Fprintf(w, color, 93, "SKIP") + case Passed: + fmt.Fprintf(w, color, 32, "PASS") + } + + fmt.Fprintf(w, " %s\n", test.name+strings.Repeat(" ", maxName-utf8.RuneCountInString(test.name))) + + if test.message != "" { + fmt.Fprintf(w, " %s\n", strings.ReplaceAll(test.message, "\n", "\n ")) + } +} + +// reportStats writes a formatted single line representation of the aggregated test statistics. +func reportStats(w io.Writer, stats testStats) { + p := message.NewPrinter(language.English) + p.Fprintf(w, "\n======= Summary\n") + + if stats.failed > 0 { + p.Fprintf(w, "%d failed, ", stats.failed) + } + + if stats.invalid > 0 { + p.Fprintf(w, "%d invalid, ", stats.invalid) + } + + if stats.ignored > 0 { + p.Fprintf(w, "%d ignored, ", stats.ignored) + } + + p.Fprintf(w, "%d passed of %d total tests. Took %dms.\n", stats.passed, stats.total, stats.wallTime.Milliseconds()) } diff --git a/internal/test/report_test.go b/internal/test/report_test.go new file mode 100644 index 00000000..a8c5b611 --- /dev/null +++ b/internal/test/report_test.go @@ -0,0 +1,151 @@ +package test + +import ( + "bytes" + "strconv" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// ===== report + +func TestReport_TwoFilesPass(t *testing.T) { + t.Parallel() + + err := reportShouldContain(t, generateFiles(), + "path/to/file1.arrai", "1,234ms", "PASS", "testA", + "path/to/file2.arrai", "4,321ms", "PASS", "testB", + "2 passed", "2 total", "5,555ms") + require.NoError(t, err) +} + +func TestReport_TwoFilesFail(t *testing.T) { + t.Parallel() + + files := generateFiles() + files[1].results[0] = testResult{name: "testB", outcome: Failed, message: "Uh oh"} + err := reportShouldContain(t, files, + "path/to/file1.arrai", "1,234ms", "PASS", "testA", + "path/to/file2.arrai", "4,321ms", "FAIL", "testB", + "1 failed", "1 passed", "2 total", "5,555ms") + require.Error(t, err) +} + +// ===== reportFile + +func TestReportFile_WithTest(t *testing.T) { + t.Parallel() + generateFiles()[0].reportShouldContain(t, "path/to/file1.arrai", "1,234ms", "PASS", "testA") +} + +func TestReportFile_SortsCorrectly(t *testing.T) { + t.Parallel() + file := generateFiles()[0] + file.results = []testResult{ + {name: "failedB", outcome: Failed}, + {name: "failedA", outcome: Failed}, + {name: "ignoredB", outcome: Ignored}, + {name: "ignoredA", outcome: Ignored}, + {name: "invalidB", outcome: Invalid}, + {name: "invalidA", outcome: Invalid}, + {name: "passedA", outcome: Passed}, + {name: "passedA", outcome: Passed, message: "Duplicate"}, + } + file.reportShouldContain(t, "passedA", "Duplicate", "ignoredA", "ignoredB", "invalidA", "invalidB", + "failedA", "failedB") +} + +// ===== reportTest + +func TestReportTest_Passed(t *testing.T) { + t.Parallel() + testResult{name: "test1", outcome: Passed}. + reportShouldContain(t, "PASS", "test1") +} + +func TestReportTest_Failed(t *testing.T) { + t.Parallel() + testResult{name: "test2", outcome: Failed, message: "Failed!"}. + reportShouldContain(t, "FAIL", "test2", "Failed!") +} + +func TestReportTest_Invalid(t *testing.T) { + t.Parallel() + testResult{name: "test3", outcome: Invalid, message: "Invalid!"}. + reportShouldContain(t, "??", "test3", "Invalid!") +} + +func TestReportTest_Ignored(t *testing.T) { + t.Parallel() + testResult{name: "test4", outcome: Ignored}. + reportShouldContain(t, "SKIP", "test4") +} + +// ===== reportStats + +func TestReportStats_Passed(t *testing.T) { + t.Parallel() + testStats{wallTime: time.Millisecond * 11111, total: 2, passed: 2}. + reportShouldContain(t, "2 passed", "2 total", "11,111ms") +} + +func TestReportStats_Failed(t *testing.T) { + t.Parallel() + testStats{wallTime: time.Millisecond * 888, total: 4, passed: 1, failed: 1, invalid: 1, ignored: 1}. + reportShouldContain(t, "1 failed", "1 invalid", "1 ignored", "1 passed", "4 total", "888ms") +} + +// ===== Helpers + +func reportShouldContain(t *testing.T, files []testFile, keyPhrases ...string) error { + buf := bytes.Buffer{} + err := Report(&buf, files) + shouldContain(t, buf.String(), keyPhrases...) + return err +} + +func (file testFile) reportShouldContain(t *testing.T, keyPhrases ...string) { + buf := bytes.Buffer{} + reportFile(&buf, file, 10) + shouldContain(t, buf.String(), keyPhrases...) +} + +func (test testResult) reportShouldContain(t *testing.T, keyPhrases ...string) { + buf := bytes.Buffer{} + reportTest(&buf, test, 10) + shouldContain(t, buf.String(), keyPhrases...) +} + +func (stats testStats) reportShouldContain(t *testing.T, keyPhrases ...string) { + buf := bytes.Buffer{} + reportStats(&buf, stats) + shouldContain(t, buf.String(), keyPhrases...) +} + +// shouldContain verifies that the provided keyPhrases exist in str in the order specified. +func shouldContain(t *testing.T, str string, keyPhrases ...string) { + for _, phrase := range keyPhrases { + index := strings.Index(str, phrase) + if index == -1 { + require.Failf(t, "shouldContain() failed", + "Key phrase '%s' was not found in remaining unmatched string: %s", strconv.Quote(phrase), strconv.Quote(str)) + } + str = str[index+len(phrase):] + } +} + +func generateFiles() []testFile { + return []testFile{ + { + path: "/path/to/file1.arrai", wallTime: 1234 * time.Millisecond, + results: []testResult{{name: "testA", outcome: Passed}}, + }, + { + path: "/path/to/file2.arrai", wallTime: 4321 * time.Millisecond, + results: []testResult{{name: "testB", outcome: Passed}}, + }, + } +} diff --git a/internal/test/results.go b/internal/test/results.go deleted file mode 100644 index 5cfa315c..00000000 --- a/internal/test/results.go +++ /dev/null @@ -1,54 +0,0 @@ -package test - -// Result describes the outcome of a particular test case. -type Result struct { - file string - pass bool -} - -// Results is a collection of test results from a test suite. -type Results struct { - results []Result -} - -// Add adds a result to the set of results. -func (rs *Results) Add(r Result) { - rs.results = append(rs.results, r) -} - -// AllPassed returns true if all results were successful. -func (rs Results) AllPassed() bool { - for _, r := range rs.results { - if !r.pass { - return false - } - } - return true -} - -// Count returns the number of results in the set. -func (rs Results) Count() int { - return len(rs.results) -} - -// CountFailed returns the number of tests that failed. -func (rs Results) CountFailed() int { - count := 0 - for _, r := range rs.results { - if !r.pass { - count++ - } - } - return count -} - -// CountPassed returns the number of tests that passed. -func (rs Results) CountPassed() int { - count := 0 - for _, r := range rs.results { - if r.pass { - count++ - } - } - return count -} diff --git a/internal/test/runner.go b/internal/test/runner.go new file mode 100644 index 00000000..01559293 --- /dev/null +++ b/internal/test/runner.go @@ -0,0 +1,127 @@ +package test + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/spf13/afero" + + "github.com/arr-ai/arrai/pkg/ctxfs" + "github.com/arr-ai/arrai/rel" + "github.com/arr-ai/arrai/syntax" +) + +// RunTests runs all arr.ai tests in a given path. It returns an error if the path is invalid, contains no test files or +// has invalid arr.ai code in any of them. +func RunTests(ctx context.Context, w io.Writer, path string) error { + if path == "" || path == "." { + var err error + path, err = os.Getwd() + if err != nil { + return err + } + } + + files, err := getTestFiles(ctx, path) + if err != nil { + return err + } + + for i := range files { + err = runFile(ctx, &files[i]) + if err != nil { + return err + } + } + + err = Report(w, files) + return err +} + +// getTestFiles finds all *_test.arrai files in given path (recursively), reads them and returns a testFile array with +// them. It skips over hidden directories. It returns an error if any filesystem operation failed, or if no files were +// found. +func getTestFiles(ctx context.Context, path string) ([]testFile, error) { + var files []testFile + fs := ctxfs.SourceFsFrom(ctx) + + err := afero.Walk(fs, path, func(path string, info os.FileInfo, walkErr error) error { + if walkErr != nil { + return walkErr + } + + if info.IsDir() { + // Skip hidden dirs. + if strings.HasPrefix(info.Name(), ".") { + return filepath.SkipDir + } + + // Not a file, continue walking the directory tree. + return nil + } + + if strings.HasSuffix(path, "_test.arrai") == false { //nolint:gosimple + return nil + } + + bytes, readErr := afero.ReadFile(fs, path) + if readErr != nil { + return fmt.Errorf("failed reading test file '%s': %v", path, readErr) + } + + files = append(files, testFile{path: path, source: string(bytes)}) + return nil + }) + + if err != nil { + return nil, err + } + if len(files) == 0 { + return nil, fmt.Errorf("no test files (ending in '_test.arrai') were found in path: %v", path) + } + return files, nil +} + +// runFile runs all tests in testFile.source and fills testFile.results and testFile.wallTime. It returns an error if +// the arr.ai code failed to evaluate. +func runFile(ctx context.Context, file *testFile) error { + start := time.Now() + result, err := syntax.EvaluateExpr(ctx, file.path, file.source) + file.wallTime = time.Since(start) + + if err != nil { + return fmt.Errorf("failed evaluating tests file '%s': %v", file.path, err) + } + + file.results = make([]testResult, 0) + ForeachLeaf(result, "", func(val rel.Value, path string) { + result := testResult{ + name: path, + } + + if isLiteralTrue(val) { + result.outcome = Passed + } else if isLiteralFalse(val) { + result.outcome = Failed + result.message = "Expected: true. Actual: false." + } else { + result.outcome = Invalid + result.message = fmt.Sprintf("Could not determine test outcome due to non-boolean result of type %s: %s", + rel.ValueTypeAsString(val), val.String()) + + if _, ok := val.(rel.GenericSet); ok { + result.message = fmt.Sprintf("Sets are not allowed as test containers. Please use tuples, " + + "dictionaries or arrays.") + } + } + + file.results = append(file.results, result) + }) + + return nil +} diff --git a/internal/test/runner_test.go b/internal/test/runner_test.go new file mode 100644 index 00000000..099e5575 --- /dev/null +++ b/internal/test/runner_test.go @@ -0,0 +1,221 @@ +// +build !windows + +package test + +import ( + "bytes" + "context" + "os" + "testing" + + "github.com/arr-ai/arrai/pkg/ctxfs" + "github.com/arr-ai/arrai/pkg/ctxrootcache" + "github.com/stretchr/testify/require" +) + +func TestRunTests_Pass(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{"/test/1_test.arrai": "(test1: 1 = 1)"} + buf := &bytes.Buffer{} + err := RunTests(withFs(t, fsContent), buf, "/test") + + require.NoError(t, err) + shouldContain(t, buf.String(), "PASS", "test1") +} + +func TestRunTests_EmptyPath(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{"/test/1_test.arrai": "(test1: 1 = 1)"} + buf := &bytes.Buffer{} + err := RunTests(withFs(t, fsContent), buf, "") + + require.NoError(t, err) + shouldContain(t, buf.String(), "PASS", "test1") +} + +func TestRunTests_Fail(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{"/test/1_test.arrai": "(test1: 1 = 1, test2: 2 = 3)"} + buf := &bytes.Buffer{} + err := RunTests(withFs(t, fsContent), buf, "/test") + + require.Error(t, err) + shouldContain(t, buf.String(), "PASS", "test1", "FAIL", "test2") +} + +func TestRunTests_Invalid(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{"/test/1_test.arrai": "(test1: 1 = 1, test2: 2)"} + buf := &bytes.Buffer{} + err := RunTests(withFs(t, fsContent), buf, "/test") + + require.Error(t, err) + shouldContain(t, buf.String(), "PASS", "test1", "??", "test2") +} + +func TestRunTests_BadArrai(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{"/test/1_test.arrai": "x"} + buf := &bytes.Buffer{} + err := RunTests(withFs(t, fsContent), buf, "/test") + + require.Error(t, err) +} + +func TestRunTests_InvalidDir(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{} + buf := &bytes.Buffer{} + err := RunTests(withFs(t, fsContent), buf, "/nowhere") + + require.Error(t, err) +} + +func TestGetTestFiles_NoFiles(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{"/test/placeholder.txt": ""} + _, err := getTestFiles(withFs(t, fsContent), "/test") + require.Error(t, err) +} + +func TestGetTestFiles_InvalidDir(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{"/test/placeholder.txt": ""} + _, err := getTestFiles(withFs(t, fsContent), "/nowhere") + require.Error(t, err) +} + +func TestGetTestFiles_OneFile(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{"/test/1_test.arrai": "source1"} + files, err := getTestFiles(withFs(t, fsContent), "/test") + require.NoError(t, err) + require.Equal(t, []testFile{{path: "/test/1_test.arrai", source: "source1"}}, files) +} + +func TestGetTestFiles_PathIsFile(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{ + "/test/1_test.arrai": "source1", + "/test/2_test.arrai": "source2", + } + files, err := getTestFiles(withFs(t, fsContent), "/test/1_test.arrai") + require.NoError(t, err) + require.Equal(t, []testFile{{path: "/test/1_test.arrai", source: "source1"}}, files) +} + +func TestGetTestFiles_NestedDir(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{ + "/test/1_test.arrai": "source1", + "/test/must/go/deeper/2_test.arrai": "source2", + } + files, err := getTestFiles(withFs(t, fsContent), "/test") + require.NoError(t, err) + require.Equal(t, []testFile{ + {path: "/test/1_test.arrai", source: "source1"}, + {path: "/test/must/go/deeper/2_test.arrai", source: "source2"}, + }, files) +} + +func TestGetTestFiles_SkipHiddenDir(t *testing.T) { + t.Parallel() + + fsContent := map[string]string{ + "/test/1_test.arrai": "source1", + "/test/.must/go/deeper/2_test.arrai": "source2", + "/test/must/.go/deeper/3_test.arrai": "source3", + "/test/must/go/.deeper/4_test.arrai": "source4", + } + files, err := getTestFiles(withFs(t, fsContent), "/test") + require.NoError(t, err) + require.Equal(t, []testFile{{path: "/test/1_test.arrai", source: "source1"}}, files) +} + +func withFs(t *testing.T, files map[string]string) context.Context { + err := os.Chdir("/") + require.NoError(t, err) + fs := ctxfs.CreateTestMemMapFs(t, files) + ctx := ctxfs.SourceFsOnto(context.Background(), fs) + return ctxrootcache.WithRootCache(ctx) +} + +func TestRunFile_InvalidArrai(t *testing.T) { + t.Parallel() + + file := testFile{source: "invalid arr.ai code"} + err := runFile(context.Background(), &file) + require.Error(t, err) +} + +func TestRunFile_AssertFails(t *testing.T) { + t.Parallel() + + file := testFile{source: "//test.assert.equal(1, 2)"} + err := runFile(context.Background(), &file) + require.Error(t, err) +} + +func TestRunFile_TwoPass(t *testing.T) { + t.Parallel() + + file := testFile{source: "(test1: 1 = 1, test2: //test.assert.equal(2, 2))"} + err := runFile(context.Background(), &file) + require.NoError(t, err) + require.NotZero(t, file.wallTime) + require.ElementsMatch(t, file.results, []testResult{ + {name: "test1", outcome: Passed}, + {name: "test2", outcome: Passed}}) +} + +func TestRunFile_OneFailOnePass(t *testing.T) { + t.Parallel() + + file := testFile{source: "(test1: 1 < 1, test2: 5 < 7)"} + err := runFile(context.Background(), &file) + require.NoError(t, err) + require.NotZero(t, file.wallTime) + require.ElementsMatch(t, file.results, []testResult{ + {name: "test1", outcome: Failed, message: "Expected: true. Actual: false."}, + {name: "test2", outcome: Passed}}) +} + +func TestRunFile_OneInvalidOnePass(t *testing.T) { + t.Parallel() + + file := testFile{source: "(test1: 1, test2: 5 < 7)"} + err := runFile(context.Background(), &file) + require.NoError(t, err) + require.NotZero(t, file.wallTime) + require.ElementsMatch(t, file.results, []testResult{ + {name: "test1", outcome: Invalid, + message: "Could not determine test outcome due to non-boolean result of type number: 1"}, + {name: "test2", outcome: Passed}}) +} + +func TestRunFile_TestInSet(t *testing.T) { + t.Parallel() + + file := testFile{ + path: "some_test.arrai", + source: "(test1: 1 = 1, category1: { 5 < 7 })", + } + err := runFile(context.Background(), &file) + require.NoError(t, err) + require.NotZero(t, file.wallTime) + require.ElementsMatch(t, file.results, []testResult{ + {name: "test1", outcome: Passed}, + {name: "category1", outcome: Invalid, message: "Sets are not allowed as test containers. " + + "Please use tuples, dictionaries or arrays."}}) +} diff --git a/internal/test/stats.go b/internal/test/stats.go new file mode 100644 index 00000000..034ed07f --- /dev/null +++ b/internal/test/stats.go @@ -0,0 +1,57 @@ +package test + +import ( + "time" + "unicode/utf8" +) + +// calcStats calculates aggregate statistics for all test results in the provided test files. Most importantly, it +// determines if the test run has succeeded or failed. A test run fails if there are any testResults with a 'failed' or +// 'invalid' outcome. +func calcStats(testFiles []testFile) testStats { + var stats testStats + + for _, testFile := range testFiles { + stats.wallTime += testFile.wallTime + + for _, result := range testFile.results { + if count := utf8.RuneCountInString(result.name); count > stats.maxNameLen { + stats.maxNameLen = count + } + + if count := utf8.RuneCountInString(testFile.path); count > stats.maxFileLen { + stats.maxFileLen = count + } + + stats.total++ + + switch result.outcome { + case Invalid: + stats.invalid++ + case Passed: + stats.passed++ + case Ignored: + stats.ignored++ + case Failed: + stats.failed++ + } + } + } + + stats.runFailed = stats.failed > 0 || stats.invalid > 0 + + return stats +} + +type testStats struct { + runFailed bool + wallTime time.Duration + maxNameLen int + maxFileLen int + + total int + invalid int + passed int + ignored int + failed int +} diff --git a/internal/test/stats_test.go b/internal/test/stats_test.go new file mode 100644 index 00000000..6b840a3f --- /dev/null +++ b/internal/test/stats_test.go @@ -0,0 +1,66 @@ +//nolint:lll +package test + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +var passFile = testFile{ + path: "file1", + source: "src1", + wallTime: 100, + results: []testResult{ + {name: "Test1", outcome: Passed}, + {name: "Test22", outcome: Passed}, + {name: "Test333", outcome: Passed}, + {name: "Test4444", outcome: Passed}, + }, +} +var failFile = testFile{ + path: "file22", + source: "src2", + wallTime: 50, + results: []testResult{ + {name: "Test1", outcome: Passed}, + {name: "Test2", outcome: Failed}, + {name: "Test3", outcome: Ignored}, + }, +} +var invalidFile = testFile{ + path: "file3", + source: "src3", + wallTime: 3, + results: []testResult{ + {name: "Test1", outcome: Invalid}, + }, +} + +func TestCalcStats_AllPass(t *testing.T) { + t.Parallel() + require.Equal(t, + testStats{runFailed: false, wallTime: 100, maxNameLen: 8, maxFileLen: 5, total: 4, invalid: 0, passed: 4, ignored: 0, failed: 0}, + calcStats([]testFile{passFile})) +} + +func TestCalcStats_Mixed(t *testing.T) { + t.Parallel() + require.Equal(t, + testStats{runFailed: true, wallTime: 153, maxNameLen: 8, maxFileLen: 6, total: 8, invalid: 1, passed: 5, ignored: 1, failed: 1}, + calcStats([]testFile{passFile, failFile, invalidFile})) +} + +func TestCalcStats_Failed(t *testing.T) { + t.Parallel() + require.Equal(t, + testStats{runFailed: true, wallTime: 50, maxNameLen: 5, maxFileLen: 6, total: 3, invalid: 0, passed: 1, ignored: 1, failed: 1}, + calcStats([]testFile{failFile})) +} + +func TestCalcStats_Invalid(t *testing.T) { + t.Parallel() + require.Equal(t, + testStats{runFailed: true, wallTime: 3, maxNameLen: 5, maxFileLen: 5, total: 1, invalid: 1, passed: 0, ignored: 0, failed: 0}, + calcStats([]testFile{invalidFile})) +} diff --git a/internal/test/test.go b/internal/test/test.go deleted file mode 100644 index 7306233d..00000000 --- a/internal/test/test.go +++ /dev/null @@ -1,98 +0,0 @@ -package test - -import ( - "context" - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "github.com/arr-ai/arrai/pkg/ctxfs" - "github.com/arr-ai/arrai/rel" - "github.com/arr-ai/arrai/syntax" -) - -// Test runs all tests in the subtree of path and returns the results. -func Test(ctx context.Context, w io.Writer, path string) (Results, error) { - results := Results{} - var files []string - err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { - if strings.HasSuffix(path, "_test.arrai") { - files = append(files, path) - } - return nil - }) - if err != nil { - return Results{}, err - } - - if len(files) == 0 { - return results, fmt.Errorf("no tests found in %v", path) - } - - fmt.Fprintf(w, "Tests:\n%s\n", strings.Join(files, "\n")) - - for _, file := range files { - bytes, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), file) - if err != nil { - return results, err - } - result, err := syntax.EvaluateExpr(ctx, file, string(bytes)) - if err != nil { - fmt.Fprintf(w, "\nfailed test: %s\n", err) - results.Add(Result{file: file, pass: false}) - } else { - results.Add(Result{file: file, pass: isRecursivelyTrue(result)}) - } - } - - return results, nil -} - -// isRecursivelyTrue returns true if every leaf value of val is true (not just truthy). -func isRecursivelyTrue(val rel.Value) bool { - switch v := val.(type) { - case rel.EmptySet: - return false - case rel.GenericSet: - if v.Count() == 1 && v.Has(rel.NewTuple()) { - return true - } - for _, item := range v.OrderedValues() { - if !isRecursivelyTrue(item) { - return false - } - } - return true - case rel.Array: - if v.Count() == 0 { - return false - } - for _, item := range v.Values() { - if !isRecursivelyTrue(item) { - return false - } - } - return true - case rel.Dict: - if v.Count() == 0 { - return false - } - for _, entry := range v.OrderedEntries() { - if !isRecursivelyTrue(entry.MustGet(rel.DictValueAttr)) { - return false - } - } - return true - case rel.Tuple: - for e := v.Enumerator(); e.MoveNext(); { - _, attr := e.Current() - if !isRecursivelyTrue(attr) { - return false - } - } - return true - } - return false -} diff --git a/internal/test/value_utils.go b/internal/test/value_utils.go new file mode 100644 index 00000000..7c8360ea --- /dev/null +++ b/internal/test/value_utils.go @@ -0,0 +1,56 @@ +package test + +import ( + "fmt" + "strings" + + "github.com/arr-ai/arrai/rel" +) + +// ForeachLeaf visits all leaves in an test tree, invoking the leafAction callback for each leaf encountered. +// Tuples, arrays and dictionaries are considered test containers and not leaves, they are recursed into. +func ForeachLeaf(val rel.Value, path string, leafAction func(val rel.Value, path string)) { + path = strings.TrimPrefix(path, ".") + + switch v := val.(type) { + case rel.Array: + for i, item := range v.Values() { + ForeachLeaf(item, fmt.Sprintf("%s(%d)", path, i), leafAction) + } + case rel.Dict: + for _, entry := range v.OrderedEntries() { + key := entry.MustGet("@") + keyStr := key.String() + if _, ok := key.(rel.String); ok { + keyStr = "'" + keyStr + "'" + } + ForeachLeaf(entry.MustGet(rel.DictValueAttr), fmt.Sprintf("%s(%s)", path, keyStr), leafAction) + } + case rel.Tuple: + for e := v.Enumerator(); e.MoveNext(); { + name, attr := e.Current() + ForeachLeaf(attr, fmt.Sprintf("%s.%s", path, name), leafAction) + } + default: + if path == "" { + leafAction(val, "") + } else { + leafAction(val, path) + } + } +} + +// isLiteralTrue returns true if and only if the provided value is a literal true, i.e. "true" or "{()}" +func isLiteralTrue(val rel.Value) bool { + v, ok := val.(rel.GenericSet) + return ok && v.Count() == 1 && v.Has(rel.EmptyTuple) +} + +// isLiteralFalse returns true if and only if the provided value is a literal false, i.e. "false" or "{}" +func isLiteralFalse(val rel.Value) bool { + if _, ok := val.(rel.EmptySet); ok { + return true + } + v, ok := val.(rel.GenericSet) + return ok && v.Count() == 0 +} diff --git a/internal/test/value_utils_test.go b/internal/test/value_utils_test.go new file mode 100644 index 00000000..9a0b5dd0 --- /dev/null +++ b/internal/test/value_utils_test.go @@ -0,0 +1,134 @@ +package test + +import ( + "context" + "testing" + + "github.com/arr-ai/arrai/pkg/arraictx" + "github.com/arr-ai/arrai/rel" + "github.com/arr-ai/arrai/syntax" + "github.com/stretchr/testify/require" +) + +func TestIsLiteralTrue(t *testing.T) { //nolint:dupl + t.Parallel() + + require.True(t, isLiteralTrue(eval("true"))) + require.True(t, isLiteralTrue(eval("{()}"))) + + require.False(t, isLiteralTrue(eval("false"))) + require.False(t, isLiteralTrue(eval("1"))) + require.False(t, isLiteralTrue(eval("0"))) + require.False(t, isLiteralTrue(eval("()"))) + require.False(t, isLiteralTrue(eval("{true}"))) + require.False(t, isLiteralTrue(eval("{false}"))) + require.False(t, isLiteralTrue(eval("{(1)}"))) + require.False(t, isLiteralTrue(eval("{(),1}"))) + require.False(t, isLiteralTrue(eval("[true]"))) + require.False(t, isLiteralTrue(eval("(val: true)"))) +} + +func TestIsLiteralFalse(t *testing.T) { //nolint:dupl + t.Parallel() + + require.True(t, isLiteralFalse(eval("false"))) + require.True(t, isLiteralFalse(eval("{}"))) + + require.False(t, isLiteralFalse(eval("true"))) + require.False(t, isLiteralFalse(eval("1"))) + require.False(t, isLiteralFalse(eval("0"))) + require.False(t, isLiteralFalse(eval("()"))) + require.False(t, isLiteralFalse(eval("{true}"))) + require.False(t, isLiteralFalse(eval("{false}"))) + require.False(t, isLiteralFalse(eval("{(1)}"))) + require.False(t, isLiteralFalse(eval("{(),1}"))) + require.False(t, isLiteralFalse(eval("[true]"))) + require.False(t, isLiteralFalse(eval("(val: true)"))) +} + +func TestForeachLeaf(t *testing.T) { + t.Parallel() + + // No root + require.Equal(t, + leavesShouldBe{"": "true"}, + forInput("true")) + require.Equal(t, + leavesShouldBe{"": "false"}, + forInput("false")) + require.Equal(t, + leavesShouldBe{"": "42"}, + forInput("42")) + + // Tuple root + require.Equal(t, + leavesShouldBe{}, + forInput("()")) + require.Equal(t, + leavesShouldBe{"a": "true", "b": "false"}, + forInput("(a: true, b: false)")) + require.Equal(t, + leavesShouldBe{"a.b.c": "true", "d.e.f": "false"}, + forInput("(a: (b: (c: true)), d: (e: (f: false)))")) + require.Equal(t, + leavesShouldBe{"a(0)": "0", "a(1)": "1", "a(2)": "'2'"}, + forInput("(a: [0, 1, '2'])")) + require.Equal(t, + leavesShouldBe{"a(0)(0)": "0", "a(0)(1)": "1", "a(1)(0)(0)": "100", "a(1)(1)": "11"}, + forInput("(a: [[0, 1], [[100], 11]])")) + require.Equal(t, + leavesShouldBe{"a('k0')": "0", "a('k1')": "1", "a(2)": "2", "a((x: 3))": "3"}, + forInput("(a: {'k0': 0, 'k1': 1, 2: 2, (x: 3):3})")) + + // Array root + require.Equal(t, + leavesShouldBe{"": "false"}, // An unfortunate side effect of everything being a set + forInput("[]")) + require.Equal(t, + leavesShouldBe{"(0)": "true", "(1)": "false", "(2).a": "1", "(2).b": "'2'", "(2).c('three')": "3", "(2).c(4)": "'4'"}, + forInput("[true, false, (a: 1, b: '2', c: { 'three': 3, 4: '4' })]")) + + // Dictionary root + require.Equal(t, + leavesShouldBe{"(0)": "true", "(1)": "false", "(2).a": "1", "(2).b": "'2'", "(2).c('three')": "3", "(2).c(4)": "'4'"}, + forInput("{0: true, 1: false, 2: (a: 1, b: '2', c: { 'three': 3, 4: '4' })}")) +} + +type leavesShouldBe map[string]string + +// Runs ForeachLeaf on the result of the arrai source. Returns a dictionary of results (node path -> leaf value) or +// or nil if it failed to parse (instead of err, so it can be inlined) +func forInput(source string) leavesShouldBe { + leaves := make(leavesShouldBe) + tree, err := evalErr(source) + if err != nil { + return leavesShouldBe{"PARSING ERROR": err.Error()} + } + + ForeachLeaf(tree, "", func(val rel.Value, path string) { + valStr := val.String() + if valStr == "{}" { + valStr = "false" + } else if _, ok := val.(rel.String); ok { + valStr = "'" + valStr + "'" + } + leaves[path] = valStr + }) + + return leaves +} + +func eval(source string) rel.Value { + value, _ := evalErr(source) //nolint:errcheck + return value +} + +// Evaluates the arrai source and returns the result, or nil if it failed to parse (instead of err so it can be inlined) +func evalErr(source string) (rel.Value, error) { + ctx := arraictx.InitRunCtx(context.Background()) + value, err := syntax.EvaluateExpr(ctx, "", source) + if err != nil { + return nil, err + } + return value, nil +} diff --git a/pkg/arrai/out_test.go b/pkg/arrai/out_test.go index 02fbe2f8..84abbde6 100644 --- a/pkg/arrai/out_test.go +++ b/pkg/arrai/out_test.go @@ -463,7 +463,7 @@ func testAssertFiles(t *testing.T, dir, script string, files map[string]string, content, exists := files[relPath] assert.True(t, exists, fmt.Sprintf("unexpected file exists: path=%s, relPath=%s", path, relPath)) - f, err := ctxfs.ReadFile(fs, path) + f, err := afero.ReadFile(fs, path) assert.NoError(t, err) assert.Equal(t, content, string(f)) diff --git a/pkg/ctxfs/ctxfs.go b/pkg/ctxfs/ctxfs.go index 28f09690..c3c3e9ee 100644 --- a/pkg/ctxfs/ctxfs.go +++ b/pkg/ctxfs/ctxfs.go @@ -2,7 +2,6 @@ package ctxfs import ( "context" - "io/ioutil" "path/filepath" "runtime" "strings" @@ -52,16 +51,6 @@ func RuntimeFsFrom(ctx context.Context) afero.Fs { return defaultFs } -func ReadFile(fs afero.Fs, filePath string) ([]byte, error) { - f, err := fs.Open(filePath) - if err != nil { - return nil, err - } - defer f.Close() - - return ioutil.ReadAll(f) -} - func ToUnixPath(p string) string { //nolint:goconst if runtime.GOOS == "windows" { diff --git a/syntax/bindata.go b/syntax/bindata.go index c66b6930..61b5e45d 100644 --- a/syntax/bindata.go +++ b/syntax/bindata.go @@ -1,7 +1,7 @@ // Code generated by go-bindata. DO NOT EDIT. // sources: // syntax/implicit_import.arrai (921B) -// syntax/stdlib.arraiz (3.361kB) +// syntax/stdlib.arraiz (3.37kB) package syntax @@ -90,7 +90,7 @@ func syntaxImplicit_importArrai() (*asset, error) { return a, nil } -var _syntaxStdlibArraiz = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x96\x67\x58\x53\x69\x9b\xc7\x0f\x2d\x74\x11\xa5\x58\x40\x29\x52\x0d\x04\x41\x41\x9a\x80\xd2\x4c\x28\x86\xde\x25\x42\x42\x82\x89\x41\x8a\x80\x14\x31\x10\x40\xa4\xc8\x4b\x0d\x4a\x11\x69\x41\x14\x11\x44\x09\x91\x08\x41\x05\x82\x09\xd2\x41\x11\xc4\x42\x0b\x02\x43\x93\xbe\xd7\x3b\xb3\xb3\xab\xee\xb5\xb3\xbb\xb3\xef\xf9\x72\xae\xf3\xe1\xb9\xdb\xf9\xdf\xff\xe7\x77\x0e\xc6\xc5\x2d\x06\xf0\x01\x7c\xc0\xaf\x8f\x10\x00\x00\x3e\xf8\x4b\x28\x8c\x9f\x3a\x22\x30\x10\x81\xe9\x6e\xb3\xee\xec\xbc\x09\xe9\x60\x41\x6a\xec\xce\x41\x59\xe0\x76\x65\xeb\x6e\x18\x93\xd5\x67\x0b\x56\xed\xb6\xee\xe4\x86\xdf\x20\x41\xf5\x04\xbc\x1d\x31\x24\x5d\xb2\x93\x00\x74\xbd\x61\x4d\xa5\x54\x97\xbc\x7e\xd2\x61\x46\xd2\xf9\x6b\xd6\x11\xa7\x49\xc9\x9c\x92\xcf\xbe\x8a\x26\x2a\x86\x89\x26\x79\x31\x00\x00\xec\xec\x9c\x83\xf1\xf2\xa9\x38\x59\x4e\xda\x01\x00\x80\x07\x00\xe0\xbf\xaf\x45\x01\x00\x00\x1c\xde\x37\x04\x8b\x84\xf8\x61\x82\xd1\x21\x17\xd4\x7d\xf0\x38\x08\x22\x30\x50\x0d\x81\x81\xfc\x5e\x1d\xc4\x0f\xaf\x8e\xc3\xfb\xc6\x65\x32\xae\xd6\x69\x0b\xc5\x0f\xe4\xdf\xf7\x1e\x52\x9f\x27\xd1\xc1\xc3\x9e\x85\x5e\x2f\xae\xf1\xb4\xf0\x05\xc4\xd7\x90\x35\x32\x34\x9c\x47\x13\xee\x5a\xbf\x2a\x69\xa7\xfd\x76\xba\xd0\x2b\x2a\xdb\xf7\xfc\x99\xc6\xe4\xdc\x65\xf5\x5d\x30\x3a\xf6\x59\xa0\x4c\x47\x33\x8e\xf7\x59\x0f\x59\x84\xe4\x6b\x57\x68\x13\xd7\xc3\xa7\x0d\x67\xda\x2b\x9d\x48\xd8\xa5\xe2\xab\xd3\x7e\xfc\xe8\xe3\xd1\x1d\xcb\x73\xdb\x05\x3a\x63\x90\xc3\x07\x5e\x76\xc6\xaf\x47\x5c\x8e\x1b\xaa\x2e\x3f\x5f\xd0\x4a\x11\xbb\xb6\xfe\x9d\x6b\xf6\xbc\x58\xb6\xca\xce\x65\x57\x0b\x99\x54\xbc\x84\x45\xa8\x45\xb4\xfd\x90\xf5\xb1\x37\xa6\xff\xe0\x96\x1f\x3e\x19\x77\xb4\xf3\x4e\x40\x31\x5b\x31\xd7\x5b\xce\xf9\x34\xff\x5d\x47\xd9\x64\x78\x65\x47\x4a\x55\x8a\x43\xf1\xd1\xa3\x3e\x5a\x4c\xad\x09\x77\x0b\xb4\x4f\xd2\x3a\x8b\x1d\x3c\x15\x9a\x6a\x47\xf6\xc6\x5b\x59\x7c\x4c\x8d\xb6\x6f\x1b\x9f\x44\x39\x0b\x69\x88\xb8\x6f\xde\x9c\x37\x95\x8d\x17\x29\x7f\x2c\x61\xae\x02\x27\x55\x2a\x07\xf6\x85\x08\xd7\x0f\xca\x48\xec\xdc\xde\x18\xfe\x7e\x09\xd6\x37\xfe\xa2\x9e\x1e\x65\x36\x1e\xd7\x95\xca\xf4\x21\x40\x88\xd9\x6e\x59\x07\xb7\x3d\xc8\xa0\x2c\x8c\x75\xca\xd9\xab\x87\x03\x8f\x08\x1c\x5f\xdc\x1a\x44\x29\x90\x0c\xd0\x6d\x29\xf2\xf3\xce\xd8\xaf\xde\x90\x7d\x3a\xe9\x08\x4c\x86\x54\xd8\x41\x73\x2a\x61\x22\xe8\x88\xe0\x22\x3d\xc9\x57\x0f\xee\x29\x6b\x4e\x13\x0f\xd1\x6a\x7d\xf6\xed\x12\x12\x9a\xa7\xbe\xd5\xb8\xbf\x99\xc9\xab\xb5\x44\x58\xd9\x75\xaf\x31\xe0\x70\xf1\x4c\xbf\x67\xc6\x1e\x54\x32\x5d\xb8\x42\xc4\x5d\xa1\xc5\x72\xe6\x9b\xc2\xec\x7b\x36\x9a\x84\x5a\x8a\x14\x32\x54\x4b\xbb\x9d\x63\xd1\x5e\x6e\x66\x4b\x4f\xd7\x18\xbe\x1e\x86\xf6\xfa\x70\xa8\x07\x31\xce\x38\x11\x21\x69\x71\x27\xf4\x93\xfb\xf3\xa5\x40\xc1\x90\x80\x33\x97\x2c\x56\xe7\x86\xef\x27\xdc\xd5\x20\xcc\xb9\x16\x15\xd0\x2e\xc3\x66\x96\xa8\x66\xd0\x44\x37\x7e\x05\xa4\x42\x12\xf5\x51\x16\x81\xd2\x3f\x01\x6d\x69\x6e\xd3\xa2\xe3\xe5\x23\x39\xef\x10\xd3\xfd\xec\x6f\xeb\x41\xc4\x83\xa5\xd1\xb8\x3c\xe8\x35\x2e\xbe\x47\xad\x26\xd2\xfb\xa0\x67\x71\x0e\xf9\x92\x0f\x54\xdd\x6e\x9d\x7b\xfa\x55\xff\x15\x95\xc9\x6b\x2c\x1b\xa7\x22\x98\xa1\xa3\xec\x8e\xab\xe3\xac\x68\x94\x97\x59\x92\x7f\xac\x64\x93\xb6\x3d\x5f\xb5\xb2\xc1\x87\xa8\x4b\x6f\x40\xbb\x71\x4c\x14\x83\x6e\xcd\xda\xc2\x8f\x0f\xbd\xce\x5c\xdd\xda\x11\xbb\xd6\x8b\xc9\xfe\xb6\x90\x3f\x6b\x6b\x60\x95\x53\xa0\xcb\x72\x7e\x96\xca\x5e\xbb\x07\xe5\xd2\x6b\x9c\x12\x09\x2d\x05\x97\xf4\x9a\x4f\x4b\x89\xaa\xe5\x52\xe8\x95\xaa\x7b\x43\x69\xf9\xdb\xfc\x7f\x4a\x56\x0d\xc1\x2b\xe0\xcd\x09\x00\x85\x3c\x7f\x25\x59\x9d\xff\x59\xb2\x41\xe1\x97\x82\x11\x61\x90\xa0\x60\x5f\x2c\xe6\x02\x04\x85\x45\xfc\xfb\xa6\x55\xb9\xbc\x09\x2a\xb6\x14\xa3\xb1\xce\x2f\x94\xc5\x0b\xbc\x49\xca\x8f\xf0\xeb\x75\xc0\x5d\x49\xcd\x60\xf4\x7b\x5d\x70\x72\xc3\x64\xa9\x72\xdf\xe7\xd0\x52\x89\xdf\x89\x9a\x59\xe6\x2d\x8a\xc9\x7d\x40\x8d\x95\xa3\x46\x9f\xa7\xad\x12\x19\x60\x91\x4f\xe8\x2d\x1e\x40\x46\x42\x77\xb7\x22\x51\xc4\xca\x92\x04\xdd\x3c\xb5\x30\xc8\x0b\x25\xf3\x6a\x75\x55\x89\x9c\xc0\xb7\xd5\x3e\x6f\xf3\x06\xd9\x75\x59\x5f\x3d\x69\x5e\x23\xd3\x4c\xeb\x99\x92\x9b\x3f\x50\xe6\x61\x9e\xa0\xc4\xa8\xb0\xed\xaa\x72\xd9\x3b\x70\xc2\x6a\xd4\x38\x29\xff\x3b\x8f\x3b\xe9\x50\x0c\xac\x5d\x7e\xbd\xde\xa6\xfe\x49\x8c\xf2\x86\xab\x54\xff\x69\xaf\x69\x57\x1b\x49\xdf\xe6\xec\x8f\xdc\x46\x53\x9a\x9b\x74\xf7\xcd\x57\x73\x37\x2c\x24\xd8\x5f\x20\x0b\xeb\x9a\xf9\x54\xf5\xf9\xd5\x16\x7e\x83\xab\x63\x6b\xa8\x51\xcd\x99\xad\x0a\xb0\xea\x08\x38\x07\xfe\x38\x28\x2a\x60\x00\x73\xe0\x53\x68\xf1\x52\x43\xd6\x57\xc5\xe9\xbd\x18\xc8\xb8\x7c\xec\x17\x45\xeb\xfa\xb7\x0f\x9b\x2e\xf2\x97\x4e\x5b\x68\xd4\x4a\x92\x85\x88\x1e\x34\xae\xa8\xb8\x35\xa9\xec\xcf\xe9\xcd\xda\x5f\x1b\x0b\x37\x65\x22\xc3\x0c\x16\x9e\xed\x5e\x67\x16\x26\x8d\xce\xf0\x99\x0d\x97\x9c\x1d\xbc\x40\x78\x9c\x23\x78\x43\x23\x98\x91\xc0\x73\x54\xb3\x69\x6f\x67\xc8\x42\xde\x6b\x51\x56\x67\x88\x72\x3d\x35\x31\x68\x59\x10\x8e\x34\x76\xf1\x49\x3f\xf5\x35\xf6\xd4\x78\xac\xa8\x9f\x6d\xf8\x76\x11\xca\x97\x7a\xab\x94\xe5\xff\x25\x66\xbd\xce\x7a\xba\x3b\x8f\xe3\xa1\xbe\x4c\xdd\xf4\xfb\xa8\xc9\xf7\x97\x09\x1f\xee\xb1\x5e\x1b\x40\x33\x92\x85\x0f\xcc\x64\x82\x0a\xd6\x22\x2d\xe3\x4b\xa7\x7a\x55\xc8\x21\x95\x81\xd0\xca\xcd\xeb\x8d\xfa\x01\x9c\x87\xcb\x56\xcc\xc5\xc7\xb4\x96\xf6\x35\x31\xad\xe4\x64\x33\x04\xd5\xad\x14\x8b\x0a\x1c\x98\x1a\x01\xd9\x15\xfc\x56\x33\x55\x7b\x6f\xe9\xb9\xd9\x60\xf6\xab\xce\x51\x46\x46\xe6\x97\xd3\xed\xed\x78\x3a\x42\xbe\xc5\xd4\x20\x7b\x91\xfc\x07\xc6\x52\x3d\x50\xa7\x57\x72\x40\xd9\x2f\xa1\xce\xa6\x68\x46\x31\x58\xa5\x34\xcb\x86\x28\xce\xd5\x87\x4d\xdc\x9b\x5b\x9e\x20\xc7\x40\xa8\xc5\x9c\xd9\x87\xdb\x0d\x55\x1f\x8d\xb2\xec\xa6\x82\x1e\x0a\x50\xa7\xef\xde\x43\xde\x7e\x21\x94\xad\x74\x88\x1e\x08\x09\x1b\x74\x2f\x7a\xa2\xd0\xc6\xa1\x20\xf1\xb9\x92\xdc\x9a\xb5\x4a\xd2\x76\xcd\xd7\x43\x32\x97\x56\x5e\x1d\x4c\x39\xcb\xd4\xe3\xaa\x21\x26\x96\x4e\x0b\x2b\xd5\xf9\xbf\x7a\x79\x2c\x7e\xff\x9d\xb0\x2a\xfb\x7f\x54\xd8\xe2\x9b\xeb\x0d\x98\xa6\x03\x9a\xb0\x8e\xca\x19\xd2\x8d\xe2\xdb\x89\xe8\x50\x8f\xf2\x5e\xd2\x9c\xff\x8e\x29\x28\x41\x8e\x79\xcf\x07\xec\x50\xdd\x92\x17\x0a\x35\xb8\x21\x75\x36\x91\xd6\xd5\xd0\x21\xfc\xb0\x4e\xb4\x44\x3c\xb6\x3b\x58\x38\xc4\x3b\xb7\xd1\x08\xb7\xb1\x06\xf7\x4f\xab\xe0\x68\xcb\xbb\xb4\x71\xed\xba\x56\x88\xdb\xaa\x49\x8a\x4c\xc5\xbe\x47\x14\xb8\x0e\x36\xa2\xc0\x70\x48\x93\x85\x94\xa5\x72\xd0\x36\x82\xf3\x31\xe8\xc9\xd3\xce\xee\xb1\x1d\x37\xde\x39\x16\x01\x69\x29\xa5\x02\xf0\xad\xb9\x46\x14\x8b\xc2\xfc\x80\x05\xd0\x54\x6c\x8b\x4a\x4d\x3d\xd0\x63\x6a\x4c\xe1\x29\xef\xe5\xa2\x1e\x7a\x94\xf5\xc0\xbc\xcf\x3f\x21\x10\x6b\x8c\x80\xed\x97\xc3\xbf\x73\xc8\xbc\x13\x26\x45\x1c\x0c\x15\xdd\xf2\x22\xf7\xd1\x73\x98\x86\x17\xba\x9f\x3e\x34\xcb\xed\x6c\x76\x08\x3c\x5a\x7b\xfc\x8c\x7e\x9f\xe2\xe2\xca\xe2\x22\xae\xe8\xdd\xdc\x34\xf2\xd3\x76\x53\xc9\xaa\xd9\xa1\x97\xe2\x59\x72\x60\x6d\x42\xb0\xd5\xb0\x62\x34\xce\xc9\xd5\x33\x17\xee\x54\x30\x3a\xe4\xc2\xf4\x5c\x56\x7f\xbf\x09\xba\x66\x64\x64\xa8\xf5\x64\x4f\xb3\x89\xef\x49\x33\x3d\x50\x65\x4d\x8c\x21\x89\xe1\xa9\x6d\xe7\xd7\x86\x9a\xc0\x42\xcc\xe5\xdb\xfd\xb4\x61\x4f\xbe\x88\x1e\x9d\x84\x32\xe2\xfa\xaf\x5e\xdd\x2d\xdb\xe9\x76\xd3\x3f\x28\xfa\x19\x6e\x78\x31\x77\x7c\xb9\xc9\x4f\xaf\x3a\x7d\x4a\x2d\xb7\x81\xa2\x5e\xe6\xe4\xc7\xb0\x81\xd8\x9b\xfb\x0a\xaa\x10\xd2\x9e\x13\x87\x15\x73\x67\x56\xf0\x37\x35\x06\x39\x4d\xfc\xae\xc2\xa4\x99\x7a\x0f\xd8\x67\x22\x5c\x20\x99\x76\xd8\xe3\x63\x46\x8f\x08\x42\x93\xfe\x52\x22\x74\xb2\x4b\xfc\xb1\x28\x13\x98\xa3\x0f\x23\x5c\x8c\x96\x1a\x3b\x34\x64\xb4\xf0\xa0\x94\xbf\xe8\xb7\x4d\x90\xd5\x68\x71\x64\x9a\xd7\x66\xec\x08\x8b\x2d\x46\x7c\xe1\xff\x52\x40\x01\x45\x1d\x1a\x77\x44\xba\x6b\x9b\x38\x76\xf6\x25\x1e\xf8\xd2\xe4\x8f\xfd\x04\xbb\xeb\xa5\xe4\xba\xd7\x93\xf0\x71\x9e\xf5\xe6\x88\x3f\x3a\x39\xd9\x49\xe1\x0d\x75\xce\x23\xad\x4c\x01\xa7\x94\x36\x7e\x90\x5d\xb7\x54\x2c\x57\x14\x90\x21\xc7\xb0\x08\x68\x2c\x79\x05\x33\x88\x9c\x2a\xdd\x33\x2b\xec\xb7\xad\x90\xf2\x9a\x6e\x77\x11\x34\xc9\x47\x6e\xbd\xae\x21\x4e\xca\x03\x47\x44\xb7\x2c\xb3\xac\x4c\x07\xc8\xa2\x87\x47\xd1\x60\xcf\xfd\xa1\xd9\xec\x46\xa5\x8d\x80\xcf\xfa\xf0\xc3\x33\xb9\x39\xbe\xa3\xb5\xd6\xc9\xef\xd6\xae\xd4\x0a\x95\x9c\x1a\xe4\x96\x60\x90\x53\xc6\xc2\x94\xc1\x51\x31\x4f\x88\x77\xd5\x87\x2c\x7d\x5e\x9b\x24\x38\x7e\x2c\x9b\x59\xd4\x32\x89\x0d\xea\x73\x1b\x98\x28\x63\x09\x3b\x55\x51\xa4\x5b\xae\x80\xd9\xb5\x28\xe9\xe6\x54\x7d\xd2\x64\xff\xba\x1e\x88\x3e\x1f\xf1\xb1\x8f\x5f\xab\x77\x11\x1f\x5e\x5e\xbd\x0a\x92\x6a\x4f\x31\x42\x3f\xc5\x4b\x87\x87\x2f\xcf\xea\x9b\xbb\x86\x1b\xe6\xd7\x1b\x21\x48\xd9\xf7\x2f\x74\xe1\x69\xa2\xfe\xdf\x83\x83\x76\x0c\x27\xa7\x74\xc6\x10\x7c\x6b\x1f\xa8\xf3\x15\x99\x4f\x77\xb0\xc1\x03\xd2\xb0\xc9\xa2\xef\xe2\xc8\xf0\x4c\x76\x74\x45\x50\x6d\x10\xb1\x56\xb7\xeb\xf5\xc7\x29\x13\x4a\x04\x07\x75\x96\x28\x66\xbc\x4c\xb1\xb9\x6d\x78\xf0\xdb\xb7\x98\x53\x2d\x03\x73\x6c\x8f\x52\xde\xc5\xf0\xad\xf3\x67\x2c\x7a\xbe\x7c\x08\x5f\xc9\x98\x54\xf6\x3f\x34\xc7\xb8\xc6\xf1\xa7\x09\xd3\x63\xf2\x55\x65\x78\x00\xc0\x48\xec\xaf\x4c\x58\xf7\xff\x6a\xc2\x7f\xbc\xfe\xb0\xe1\xc8\xb4\xe6\x4b\xb5\x1a\xbb\xe3\xe6\x0c\x5e\x5a\x36\xc4\xec\xd1\x0d\x8d\xde\x82\x57\x3f\x5f\xcd\x65\x10\x84\x5d\x42\xfa\xdb\xbc\xf2\x4c\xad\x37\xd8\x59\xb2\xa5\x5d\x96\x23\x3d\x63\x24\x7c\xe8\xe0\x58\x69\xad\x68\xcd\xae\x7a\xc9\xa5\x66\x9a\x2b\x4e\x8e\xcf\x8f\x52\xbc\xc0\xf1\xb6\x50\x28\xef\xfa\xc5\x9a\x02\x79\xf0\x19\xbd\x58\xe5\xd6\x3c\x42\x7c\xad\x63\x51\x00\x31\xcc\x01\x34\xe1\x6d\x26\x54\x2e\x46\x2c\x6a\x53\x35\x10\x8c\xf3\xb2\xf6\x40\x0c\xa1\x40\x92\x96\x33\xd2\x97\x05\xa1\x13\xc8\xc9\x7c\x8e\x1a\x73\x0d\x4d\xee\x54\x99\x95\x6e\x52\x40\x28\xd3\x49\xef\x56\xbf\x76\x9f\xdc\x53\xf4\xf7\x41\x59\x7d\xf3\xb7\x4c\xa7\xfd\xc0\x49\xf9\xb6\x42\xf0\x4d\x7e\xab\x87\xad\x4f\x87\x4d\x54\x1c\xc7\x8c\xca\xd8\xb4\x63\x06\x98\xe5\xf0\xdb\xd7\x0f\xd6\xf3\x7e\xbe\x47\x2a\xbb\xdc\x9b\x3d\x38\x37\xda\x77\xaa\x3d\x2d\x52\x2e\x69\xe4\x8e\x86\x47\x04\xae\x61\xe4\x50\x07\x4f\xce\xd7\xc0\x40\x5a\x61\x37\xda\x3e\x02\x77\xfa\x86\xcf\x6c\x43\x8d\xda\x6f\x53\xa5\xad\x35\x94\x24\x92\xe0\x7d\x46\x3b\xb9\xbd\xa3\xfc\xf3\x2e\x95\x73\xe0\xd4\xea\xd0\xed\x27\xb2\x90\x6f\x4b\xf2\x06\xdb\x26\x04\xb5\xd5\xd8\xaa\xa9\x76\x86\x45\xb9\x4b\xba\xd1\xf9\x7b\xaa\x8f\xab\xff\x03\xe1\x42\xae\x00\xf2\x47\x38\x00\xc0\x9f\xf3\x5f\x7a\x1f\x86\x04\x63\xb0\x7f\xfc\x08\x6c\x6a\x73\x52\x9c\xc6\x6e\xd3\xb9\x2d\x71\x9e\x08\xf8\x2d\xb6\xe7\x10\xc4\xd5\xb0\x2e\x9f\x9b\xf3\xad\xb1\x92\x6b\x1c\x72\x3b\x2a\x51\x53\x4b\xc8\xca\x93\x12\xbe\xbc\x6b\x99\xa8\xb6\xa5\x2b\x58\x8d\x89\x2d\x79\x8e\x76\xa1\x5d\xe4\x7f\xfb\x48\x95\x37\x33\x2e\x4f\x3a\xd2\xee\xf3\x05\x1f\x97\x37\x4a\xf7\x51\x39\x87\x0d\xcd\x9c\x8b\x98\x4e\x97\xbf\x37\xb3\xf9\x01\x75\xc5\xae\x78\x17\x6e\xad\xfb\x05\xd6\x37\x4b\x46\x08\x8e\x0b\x3b\xa6\x6a\x34\x06\x9f\xc4\xfe\x0f\x1f\x8f\xbd\xd8\x67\x7f\xb2\x15\x19\x77\xa5\xe9\xa2\x40\x6a\xb4\xb1\xcf\x16\xa3\x29\x7f\xd4\x74\x02\x9e\x97\x48\x59\x18\xee\xeb\x31\xfc\xb3\xfb\x04\x45\xbd\x02\x12\x00\x00\xab\xbf\x03\x2c\x07\xa7\x18\xf0\x9f\xfd\xff\x08\xb7\x42\xff\x65\x22\x3f\xc2\xf5\xaf\x27\x7f\x64\x0c\x85\x9f\x4e\x25\xff\x6f\x51\xf8\xd7\x90\x3f\x6e\x8c\xce\x4f\x21\x8d\xb8\xfe\x36\xaa\xfc\x9a\xe4\x47\x2d\xe8\xfe\x94\xa4\x95\xef\xff\xb1\x8a\xbf\xa6\xf9\x71\xe8\x3f\xf7\x62\x2b\xf0\xb7\x65\x76\x0e\xc6\x03\xfa\x67\x08\x1e\x80\x07\x68\xe2\x00\x00\x6b\xc1\x7f\x7e\xfd\x5b\x00\x00\x00\xff\xff\xb0\x4a\x1a\xfe\x21\x0d\x00\x00") +var _syntaxStdlibArraiz = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x96\x79\x3c\x94\x7b\xdf\xc7\x2f\xfb\x5e\x84\x48\x84\x91\xb5\x61\x44\x29\x3b\x25\xc9\x52\x91\x99\xec\x65\x99\x31\x33\xcd\x34\xc2\x24\x59\x92\x31\x96\x84\x9c\x6c\x23\x34\x61\x30\x25\x4b\x96\x8e\x31\x99\x2c\x65\x19\x66\xac\x21\x89\x56\x6b\x96\x90\x9d\xe7\x75\xce\x79\xce\xf3\x54\xcf\xeb\x39\xf7\x7d\x9f\xfb\xbe\xfe\xb9\x5e\xd7\x1f\xbf\xef\x76\x7d\xbe\x9f\xdf\xfb\xac\x0d\x17\xb7\x04\xc0\x0f\xf0\x03\x3f\x3f\xc2\x00\x00\x78\xe3\x2e\x23\x50\xbe\x5a\x9e\xfe\xfe\x9e\xa8\xee\x16\xbb\xf6\xf6\x78\x48\x1b\x1b\x52\xee\x70\xd6\x9a\x0d\x6e\x55\xb3\xeb\xb6\x61\xb1\xfb\xce\x80\x35\xba\xed\xda\xb9\xed\xe3\x48\xd6\x06\x82\x17\xa1\x28\x92\x3e\x15\x26\x68\xbd\x56\xb3\xaa\x4e\xd1\xa7\xae\x1d\x75\x9c\x92\x3a\xff\x39\x75\x3f\x6c\x5c\x2a\x3d\xff\xa3\x8f\x8a\xb9\xba\x71\xac\x79\x66\x04\x00\x00\xdb\xdb\x67\x6d\xf8\xf8\xd5\x61\x56\xe3\x0e\x00\x00\xe0\x00\x00\xf8\xff\x6b\x51\x06\x00\x00\x8b\xf3\xc1\x63\xe0\x10\x5f\x54\x20\x12\xef\xa5\xe5\x8d\xc3\x42\x3c\xfd\xfd\x35\x3d\x51\x90\xdf\xab\x83\xf8\xe2\xb4\xb0\x38\x9f\xd0\x14\x66\x66\x85\x9e\x70\x74\x7f\x07\xb4\x0c\x23\xb5\xd2\xae\x18\x7f\xb5\xbf\x50\x6a\xee\xc2\x4d\x6d\xce\x8d\x98\x01\xc7\x0c\xdb\x96\xbe\x65\xc5\x4a\xdd\x83\x3d\xa4\x54\xa9\xb5\xc5\xc9\x8a\xfd\xeb\x32\xaf\xe1\x4d\xc5\x41\x57\x9c\x64\x10\x8d\xa9\x5c\xf6\x3b\x2e\x1b\x3e\xcf\x3e\x1d\xd6\xb2\xff\xee\x5a\x20\x28\xcb\xb2\x2c\xce\x3f\xc7\x80\xaa\x1f\x68\xa5\x58\x7b\xaa\xc0\x39\x79\x73\xeb\x64\xe4\xd7\x24\x3a\xc7\x8d\xd5\x4e\x35\x84\x5e\xef\x48\xbc\x52\x7b\xb8\xf1\xe6\x57\xd0\x2b\xca\x4a\x4f\xb3\x3c\x72\x3b\xb7\xcc\x76\x35\x89\x69\xc0\x79\x85\xee\x2a\x3b\xcc\xb5\x6a\x07\x3f\xf2\xe2\x90\xf8\xa9\x5d\xb7\x91\x1f\x44\x4b\xb4\x03\x1a\xe2\x9e\xdc\xbb\x18\x19\xe7\x26\xa0\x1c\x05\x23\x6a\x5b\x52\x35\x8e\x3c\xd1\x73\x88\x2b\x29\xbe\xdd\x71\x94\x9d\x85\x93\x7b\x90\xa0\x3e\x69\x5a\xd1\x43\x19\xd1\xb7\xa6\x44\x3e\x3b\x28\x47\xe4\x5b\xb3\x53\x8c\xcf\xbb\xe7\x7e\xf0\xb9\x11\x66\x1e\xd2\x20\x4e\x94\x36\xc9\x6a\x73\x91\xa6\x58\xc0\x1f\xe6\xb6\x40\xfb\x0f\x0f\x78\x11\x9c\x36\x70\x33\xae\x1f\x6a\x35\xa1\xf1\x5c\x43\x3b\x92\x24\xe3\xc4\x8f\x19\x1a\x24\x09\xd4\x49\xb8\xa0\xa1\x97\x57\x02\x28\x32\x8e\x05\x87\x0f\xab\x4c\x84\xb5\x24\xab\x75\xb6\x2f\x7b\xdf\x4b\xf5\xf9\x4a\x56\x3c\x92\xf8\x7c\xa4\x8c\x44\x78\x76\xf1\x8d\x45\x34\xc5\xce\x7f\x14\x2b\x3d\x2f\x98\xc3\x4e\x06\xb7\x9f\x39\xf0\xcb\x84\x05\x9e\x28\xbd\x09\xeb\xed\x10\xfd\x40\xab\x4d\x55\x47\xd1\x16\x67\x11\xdc\x86\xf2\xed\x9d\x67\x2a\x77\x4a\x0f\x13\xd7\x45\xdd\x9f\xce\x64\xa6\x68\x7b\xcb\x5e\x36\x0b\x1b\xd0\x10\xb3\x74\x09\x82\xa0\xc6\x10\x27\x99\x87\xf2\xc6\xbc\xc7\x51\xc1\x6f\xe9\x46\xd6\x26\x9a\xd7\x65\x12\xa3\xba\x76\x34\x1d\x5f\x58\xd8\xca\xda\x21\x7e\xa1\x64\xb0\xf6\xe8\x7b\xfa\xde\x91\x49\x4a\xba\x6b\x11\x61\xaf\xbf\x21\xed\x46\xbf\x87\xb1\x08\x51\x2c\x94\xcd\xb9\xb4\xc1\xd2\x7c\xdc\x33\xb5\x5b\x5d\x11\xad\x9c\x92\x92\xaa\xf6\xb5\x01\x26\x38\xe9\x72\xff\x80\x00\x9f\x52\xa9\x30\x23\x61\x8e\xc7\x6f\xb7\x65\x8e\x1d\x76\x92\xee\xd8\x89\x27\x57\xa1\x34\x36\x45\x14\x9e\xef\xe2\xc3\x5f\x54\x53\xae\xb2\xc7\x7b\x54\x3e\x44\x9a\x1f\x1b\xce\x9c\x95\x5e\x30\x90\x07\x88\xa2\x05\x60\xdb\xb7\x64\x4c\x55\x97\x20\x79\xa6\x8e\xc3\x82\xde\xfc\x6c\x62\x28\x91\xd8\x99\xd4\xe2\xc1\x6d\xbc\xd6\x48\x18\xc2\x69\x44\x75\x59\x7e\xce\x45\xf1\x64\xc5\xcb\x98\x55\x37\x83\xee\x9f\x3d\x34\x38\xb6\xb1\x9c\x3e\xaf\xbe\x55\xdc\x03\x1e\x5e\xc8\x1b\x92\xbf\x91\xa1\x65\x1c\x90\x53\xea\x27\x37\x38\x25\x95\xc6\x1b\x78\xb5\x47\x7e\xc6\xda\xd9\x8a\x9a\xd0\xab\x0f\x72\xd1\xc6\xc7\x40\xa1\x2a\x33\xe1\x86\x9b\xe2\x7f\x6a\x97\xf1\x48\xff\x0c\x8a\x13\x00\x6a\x78\xfe\x4a\xbb\x47\xfe\xb1\x76\x03\x82\x2f\x07\x7a\x5e\x83\x04\x04\xfa\x60\x50\x5e\x10\x04\xc6\xf3\xbf\x57\xae\xd8\xa9\x23\x20\xd7\x4a\x82\xc1\xbe\x30\x5f\x10\x2d\xd8\x71\x2b\x3b\xc4\xb7\xd7\x11\x7b\x35\xf1\x2e\xf3\x95\x87\x17\xcc\x05\x95\xaa\xc1\xfd\x88\x43\x57\x3d\x7a\x3b\x6c\x6a\x89\x8f\x1c\x91\xf1\x98\x4e\x00\xd1\xc3\x2f\x30\x96\x89\x4c\xf0\xce\x0f\xc8\x4d\x1e\x40\x61\xb7\xbe\xa8\x0a\x71\xa7\xad\x15\xc9\x7a\xc3\x64\x7e\x80\xcf\x9a\xca\xa7\xdb\x59\xbc\xf3\x30\xae\xa5\xe2\x59\xcb\x45\x5e\x87\x4e\xbb\xeb\x47\x2d\xcb\x15\xea\x19\x3d\x13\xa0\x39\x99\x02\x37\xcb\x18\x55\x66\xd1\x99\xce\x62\x27\xf1\xfe\xc3\xb6\x23\x66\xb7\xb2\x57\x78\x5c\x49\xfb\x22\x6c\x5a\x95\xd6\xaa\x4f\x57\x57\x45\xa8\xad\x3b\xcb\xbe\x3a\xe6\x31\xe9\x7c\x5a\xca\xa7\x3e\xed\x1d\xb7\xe9\x84\xce\x46\xa3\xeb\xc6\xcb\xd9\xb8\x93\xbb\xa7\x3f\x41\xe6\xd7\x74\xb2\xe9\x5a\x73\xcb\x0d\x02\x46\xd7\x47\x57\x11\x23\x3a\x53\x9b\x45\x60\x8d\x61\x70\xba\xfd\x93\x80\x30\xbf\x7e\x94\xcc\x87\xa0\xdc\xc5\x9a\xd4\xcf\x2a\x93\xe2\x28\xc8\x7b\x25\xc2\x27\x15\xbb\xea\xae\x92\xba\x4b\x02\x94\xc9\x93\xda\x15\x52\x54\x61\xa2\x1b\x83\x2b\x2c\x6a\x55\x36\xed\x63\x72\xbd\xde\xe7\xda\xfb\x1b\x0a\xa1\xd7\x8c\xe6\x7f\x15\x5d\x63\xdd\xbf\x35\x32\xc5\x7f\xe2\x75\xfe\xa9\x01\xaf\xc8\x27\xe9\x42\x71\xda\x81\xcc\x18\x9e\x03\x3a\x75\xe2\xed\xf8\xf9\xcc\x66\x31\x76\x3b\x5e\xad\x9a\x1e\x1b\xb0\x24\x64\x0f\x37\x73\xf2\x4e\x36\xf9\x4c\x30\x79\x4f\x10\xf3\x3d\x13\xbc\x45\x46\xf8\xd0\xef\x50\xd8\xe8\x4f\x11\x6b\x95\x76\x93\xdd\x99\x1c\x25\x86\x0a\x95\x93\x6f\xc2\xc6\xdf\x5c\x89\x7c\x9b\xc7\x6e\x36\xb2\xbe\x7b\x5b\x44\x66\x2a\x85\x37\x67\x35\xd4\x2a\x9a\x32\xd1\xab\x4e\xc5\x3f\xf4\xb7\x7e\xb8\x71\xb3\xd6\xd0\x8f\x53\xbe\xe0\x9b\xa5\xe4\xa8\xee\xa2\x74\x1d\xcb\x16\xa4\x78\x57\x48\xcb\x56\x85\x9c\xe3\xc8\xd2\xf6\x4b\x2b\x12\xb0\x9d\x2a\x16\xbf\x63\xe0\x72\x1a\xb5\x47\x63\x96\x36\x3c\x3c\xb7\x94\x7c\xce\x81\xa7\x0d\x3f\x13\x51\x0e\xef\x85\x0b\xc8\x8c\x26\xba\x21\x8e\x7d\x4b\xe7\x4d\x7b\x61\x7d\xde\x02\xc9\xcc\x05\xab\x53\x52\x4f\x13\x25\xb9\xfa\x30\xb1\xe2\x19\x85\x31\x20\xa6\xa7\x66\xc4\x71\x69\xac\xa8\xb5\xd6\x48\x98\x55\x37\x9d\xb7\x44\x90\x3e\xf9\x20\x0f\x7e\xef\xb9\x70\x9a\xea\xbe\x46\x7f\xc8\xb5\x01\x57\x72\x95\x72\x0b\x87\xf2\xee\x8f\x0f\xa9\x4d\xa9\xcb\x24\x3d\xe7\x6c\x03\x38\x6b\xf1\xdb\xcb\xbd\x09\xa7\x58\x06\x5c\xe5\xc4\x58\xca\xa4\x88\x6a\x25\xfa\xe5\x8b\x83\xd1\x7b\xb2\xae\x15\x9f\xfb\xa5\xe8\x0c\xae\xbe\xda\x88\x65\xd1\xaf\x63\xd3\xf6\x70\x8a\x14\x97\x7b\x2f\x16\x19\xe4\x56\xd8\x4b\x9a\x45\x6f\x5b\xf0\xc6\x80\x58\x79\xde\x60\xc7\xd2\x86\xcc\x20\x6b\xa3\x38\xd9\x53\xb1\x8c\xce\x9a\x36\x91\x92\x4a\xb1\x7c\x49\x42\x77\xa0\x08\xfe\x62\x46\xad\x29\x76\x7d\xd5\x1e\x9d\x54\xc4\xd1\x92\x79\x79\xfd\xc6\x4d\x5d\xbc\xcb\xb2\x79\x82\x42\x91\x74\x19\xcd\xfe\x08\x26\x24\xc7\x78\x50\x87\x0d\x57\xa4\x73\x30\xd6\x03\xb3\x51\xc8\xf1\x63\xe7\x5d\x09\x6d\x71\x43\x50\x32\x90\x94\x40\x11\xb4\xdf\x9c\xad\x45\xb0\x69\xac\xb7\x18\x00\x49\xc7\x34\xa8\x97\x57\x03\x3d\x16\x66\x34\x9e\xc2\x5e\x2e\xfa\xbe\xb2\xd4\xc7\x96\x7d\xe8\x18\x7f\x8c\x99\xa7\xcd\x1e\x10\x6e\xc8\x31\x25\xeb\x9a\x2c\x71\x20\x48\x6c\xd3\x83\xda\xd7\x98\xce\x32\xf6\xea\x7e\x5a\x72\x22\xa3\xbd\xde\xd1\xff\x40\xc5\xa1\xe3\x86\x7d\x2a\x0b\xdf\x16\x16\xb0\xe4\xa1\xd9\x49\xf8\x87\xad\xba\xfc\xe5\x13\xfb\x5e\x48\xa6\x82\xc0\x7a\x91\x81\xb6\xaf\x55\xc2\xb1\x30\x67\xf7\x0c\x7b\x58\xce\xc8\xa0\x13\xcb\x7d\x49\xeb\xcd\x06\xef\x0d\x53\x53\x63\xdd\xaa\x5d\xf5\xe6\x3e\x47\x4f\x18\xf0\x3e\x2c\x8f\x30\x26\x31\xdd\xf5\x1c\x7c\x5b\x10\x63\x18\x88\xa5\x52\xab\xaf\x9e\x4d\xd5\x27\xb1\x03\xe3\xd6\xcc\xa8\x57\xd7\xaf\x8b\x2a\xb6\xbb\xc4\xa3\x03\xc2\x7f\xc5\xbe\x5e\xc8\x78\xbf\x54\xe7\x6b\x50\x9a\x3c\xa1\x99\x51\x43\xd3\x2a\x80\xf9\x32\x4f\x43\xce\x59\xfa\x08\xa9\x47\x26\x3d\x23\xbe\x56\xc9\x98\xfa\x86\x8b\xd7\x1e\xe0\x34\xf7\xbd\x6e\x23\xc7\x32\x78\x3c\x7d\x3c\xc4\x09\x92\xe2\x80\x39\x34\x6a\x5a\x16\x29\x3c\x8e\x96\xdd\xd9\x48\x75\x8a\x3e\x18\x66\x6e\x03\xf5\x66\x06\x4b\x30\x12\x09\x83\x83\xa6\xf3\x8f\x29\x02\xe4\xaf\x1b\xbc\xb6\x23\xb9\xa1\x49\x1e\x1b\x84\x61\xf6\xb4\x04\xf1\x39\xfa\x85\xa0\x32\x82\x3e\xf8\x1e\x0a\x77\xd5\x33\x87\xb6\xf7\xc5\xca\x7c\xaa\x43\x63\x3e\xd8\x3c\xf0\x50\x75\x16\x77\x8f\x7c\x37\xc7\xee\xd8\x8f\x46\xde\xbe\x0d\x53\xee\xa0\xcf\xba\x25\x15\x28\x63\x55\x93\xde\xef\x9d\xae\x5c\xcc\x05\x91\xfd\xee\x82\x98\x27\xfd\x6a\xf3\x5f\xda\x18\x85\x4e\x50\x76\x7d\x11\xf1\xdd\x52\x4e\x68\x6e\x74\xb8\xc4\x3b\xce\x4f\x6d\xba\xa9\x2d\x49\xca\x04\x87\x84\x37\x2c\xb1\x6d\x2d\xfa\xa9\x62\xf2\x23\x48\xb0\xfb\x9e\xa0\xb4\xe9\x5a\xd5\x75\xbf\x8f\x86\xf6\xf2\x53\x19\xe9\x3e\x23\x15\x76\xb7\x87\x56\xaf\x56\x08\xe7\x9b\x0c\x70\xef\x66\x52\x13\x46\xaf\xa9\x81\xc3\x22\xaa\x88\x0f\xb4\x06\xad\xbc\x9b\xcd\x63\xa0\xef\x0a\xa6\x16\x74\xcd\x09\x01\x7d\x2e\xfd\x63\x05\x6c\x11\x58\x31\x4d\xae\xe1\x2a\x78\xba\x02\x21\x57\x9f\x68\x48\x1a\x7f\xb5\x66\xc0\xdb\x38\x17\xf2\xae\x4f\x40\xb7\x77\x01\x17\x5c\x58\xba\xcc\x2b\xdb\x9a\x60\x8a\x7c\x8a\x93\x0b\x0e\x5e\xfa\x62\x68\xe9\x1c\x6c\x9c\x5d\x6d\xea\x49\x4a\x7b\xe4\xd5\x89\x63\x88\xa1\x57\x02\x03\xb6\x8d\xc7\x27\x8e\x8c\x7a\xf2\xaf\xbe\xa5\xcf\x15\xa5\x3c\xdd\xc6\x04\xf6\xcb\xd9\x8c\x93\x57\x24\xe1\xc1\x29\xd3\xe1\x45\x01\x15\x01\xc4\x0a\xfd\xce\xe6\x77\x13\xe6\xb4\x10\x0e\xfa\x17\xa2\x84\xd9\x12\xed\xf4\x3d\xe3\xbd\x33\x33\x11\x26\x0d\xfd\xb3\xd3\x6e\x14\xbe\x85\xe0\xcd\x0b\xc7\x4f\xf6\x7c\x7a\x1b\xfc\xed\xee\xb8\x1a\x7a\xdf\x2c\xf3\x06\xc7\x9f\x26\xdc\x18\x91\xad\xa1\xc0\x03\x00\xa6\x12\x7f\x65\xc2\xfa\xff\xaa\x09\xff\xf1\xfa\xc3\x86\x43\x93\xea\x2f\x57\x68\x8b\x46\xcd\x1a\xbd\xb0\xaa\x89\xd8\xa5\x1f\x14\xbe\x69\x5f\xfa\x6c\x39\x83\x19\x29\xe2\x84\x7f\xd5\xe2\x91\x69\x61\xb7\x3e\x9d\xaa\x48\xe9\xb4\x1a\xee\x19\x25\xe1\x82\x06\x46\x29\x15\x62\xe5\x3b\xaa\xa5\x16\xeb\x19\xce\x58\x10\xbf\x2f\x2d\x77\x9e\xa3\xeb\xbe\x70\xe6\xcd\x4b\xe5\x39\x4a\xe0\xe3\x06\x04\xb5\xa6\xcc\xc8\xe8\x0a\x28\xd9\x8f\x78\xcd\x91\x77\xec\xe2\x09\xe1\x42\x09\x22\xb9\x45\xc3\x48\x28\xca\xc3\xce\xcd\x73\x10\xc1\x2b\x65\x35\x25\x77\x45\xc8\x7a\x0c\x3e\x9e\xcd\x51\x6e\xa9\xad\xc3\x9d\xa8\xf0\xad\x9b\xe4\x17\xc4\x82\x19\xdc\x79\xa5\xd7\x07\x7a\x8a\x5c\x19\x50\x34\xb4\xec\x62\xc1\xf6\x00\x47\x95\x5a\xee\x83\xe3\x05\x6c\x4b\x9a\x9e\xbe\x36\x57\x87\x8e\x9a\x16\x4c\x33\x0e\x1a\xa1\x96\x82\xef\xdd\xdc\x5b\xcd\xf7\x31\x8f\x54\x70\xa5\x37\x6d\x60\x76\xa4\xcf\xa4\x35\x29\x14\x74\x6b\x38\x4b\xdb\x2d\x04\x5b\x33\xbc\xaf\x8d\x27\xfd\xb3\xbf\x3f\xe3\x7e\x37\xf2\x5c\x08\xf6\x58\x9c\xf7\x97\x9a\x72\xcd\xaf\x13\x94\xa6\x72\xda\x2d\x92\xd0\x23\x66\x2b\xb5\xb5\xad\xf0\xe3\x0e\xf5\xb3\xe0\xc4\xd2\xa0\xad\x2a\x45\xc8\xcc\xa2\x92\xd1\x96\x79\xa4\xe6\x32\xa1\x78\xa2\x95\x79\xb2\xd0\x29\xd9\xf4\x42\x9e\xc6\x93\xd2\xff\x61\x39\xfc\x55\x40\x69\x3f\x07\x00\xa0\x39\xff\xa3\xf7\x21\x3e\x10\x85\xf9\xe3\x47\x60\x12\xeb\x6f\x45\x69\x8b\x5a\xcc\x6e\x4a\xf2\x84\xd8\xdf\x99\x76\x1f\x84\x38\x1b\x57\x66\x73\x73\x76\x99\xa9\x3a\x47\xc1\xb7\xc2\x62\x75\x74\x85\x6d\xdd\x69\xc1\x4b\x3b\x96\x88\x9a\x9b\xfa\x42\xa5\x28\x42\xfe\x33\xa4\x13\xe3\x92\x40\x57\x99\x06\x5f\x4a\x54\xa6\x5c\xa8\xc3\x47\x2f\x6f\xa7\x0e\xd5\x47\x88\x74\x79\xe3\x13\xe7\xc9\x2c\xd8\x95\x95\xfa\x69\x01\x40\x4b\xa5\x33\xda\x89\x5b\xf7\x51\x8e\x5d\x7c\xfe\x70\x24\x74\x7e\xdb\x42\x93\xc1\xe4\xdf\xbd\xe7\xed\xbb\x83\xcf\xa5\xcf\x1d\x6d\x82\x47\x5d\xad\xbb\x24\x98\x18\x6e\xe6\xbd\xc9\xac\xcb\x1e\xb1\x18\xb3\xcf\x8c\xa5\xcd\xbf\xee\xeb\x31\xfe\xb3\xfb\x18\x15\x83\x1c\x12\x00\x00\xcb\xbf\x93\x2c\x07\xa7\x04\xf0\xbf\xfd\x7f\x4f\xb9\xc2\xff\x67\x22\xdf\x53\xf6\xcf\x27\xbf\x67\x0c\xe5\x1f\x4e\xdd\xfe\x67\x99\xf8\xe7\x90\xdf\x6f\xcc\x91\x1f\x42\x5a\x71\xfd\x6d\x54\xf9\x39\xc9\xf7\x5a\xd0\xff\x21\x49\x07\xff\xbf\xb1\x8a\x3f\xa7\xf9\x7e\xe8\x3f\xf6\xe2\x24\xf8\xb7\x65\x76\xd6\x86\x87\xf7\xb7\x10\x3c\x00\x0f\x50\xc7\x01\x00\x30\xa1\xdf\xbe\xfe\x2b\x00\x00\xff\xff\xb2\x21\x5c\xb0\x2a\x0d\x00\x00") func syntaxStdlibArraizBytes() ([]byte, error) { return bindataRead( @@ -105,8 +105,8 @@ func syntaxStdlibArraiz() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "syntax/stdlib.arraiz", size: 3361, mode: os.FileMode(0644), modTime: time.Unix(1, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xcc, 0x46, 0xf, 0x83, 0x56, 0xfb, 0x23, 0xb0, 0x5e, 0x6d, 0x49, 0xb0, 0xd8, 0x18, 0x9c, 0x7e, 0xe7, 0x41, 0xe5, 0xc8, 0x99, 0x35, 0x90, 0x24, 0xf9, 0xb7, 0xf, 0x8e, 0xff, 0xe, 0x6c, 0x31}} + info := bindataFileInfo{name: "syntax/stdlib.arraiz", size: 3370, mode: os.FileMode(0644), modTime: time.Unix(1, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9f, 0x2f, 0xc6, 0x2, 0x47, 0x55, 0x82, 0x17, 0x25, 0xb1, 0xde, 0x53, 0x41, 0xd5, 0x8e, 0x9d, 0x7f, 0xba, 0x81, 0xd2, 0x91, 0x3b, 0x5e, 0xd0, 0xc8, 0x7, 0xaf, 0x27, 0x49, 0x77, 0x7a, 0x7f}} return a, nil } diff --git a/syntax/bundle.go b/syntax/bundle.go index 1ec41d20..56b90cd2 100644 --- a/syntax/bundle.go +++ b/syntax/bundle.go @@ -13,6 +13,8 @@ import ( "runtime" "strings" + "github.com/spf13/afero" + "github.com/anz-bank/pkg/mod" "github.com/arr-ai/arrai/pkg/ctxfs" "github.com/arr-ai/arrai/rel" @@ -98,7 +100,7 @@ func GetMainBundleSource(ctx context.Context) (context.Context, []byte, string) ctx = withBundledConfig(ctx) mainFile := bundleToValidPath(ctx, fromBundleConfig(ctx).mainFile) fs := ctxfs.SourceFsFrom(ctx) - buf, err := ctxfs.ReadFile(fs, mainFile) + buf, err := afero.ReadFile(fs, mainFile) if err != nil { panic(fmt.Errorf("not bundled properly, main file not accessible: %s", err)) } @@ -131,7 +133,7 @@ func withBundledConfig(ctx context.Context) context.Context { return ctx } - buf, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), BundleConfig) + buf, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), BundleConfig) if err != nil { // config file not generated panic(err) @@ -200,7 +202,7 @@ func SetupBundle(ctx context.Context, filePath string, source []byte) (_ context return ctx, createConfig(ctx) } - buf, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), filepath.Join(root, ModuleRootSentinel)) + buf, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), filepath.Join(root, ModuleRootSentinel)) if err != nil { return ctx, err } @@ -239,7 +241,7 @@ func addModuleSentinel(ctx context.Context, rootPath string) (err error) { rootPath = filepath.Join(rootPath, ModuleRootSentinel) - buf, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), rootPath) + buf, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), rootPath) if err != nil { return err } @@ -292,7 +294,7 @@ func bundleLocalFile(ctx context.Context, filePath string) (err error) { filePath += arraiExt } - source, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), filePath) + source, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), filePath) if err != nil { return err } @@ -327,7 +329,7 @@ func bundleModule(ctx context.Context, relImportPath string, m *mod.Module) (con relImportPath += arraiExt } - source, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), filepath.Join(m.Dir, relImportPath)) + source, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), filepath.Join(m.Dir, relImportPath)) if err != nil { return ctx, err } diff --git a/syntax/import.go b/syntax/import.go index 675233fa..7a2d6aab 100644 --- a/syntax/import.go +++ b/syntax/import.go @@ -12,6 +12,8 @@ import ( "strings" "sync" + "github.com/spf13/afero" + "github.com/anz-bank/pkg/mod" "github.com/arr-ai/arrai/pkg/ctxfs" "github.com/arr-ai/arrai/pkg/ctxrootcache" @@ -259,7 +261,7 @@ func fileValue(ctx context.Context, decoder rel.Tuple, filename string) (rel.Exp filename += ".arrai" } - bytes, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), filename) + bytes, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), filename) if err != nil { return nil, err } diff --git a/syntax/std_os_nonwasm.go b/syntax/std_os_nonwasm.go index 7ecf1841..683c793a 100644 --- a/syntax/std_os_nonwasm.go +++ b/syntax/std_os_nonwasm.go @@ -10,6 +10,8 @@ import ( "runtime" "strings" + "github.com/spf13/afero" + "github.com/arr-ai/arrai/pkg/arraictx" "github.com/arr-ai/arrai/pkg/ctxfs" @@ -75,7 +77,7 @@ func stdOsFile(ctx context.Context, v rel.Value) (rel.Value, error) { if err != nil { return nil, err } - buf, err := ctxfs.ReadFile(ctxfs.RuntimeFsFrom(ctx), filePath.String()) + buf, err := afero.ReadFile(ctxfs.RuntimeFsFrom(ctx), filePath.String()) if err != nil { //TODO: wrap this in an arrai error message return nil, err diff --git a/syntax/stdlib/flag_test.arrai b/syntax/stdlib/flag_test.arrai index afd8a3d7..26c509c5 100644 --- a/syntax/stdlib/flag_test.arrai +++ b/syntax/stdlib/flag_test.arrai @@ -140,14 +140,14 @@ let alias = flag.parser({'alias': (type: 'string', alias: 'a')}); 'bool': (type: 'bool', alias: 'b'), 'default': (type: 'string', default: 'abc') }); - { + [ 'Options:', '--bool, -b', '--default string (default: abc)', '--num, -n number', 'takes a number', '--str string', - } => //test.assert.true( + ] >> //test.assert.true( //seq.contains(., help) ) # TODO: test overlapping alias when errors can be tested