Skip to content

Commit

Permalink
Add Fprint (#10)
Browse files Browse the repository at this point in the history
* Printing

* Added string methods to AST nodes

Two new string methods have been added to the LiteralFileName and Ident nodes in the Abstract Syntax Tree (AST). These methods return the literal identifier of each node. Corresponding tests have also been added to ensure these new methods work as expected.

* Added printing functionality for recipes and rules

The print.go file has been updated to include the ability to print 'Recipe' and 'Rule' nodes. This includes writing the token, text, targets, prerequisites, and any associated recipes. Corresponding tests have also been added in print_test.go to ensure that these new functionalities work as expected.

* Added error handling tests to print_test.go

Enhanced the test suite in print_test.go by adding a DescribeTable for surfacing errors. This includes multiple entries that simulate different error positions within the writing process of targets, prerequisites, and recipes. The new tests ensure that our package correctly handles and reports these errors.

* Updated README and codecov settings

Added a new section in the README file explaining how to use `make.Fprint` for writing ast nodes. Also, adjusted the default target and threshold values under 'patch' in the codecov configuration.
  • Loading branch information
UnstoppableMango authored Jan 19, 2025
1 parent e2fa3ed commit c8f7490
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 1 deletion.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,13 @@ if err := s.Err(); err != nil {
fmt.Println(err)
}
```

### Writing

Use `make.Fprint` to write ast nodes.

```go
var file *ast.File

err := make.Fprint(os.Stdout, file)
```
12 changes: 12 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,21 @@ type LiteralFileName struct {

func (*LiteralFileName) fileNameNode() {}

// Pos implements Node
func (l *LiteralFileName) Pos() token.Pos {
return l.Name.Pos()
}

// End implements Node
func (l *LiteralFileName) End() token.Pos {
return l.Name.End()
}

// String returns the literal identifier
func (l *LiteralFileName) String() string {
return l.Name.String()
}

// A Recipe represents a line of text to be passed to the shell to build a Target.
type Recipe struct {
Tok token.Token // TAB or SEMI
Expand Down Expand Up @@ -166,3 +173,8 @@ func (i *Ident) Pos() token.Pos {
func (i *Ident) End() token.Pos {
return token.Pos(int(i.NamePos) + len(i.Name))
}

// String returns the literal identifier.
func (i *Ident) String() string {
return i.Name
}
16 changes: 16 additions & 0 deletions ast/ast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ var _ = Describe("Ast", func() {

Expect(c.End()).To(Equal(token.Pos(423)))
})

It("should stringify", func() {
c := &ast.LiteralFileName{Name: &ast.Ident{
Name: "foo",
}}

Expect(c.String()).To(Equal("foo"))
})
})

Describe("Recipe", func() {
Expand Down Expand Up @@ -193,5 +201,13 @@ var _ = Describe("Ast", func() {

Expect(c.End()).To(Equal(token.Pos(423)))
})

It("should stringify", func() {
c := &ast.Ident{
Name: "baz",
}

Expect(c.String()).To(Equal("baz"))
})
})
})
3 changes: 2 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ coverage:
default:
target: 75%
threshold: 3%
patch:
patch:
default:
target: 60%
threshold: 10%
58 changes: 58 additions & 0 deletions print.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package make

import (
"fmt"
"io"

"github.com/unmango/go-make/ast"
)

func Fprint(w io.Writer, node ast.Node) (err error) {
switch node := node.(type) {
case *ast.LiteralFileName:
_, err = io.WriteString(w, node.String())
case *ast.TargetList:
for _, t := range node.List {
if err = Fprint(w, t); err != nil {
return
}
}
_, err = io.WriteString(w, ":")
case *ast.PreReqList:
for _, t := range node.List {
if err = Fprint(w, t); err != nil {
return
}
}
case *ast.Recipe:
if _, err = fmt.Fprint(w, node.Tok); err != nil {
return err
}
if _, err = io.WriteString(w, node.Text); err != nil {
return err
}
if _, err = fmt.Fprintln(w); err != nil {
return err
}
case *ast.Rule:
if err = Fprint(w, node.Targets); err != nil {
return err
}
if _, err = fmt.Fprint(w, " "); err != nil {
return err
}
if err = Fprint(w, node.PreReqs); err != nil {
return err
}
if _, err = fmt.Fprintln(w); err != nil {
return err
}
for _, r := range node.Recipes {
if err = Fprint(w, r); err != nil {
return err
}
}
}

return
}
130 changes: 130 additions & 0 deletions print_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package make_test

import (
"bytes"
"fmt"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/unmango/go-make"
"github.com/unmango/go-make/ast"
"github.com/unmango/go-make/internal/testing"
"github.com/unmango/go-make/token"
)

var _ = Describe("Print", func() {
Describe("Fprint", func() {
It("should print a literal file name", func() {
buf := &bytes.Buffer{}
l := &ast.LiteralFileName{Name: &ast.Ident{
Name: "target",
}}

err := make.Fprint(buf, l)

Expect(err).NotTo(HaveOccurred())
Expect(buf.String()).To(Equal("target"))
})

It("should print a target list", func() {
buf := &bytes.Buffer{}
t := &ast.TargetList{List: []ast.FileName{
&ast.LiteralFileName{Name: &ast.Ident{
Name: "target",
}},
}}

err := make.Fprint(buf, t)

Expect(err).NotTo(HaveOccurred())
Expect(buf.String()).To(Equal("target:"))
})

It("should print a prereq list", func() {
buf := &bytes.Buffer{}
t := &ast.PreReqList{List: []ast.FileName{
&ast.LiteralFileName{Name: &ast.Ident{
Name: "prereq",
}},
}}

err := make.Fprint(buf, t)

Expect(err).NotTo(HaveOccurred())
Expect(buf.String()).To(Equal("prereq"))
})

It("should print a recipe", func() {
buf := &bytes.Buffer{}
r := &ast.Recipe{
Tok: token.TAB,
Text: "recipe",
}

err := make.Fprint(buf, r)

Expect(err).NotTo(HaveOccurred())
Expect(buf.String()).To(Equal("\trecipe\n"))
})

It("should print a rule", func() {
buf := &bytes.Buffer{}
r := &ast.Rule{
Targets: &ast.TargetList{List: []ast.FileName{
&ast.LiteralFileName{Name: &ast.Ident{
Name: "target",
}},
}},
PreReqs: &ast.PreReqList{List: []ast.FileName{
&ast.LiteralFileName{Name: &ast.Ident{
Name: "prereq",
}},
}},
Recipes: []*ast.Recipe{{
Tok: token.TAB,
Text: "recipe",
}},
}

err := make.Fprint(buf, r)

Expect(err).NotTo(HaveOccurred())
Expect(buf.String()).To(Equal("target: prereq\n\trecipe\n"))
})

DescribeTable("should surface errors",
Entry("write target", 1),
Entry("write colon", 2),
Entry("write space", 3),
Entry("write prereq", 4),
Entry("write newline", 5),
Entry("write tab", 6),
Entry("write recipe", 7),
Entry("write newline", 8),
func(position int) {
w := testing.NewErrAfterWriter(position)
r := &ast.Rule{
Targets: &ast.TargetList{List: []ast.FileName{
&ast.LiteralFileName{Name: &ast.Ident{
Name: "target",
}},
}},
PreReqs: &ast.PreReqList{List: []ast.FileName{
&ast.LiteralFileName{Name: &ast.Ident{
Name: "prereq",
}},
}},
Recipes: []*ast.Recipe{{
Tok: token.TAB,
Text: "recipe",
}},
}

err := make.Fprint(w, r)

Expect(err).To(MatchError(fmt.Sprintf("write err: %d", position)))
},
)
})
})

0 comments on commit c8f7490

Please sign in to comment.