Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype v2 #197

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2e62846
drop support for multiple destination structs
alexflint Oct 4, 2022
09d28e1
split the parsing logic into ProcessEnvironment, ProcessCommandLine, …
alexflint Oct 4, 2022
22f214d
added test that library does not directly access environment variable…
alexflint Oct 4, 2022
a1e2b67
add a test to check that default values can be ignored if needed
alexflint Oct 4, 2022
4aea783
changed NewParser to take options at the end rather than config at th…
alexflint Oct 4, 2022
5ca19cd
cleaned up the test helpers parse, pparse, and parseWithEnv: now all …
alexflint Oct 4, 2022
5f0c48f
move construction logic out of parse.go into construct.go
alexflint Oct 4, 2022
2775f58
add OverwriteWithOptions, OverwriteWithCommandLine
alexflint Oct 4, 2022
64288c5
add appendToSlice, appendToMap, appendToSliceOrMap
alexflint Oct 4, 2022
b365ec0
add processSingle and make it responsible for checking whether an arg…
alexflint Oct 4, 2022
1cc263f
add processSequence and make it responsible for respecting "overwrite"
alexflint Oct 4, 2022
84b7154
add TestSliceWithEqualsSign
alexflint Oct 4, 2022
0769dd5
add tests for new Process* and OverwriteWith* functions
alexflint Oct 4, 2022
55d9025
rename "accumulatedArgs" -> "accessible"
alexflint Oct 4, 2022
60a0117
update readme for v2 (still has some TODOs)
alexflint Oct 7, 2022
47ff443
drop support for help tag inside arg tag
alexflint Oct 7, 2022
2ffe246
add mdtest command to generate and run tests from a markdown file
alexflint Oct 7, 2022
f2539d7
add go.work -- maybe remove before merge?
alexflint Oct 29, 2022
c046f49
drop go.work and add it to .gitignore
alexflint Oct 29, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
289 changes: 141 additions & 148 deletions README.md

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM=
github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
2 changes: 2 additions & 0 deletions go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
use .
use ./v2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go.work is meant to locally modify your workspace. It shouldn't be committed and shouldn't be part of a release. It's generally recommended to add it to .gitignore.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

11 changes: 11 additions & 0 deletions mdtest/example1.go.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
"github.com/alexflint/go-arg/v2"
{{if contains .Code "fmt."}}"fmt"{{end}}
{{if contains .Code "strings."}}"strings"{{end}}
)

func main() {
{{.Code}}
}
9 changes: 9 additions & 0 deletions mdtest/example2.go.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import (
"github.com/alexflint/go-arg/v2"
{{if contains .Code "fmt."}}"fmt"{{end}}
{{if contains .Code "strings."}}"strings"{{end}}
)

{{.Code}}
179 changes: 179 additions & 0 deletions mdtest/mdtest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// mdtest executes code blocks in markdown and checks that they run as expected
package main

import (
"bytes"
"context"
_ "embed"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"text/template"
"time"

"github.com/alexflint/go-arg/v2"
)

// var pattern = "```go(.*)```\\s*```\\s*\\$(.*)\\n(.*)```"
var pattern = "(?s)```go([^`]*?)```\\s*```([^`]*?)```" //go(.*)```\\s*```\\s*\\$(.*)\\n(.*)```"

var re = regexp.MustCompile(pattern)

var funcs = map[string]any{
"contains": strings.Contains,
}

//go:embed example1.go.tpl
var templateSource1 string

//go:embed example2.go.tpl
var templateSource2 string

var t1 = template.Must(template.New("example1.go").Funcs(funcs).Parse(templateSource1))
var t2 = template.Must(template.New("example2.go").Funcs(funcs).Parse(templateSource2))

type payload struct {
Code string
}

func runCode(ctx context.Context, code []byte, cmd string) ([]byte, error) {
dir, err := os.MkdirTemp("", "")
if err != nil {
return nil, fmt.Errorf("error creating temp dir to build and run code: %w", err)
}

fmt.Println(dir)
fmt.Println(strings.Repeat("-", 80))

srcpath := filepath.Join(dir, "src.go")
binpath := filepath.Join(dir, "example")

// If the code contains a main function then use t2, otherwise use t1
t := t1
if strings.Contains(string(code), "func main") {
t = t2
}

var b bytes.Buffer
err = t.Execute(&b, payload{Code: string(code)})
if err != nil {
return nil, fmt.Errorf("error executing template for source file: %w", err)
}

fmt.Println(b.String())
fmt.Println(strings.Repeat("-", 80))

err = os.WriteFile(srcpath, b.Bytes(), os.ModePerm)
if err != nil {
return nil, fmt.Errorf("error writing temporary source file: %w", err)
}

compiler, err := exec.LookPath("go")
if err != nil {
return nil, fmt.Errorf("could not find path to go compiler: %w", err)
}

buildCmd := exec.CommandContext(ctx, compiler, "build", "-o", binpath, srcpath)
out, err := buildCmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("error building source: %w. Compiler said:\n%s", err, string(out))
}

// replace "./example" with full path to compiled program
var env, args []string
var found bool
for _, part := range strings.Split(cmd, " ") {
if found {
args = append(args, part)
} else if part == "./example" {
found = true
} else {
env = append(env, part)
}
}

runCmd := exec.CommandContext(ctx, binpath, args...)
runCmd.Env = env
output, err := runCmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("error runing example: %w. Program said:\n%s", err, string(output))
}

// Clean up the temp dir
if err := os.RemoveAll(dir); err != nil {
return nil, fmt.Errorf("error deleting temp dir: %w", err)
}

return output, nil
}

func Main() error {
ctx := context.Background()

var args struct {
Input string `arg:"positional,required"`
}
arg.MustParse(&args)

buf, err := os.ReadFile(args.Input)
if err != nil {
return err
}

fmt.Println(strings.Repeat("=", 80))

matches := re.FindAllSubmatchIndex(buf, -1)
for k, match := range matches {
codebegin, codeend := match[2], match[3]
code := buf[codebegin:codeend]

shellbegin, shellend := match[4], match[5]
shell := buf[shellbegin:shellend]

lines := strings.Split(string(shell), "\n")
for i := 0; i < len(lines); i++ {
if strings.HasPrefix(lines[i], "$") && strings.Contains(lines[i], "./example") {
cmd := strings.TrimSpace(strings.TrimPrefix(lines[i], "$"))

var output []string
i++
for i < len(lines) && !strings.HasPrefix(lines[i], "$") {
output = append(output, lines[i])
i++
}

expected := strings.TrimSpace(strings.Join(output, "\n"))

fmt.Println(string(code))
fmt.Println(strings.Repeat("-", 80))
fmt.Println(string(cmd))
fmt.Println(strings.Repeat("-", 80))
fmt.Println(string(expected))
fmt.Println(strings.Repeat("-", 80))

ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()

actual, err := runCode(ctx, code, cmd)
if err != nil {
return fmt.Errorf("error running example %d: %w\nCode was:\n%s", k, err, string(code))
}

fmt.Println(string(actual))
fmt.Println(strings.Repeat("=", 80))
}
}
}
fmt.Printf("found %d matches\n", len(matches))
return nil
}

func main() {
if err := Main(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
Loading