Skip to content

Commit

Permalink
feat: Add the stdlib_diff tool to compare gno and go standard librari…
Browse files Browse the repository at this point in the history
…es (gnolang#2869)

continuing the work started on gnolang#1425 
thanks FloRichardAloeCorp for the great job on this issue 👍 
closes gnolang#1310 
<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [ ] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>

---------

Co-authored-by: hthieu1110 <[email protected]>
Co-authored-by: Florian Richard <[email protected]>
Co-authored-by: Morgan Bazalgette <[email protected]>
  • Loading branch information
4 people authored and omarsy committed Dec 18, 2024
1 parent c1b5599 commit 77c89e1
Show file tree
Hide file tree
Showing 15 changed files with 1,061 additions and 2 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: "cd misc/gendocs && make install gen"
- run: echo "GOROOT=$(go env GOROOT)" >> $GITHUB_ENV
- run: echo $GOROOT
- run: "cd misc/stdlib_diff && make install gen"
- run: "cd misc/gendocs && make gen"
- run: |
mv misc/gendocs/godoc pages_output
mv misc/stdlib_diff/stdlib_diff pages_ouput/stdlib_diff
- uses: actions/configure-pages@v5
id: pages
- uses: actions/upload-pages-artifact@v3
with:
path: ./misc/gendocs/godoc
path: ./pages_output

deploy:
if: ${{ github.repository == 'gnolang/gno' }} # Alternatively, validate based on provided tokens and permissions.
Expand Down
22 changes: 22 additions & 0 deletions examples/gno.land/r/demo/users/z_13_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

// SEND: 200000000ugnot

import (
"strconv"

"gno.land/r/demo/users"
)

func main() {
{
// Verify pre-registered test1 user
names := users.ListUsersByPrefix("test1", 1)
println("# names: " + strconv.Itoa(len(names)))
println("name: " + names[0])
}
}

// Output:
// # names: 1
// name: test1
1 change: 1 addition & 0 deletions misc/stdlib_diff/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stdlib_diff/
7 changes: 7 additions & 0 deletions misc/stdlib_diff/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
all: clean gen

gen:
go run . -src $(GOROOT)/src -dst ../../gnovm/stdlibs -out ./stdlib_diff

clean:
rm -rf stdlib_diff
30 changes: 30 additions & 0 deletions misc/stdlib_diff/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# stdlibs_diff

stdlibs_diff is a tool that generates an html report indicating differences between gno standard libraries and go standrad libraries

## Usage

Compare the `go` standard libraries the `gno` standard libraries

```shell
./stdlibs_diff -src <path to go standard libraries> -dst <path to gno standard libraries> -out <output directory>
```

Compare the `gno` standard libraries the `go` standard libraries

```shell
./stdlibs_diff -src <path to gno standard libraries> -dst <path to go standard libraries> -out <output directory>
```


## Parameters

| Flag | Description | Default value |
| ---------- | ------------------------------------------------------------------ | ------------- |
| src | Directory containing packages that will be compared to destination | None |
| dst | Directory containing packages; used to compare src packages | None |
| out | Directory where the report will be created | None |

## Tips

An index.html is generated at the root of the report location. Utilize it to navigate easily through the report.
25 changes: 25 additions & 0 deletions misc/stdlib_diff/diffstatus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

type diffStatus uint

const (
missingInSrc diffStatus = iota
missingInDst
hasDiff
noDiff
)

func (status diffStatus) String() string {
switch status {
case missingInSrc:
return "missing in src"
case missingInDst:
return "missing in dst"
case hasDiff:
return "files differ"
case noDiff:
return "files are equal"
default:
return "Unknown"
}
}
196 changes: 196 additions & 0 deletions misc/stdlib_diff/filediff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package main

import (
"fmt"
"os"
"strings"

"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
)

// DiffChecker is a struct for comparing differences between two files.
type DiffChecker struct {
Src string // Name of the source file.
Dst string // Name of the destination file.
srcContent string // Content of the source file.
dstContent string // Content of the destination file.
srcLines []string // Lines of the source file.
dstLines []string // Lines of the destination file.
}

// LineDifferrence represents a difference in a line during file comparison.
type LineDifferrence struct {
SrcLine string // The line on Src.
DestLine string // The line on Src.
SrcOperation operation // The operation performed on the line (e.g., "add", "delete", "equal").
DestOperation operation
SrcNumber int
DestNumber int
}
type Diff struct {
Diffs []LineDifferrence
MissingSrc bool
MissingDst bool
}

// NewDiffChecker creates a new DiffChecker instance for comparing differences between
// the specified source and destination files. It initializes the source and
// destination file lines .
func NewDiffChecker(srcPath, dstPath string) (*DiffChecker, error) {
src, err := getFileContent(srcPath)
if err != nil {
return nil, fmt.Errorf("can't read src file: %w", err)
}

dst, err := getFileContent(dstPath)
if err != nil {
return nil, fmt.Errorf("can't read dst file: %w", err)
}

return &DiffChecker{
srcContent: src,
dstContent: dst,
srcLines: strings.Split(src, "\n"),
dstLines: strings.Split(dst, "\n"),
Src: srcPath,
Dst: dstPath,
}, nil
}

// Differences returns the differences in lines between the source and
// destination files using the configured diff algorithm.
func (f *DiffChecker) Differences() *Diff {
var (
srcIndex, dstIndex int
insertCount, deleteCount int
diff []LineDifferrence
)

if len(f.dstContent) == 0 {
return f.destEmpty()
}

if len(f.srcContent) == 0 {
return f.srcEmpty()
}

/* printUntil prints all the lines than do not appear on the computed edits from gotextdiff
so we need to add them manually looping always from the current value of
srcIndex until the line before the start of the hunk computed diff, hunk.FromLine-1
We need to print all the lines before each hunk and then ensure the end of the file is printed too
*/
printUntil := func(until int) {
for i := srcIndex; i < until; i++ {
diff = append(diff, LineDifferrence{
SrcLine: f.srcLines[srcIndex],
DestLine: f.srcLines[srcIndex],
DestOperation: equal,
SrcOperation: equal,
SrcNumber: srcIndex + 1,
DestNumber: dstIndex + 1,
})

srcIndex++
dstIndex++
}
}

edits := myers.ComputeEdits(span.URIFromPath(f.Src), f.srcContent, f.dstContent)
unified := gotextdiff.ToUnified(f.Src, f.Dst, f.srcContent, edits)
for _, hunk := range unified.Hunks {
printUntil(hunk.FromLine - 1)

currentLine := LineDifferrence{}
for _, line := range hunk.Lines {
switch line.Kind {
case gotextdiff.Insert:
if currentLine.DestLine != "" {
diff = append(diff, currentLine)
currentLine = LineDifferrence{}
}

insertCount++
dstIndex++

currentLine.DestLine = line.Content
currentLine.DestOperation = insert
currentLine.DestNumber = dstIndex

case gotextdiff.Equal:
if currentLine.DestLine != "" || currentLine.SrcLine != "" {
diff = append(diff, currentLine)
currentLine = LineDifferrence{}
}

srcIndex++
dstIndex++

currentLine = LineDifferrence{
SrcLine: line.Content,
DestLine: line.Content,
DestOperation: equal,
SrcOperation: equal,
SrcNumber: srcIndex,
DestNumber: dstIndex,
}

case gotextdiff.Delete:
if currentLine.SrcLine != "" {
diff = append(diff, currentLine)
currentLine = LineDifferrence{}
}
srcIndex++
deleteCount++
currentLine.SrcLine = line.Content
currentLine.SrcOperation = delete
currentLine.SrcNumber = srcIndex
}
}
diff = append(diff, currentLine)
}

printUntil(len(f.srcLines))

return &Diff{
Diffs: diff,
}
}

func (f *DiffChecker) destEmpty() *Diff {
diffs := []LineDifferrence{}
for index, line := range f.srcLines {
diffs = append(diffs, LineDifferrence{SrcLine: line, SrcOperation: delete, SrcNumber: index + 1})
}

return &Diff{
Diffs: diffs,
MissingDst: true,
}
}

func (f *DiffChecker) srcEmpty() *Diff {
diffs := []LineDifferrence{}
for index, line := range f.dstLines {
diffs = append(diffs, LineDifferrence{DestLine: line, DestOperation: insert, DestNumber: index + 1})
}

return &Diff{
Diffs: diffs,
MissingSrc: true,
}
}

// getFileContent reads and returns the lines of a file given its path.
func getFileContent(p string) (string, error) {
data, err := os.ReadFile(p)
if err != nil {
if os.IsNotExist(err) {
return "", nil
}
return "", err
}
return strings.ReplaceAll(string(data), "\t", " "), nil
}
5 changes: 5 additions & 0 deletions misc/stdlib_diff/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/gnolang/gno/misc/stdlib_diff

go 1.21.0

require github.com/hexops/gotextdiff v1.0.3
2 changes: 2 additions & 0 deletions misc/stdlib_diff/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions misc/stdlib_diff/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"flag"
"log"
)

func main() {
var srcPath string
var dstPath string
var outDirectory string

flag.StringVar(&srcPath, "src", "", "Directory containing packages that will be compared to destination")
flag.StringVar(&dstPath, "dst", "", "Directory containing packages; used to compare src packages")
flag.StringVar(&outDirectory, "out", "", "Directory where the report will be created")
flag.Parse()

reportBuilder, err := NewReportBuilder(srcPath, dstPath, outDirectory)
if err != nil {
log.Fatal("can't build report builder: ", err.Error())
}

log.Println("Building report...")
if err := reportBuilder.Build(); err != nil {
log.Fatalln("can't build report: ", err.Error())
}
log.Println("Report generation done!")
}
28 changes: 28 additions & 0 deletions misc/stdlib_diff/operation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

// operation is an enumeration type representing different types of operations. Used in diff algorithm
// to indicates differences between files.
type operation uint

const (
// insert represents an insertion operation.
insert operation = iota + 1
// delete represents a deletion operation.
delete
// equal represents an equal operation.
equal
)

// String returns a string representation of the operation.
func (op operation) String() string {
switch op {
case insert:
return "INS"
case delete:
return "DEL"
case equal:
return "EQ"
default:
return "UNKNOWN"
}
}
Loading

0 comments on commit 77c89e1

Please sign in to comment.