Files
judge/runner/expander.go
Mikhail Kornilovich 7f9f6a0a6e
All checks were successful
build-dsl-smoke / Build judge (push) Successful in 13s
build-dsl-smoke / debug / clang / linux (push) Successful in 6s
build-dsl-smoke / debug / gcc / linux (push) Successful in 8s
build-dsl-smoke / release / clang / linux (push) Successful in 9s
build-dsl-smoke / release / gcc / linux (push) Successful in 7s
build-dsl-smoke / sanitized / clang / linux (push) Successful in 8s
build-dsl-smoke / sanitized / gcc / linux (push) Successful in 7s
build-dsl-smoke / debug / clang / windows (push) Successful in 16s
build-dsl-smoke / debug-valgrind / gcc / linux (push) Successful in 14s
build-dsl-smoke / debug / msvc / windows (push) Successful in 18s
build-dsl-smoke / release / clang / windows (push) Successful in 17s
build-dsl-smoke / release / msvc / windows (push) Successful in 17s
build-dsl-smoke / SUMMARY (push) Successful in 5s
feat: pattern support args and multiple variants; add zed extension for highlight
2026-04-11 14:37:43 +03:00

212 lines
5.5 KiB
Go

package runner
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/Mond1c/judge/dsl"
)
func expandPattern(pattern *dsl.Pattern) ([]*dsl.Test, error) {
if pattern.IsDirMode() {
return expandDirPattern(pattern)
}
return expandGlobPattern(pattern)
}
type patternCase struct {
name string
inputPath string
outputPath string
dir string
}
func expandGlobPattern(pattern *dsl.Pattern) ([]*dsl.Test, error) {
inputIsGlob := strings.Contains(pattern.InputGlob, "*")
outputIsGlob := strings.Contains(pattern.OutputGlob, "*")
if pattern.InputGlob == "" && pattern.OutputGlob == "" {
return nil, fmt.Errorf("pattern needs at least one of input/output/dirs")
}
if !inputIsGlob && !outputIsGlob {
return nil, fmt.Errorf("pattern needs at least one glob field (input or output must contain *)")
}
var cases []patternCase
switch {
case inputIsGlob && outputIsGlob:
inputFiles, err := filepath.Glob(pattern.InputGlob)
if err != nil {
return nil, fmt.Errorf("invalid input glob %q: %w", pattern.InputGlob, err)
}
if len(inputFiles) == 0 {
return nil, fmt.Errorf("no files matched input glob %q", pattern.InputGlob)
}
inputPrefix, inputSuffix := splitGlob(pattern.InputGlob)
outputPrefix, outputSuffix := splitGlob(pattern.OutputGlob)
for _, inputPath := range inputFiles {
wildcard := extractWildcard(inputPath, inputPrefix, inputSuffix)
outputPath := outputPrefix + wildcard + outputSuffix
cases = append(cases, patternCase{
name: wildcard,
inputPath: inputPath,
outputPath: outputPath,
})
}
case inputIsGlob && !outputIsGlob:
inputFiles, err := filepath.Glob(pattern.InputGlob)
if err != nil {
return nil, fmt.Errorf("invalid input glob %q: %w", pattern.InputGlob, err)
}
if len(inputFiles) == 0 {
return nil, fmt.Errorf("no files matched input glob %q", pattern.InputGlob)
}
inputPrefix, inputSuffix := splitGlob(pattern.InputGlob)
for _, inputPath := range inputFiles {
wildcard := extractWildcard(inputPath, inputPrefix, inputSuffix)
cases = append(cases, patternCase{
name: wildcard,
inputPath: inputPath,
outputPath: pattern.OutputGlob,
})
}
case !inputIsGlob && outputIsGlob:
outputFiles, err := filepath.Glob(pattern.OutputGlob)
if err != nil {
return nil, fmt.Errorf("invalid output glob %q: %w", pattern.OutputGlob, err)
}
if len(outputFiles) == 0 {
return nil, fmt.Errorf("no files matched output glob %q", pattern.OutputGlob)
}
outputPrefix, outputSuffix := splitGlob(pattern.OutputGlob)
for _, outputPath := range outputFiles {
wildcard := extractWildcard(outputPath, outputPrefix, outputSuffix)
cases = append(cases, patternCase{
name: wildcard,
inputPath: pattern.InputGlob,
outputPath: outputPath,
})
}
}
return buildTests(cases, pattern.Args)
}
func expandDirPattern(pattern *dsl.Pattern) ([]*dsl.Test, error) {
dirs, err := filepath.Glob(pattern.DirsGlob)
if err != nil {
return nil, fmt.Errorf("invalid dirs glob %q: %w", pattern.DirsGlob, err)
}
if len(dirs) == 0 {
return nil, fmt.Errorf("no directories matched %q", pattern.DirsGlob)
}
var cases []patternCase
for _, dir := range dirs {
info, err := os.Stat(dir)
if err != nil || !info.IsDir() {
continue
}
cases = append(cases, patternCase{
name: filepath.Base(dir),
inputPath: filepath.Join(dir, pattern.InputFile),
outputPath: filepath.Join(dir, pattern.OutputFile),
dir: dir,
})
}
return buildTests(cases, pattern.Args)
}
func buildTests(cases []patternCase, argTemplate []string) ([]*dsl.Test, error) {
useInputAsFile := argsContain(argTemplate, "{input_path}")
useOutputAsFile := argsContain(argTemplate, "{output_path}")
var tests []*dsl.Test
for _, c := range cases {
inputContent, err := os.ReadFile(c.inputPath)
if err != nil {
return nil, fmt.Errorf("read input %q: %w", c.inputPath, err)
}
outputContent, err := os.ReadFile(c.outputPath)
if err != nil {
return nil, fmt.Errorf("read output %q: %w", c.outputPath, err)
}
t := &dsl.Test{
Name: fmt.Sprintf("pattern:%s", c.name),
Env: map[string]string{},
InFiles: map[string]string{},
OutFiles: map[string]string{},
Stderr: dsl.NoMatcher{},
}
inputName := filepath.Base(c.inputPath)
outputName := filepath.Base(c.outputPath)
if useInputAsFile {
t.InFiles[inputName] = string(inputContent)
} else {
s := string(inputContent)
t.Stdin = &s
}
if useOutputAsFile {
t.OutFiles[outputName] = string(outputContent)
t.Stdout = dsl.NoMatcher{}
} else {
t.Stdout = dsl.ExactMatcher{Value: string(outputContent)}
}
if len(argTemplate) > 0 {
t.Args = substituteArgs(argTemplate, map[string]string{
"{input_path}": inputName,
"{output_path}": outputName,
"{name}": c.name,
"{dir}": c.dir,
})
}
tests = append(tests, t)
}
return tests, nil
}
func argsContain(args []string, placeholder string) bool {
for _, a := range args {
if strings.Contains(a, placeholder) {
return true
}
}
return false
}
func substituteArgs(template []string, vars map[string]string) []string {
out := make([]string, len(template))
for i, a := range template {
for k, v := range vars {
a = strings.ReplaceAll(a, k, v)
}
out[i] = a
}
return out
}
func splitGlob(pattern string) (prefix, suffix string) {
before, after, found := strings.Cut(pattern, "*")
if !found {
return pattern, ""
}
return before, after
}
func extractWildcard(path, prefix, suffix string) string {
s := strings.TrimPrefix(path, prefix)
s = strings.TrimSuffix(s, suffix)
return s
}