Skip to content

Commit 74e8195

Browse files
Merge master
2 parents aeee85c + bf9776c commit 74e8195

File tree

12 files changed

+312
-283
lines changed

12 files changed

+312
-283
lines changed

.github/workflows/test.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# This workflow will build a golang project
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
3+
4+
name: Go
5+
6+
on:
7+
push:
8+
pull_request:
9+
10+
jobs:
11+
build:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Set up Go
17+
uses: actions/setup-go@v4
18+
with:
19+
go-version: "1.22"
20+
21+
- name: Test
22+
run: go test -v ./...
23+
24+
- name: Check Generated Schema
25+
run: |
26+
go generate ./...
27+
git diff --exit-code

generator/cmd/generator/main.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,26 @@ import (
55
"io"
66
"log"
77
"os"
8+
"path/filepath"
89

910
"github.com/MetalBlueberry/go-plotly/generator"
1011
)
1112

1213
type Creator struct{}
1314

1415
func (c Creator) Create(name string) (io.WriteCloser, error) {
15-
return os.Create(name)
16+
abs, err := filepath.Abs(name)
17+
if err != nil {
18+
return nil, err
19+
}
20+
21+
return os.Create(abs)
1622
}
1723

24+
//go:generate go run main.go --clean --schema ../../schema.json --output-directory ../../../graph_objects
25+
1826
func main() {
27+
clean := flag.Bool("clean", false, "clean the output directory first. Mandatory on CI")
1928
schema := flag.String("schema", "schema.json", "plotly schema")
2029
outputDirectory := flag.String("output-directory", "gen/", "output directory, must exist before generation")
2130

@@ -34,13 +43,26 @@ func main() {
3443
r, err := generator.NewRenderer(Creator{}, root)
3544
if err != nil {
3645
log.Fatalf("unable to create a new renderer, %s", err)
37-
panic(err)
3846
}
3947

4048
output := *outputDirectory
4149

42-
if err = os.MkdirAll(output, 0755); err != nil {
43-
log.Fatalf("Error creating directory, %s", err)
50+
if *clean {
51+
err = os.RemoveAll(output)
52+
if err != nil {
53+
log.Fatalf("Failed to clean output directory, %s", err)
54+
}
55+
}
56+
57+
err = os.MkdirAll(output, os.0755)
58+
if err != nil {
59+
log.Fatalf("Failed to create output dir %s, %s", *outputDirectory, err)
60+
61+
}
62+
63+
err = r.CreatePlotly(output)
64+
if err != nil {
65+
log.Fatalf("unable to write plotly, %s", err)
4466
}
4567

4668
err = r.CreateTraces(output)

generator/generator_suite_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package generator_test
33
import (
44
"testing"
55

6-
. "github.com/onsi/ginkgo"
6+
. "github.com/onsi/ginkgo/v2"
77
. "github.com/onsi/gomega"
88
)
99

generator/parser_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"os"
66

77
"github.com/MetalBlueberry/go-plotly/generator"
8-
. "github.com/onsi/ginkgo"
8+
. "github.com/onsi/ginkgo/v2"
99
. "github.com/onsi/gomega"
1010
)
1111

generator/renderer.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,36 @@ func NewRenderer(fs Creator, root *Root) (*Renderer, error) {
4646

4747
var doNotEdit = "// Code generated by go-plotly/generator. DO NOT EDIT."
4848

49+
func (r *Renderer) CreatePlotly(dir string) error {
50+
src := &bytes.Buffer{}
51+
err := r.WritePlotly(src)
52+
if err != nil {
53+
return err
54+
}
55+
56+
fmtsrc, err := format.Source(src.Bytes())
57+
if err != nil {
58+
return fmt.Errorf("cannot format source, %w", err)
59+
}
60+
61+
file, err := r.fs.Create(path.Join(dir, "plotly_gen.go"))
62+
if err != nil {
63+
return fmt.Errorf("Path %s, error %s", dir, err)
64+
}
65+
defer file.Close()
66+
_, err = file.Write(fmtsrc)
67+
if err != nil {
68+
return fmt.Errorf("cannot write source, %w", err)
69+
}
70+
71+
return nil
72+
}
73+
74+
// WritePlotly writes the base plotly file
75+
func (r *Renderer) WritePlotly(w io.Writer) error {
76+
return r.tmpl.ExecuteTemplate(w, "plotly.tmpl", w)
77+
}
78+
4979
// CreateTrace creates a file with the content of a trace by name
5080
func (r *Renderer) CreateTrace(dir string, name string) error {
5181
src := &bytes.Buffer{}
@@ -204,7 +234,8 @@ func (r *Renderer) WriteLayout(w io.Writer) error {
204234
}
205235
traceFile.MainType.Fields = append(traceFile.MainType.Fields, fields...)
206236

207-
for name, trace := range r.root.Schema.Traces {
237+
for _, name := range sortKeys(r.root.Schema.Traces) {
238+
trace := r.root.Schema.Traces[name]
208239
fields, err := traceFile.parseAttributes(xstrings.ToCamelCase(name), "Layout", trace.LayoutAttributes.Names)
209240
if err != nil {
210241
return fmt.Errorf("cannot parse attributes, %w", err)

generator/renderer_test.go

Lines changed: 78 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import (
77
_ "embed"
88

99
"github.com/golang/mock/gomock"
10-
. "github.com/onsi/ginkgo"
10+
. "github.com/onsi/ginkgo/v2"
1111
. "github.com/onsi/gomega"
1212

13+
// . "github.com/onsi/gomega/format"
14+
1315
"github.com/MetalBlueberry/go-plotly/generator"
1416
"github.com/MetalBlueberry/go-plotly/generator/mocks"
1517
)
@@ -19,41 +21,89 @@ var schema []byte
1921

2022
var _ = Describe("Renderer", func() {
2123

22-
var (
23-
ctrl *gomock.Controller
24-
mockCreator *mocks.MockCreator
25-
)
26-
BeforeEach(func() {
27-
ctrl = gomock.NewController(GinkgoT())
28-
mockCreator = mocks.NewMockCreator(ctrl)
29-
})
30-
AfterEach(func() {
31-
ctrl.Finish()
32-
})
24+
Describe("A single trace", func() {
25+
var (
26+
ctrl *gomock.Controller
27+
mockCreator *mocks.MockCreator
28+
)
29+
BeforeEach(func() {
30+
ctrl = gomock.NewController(GinkgoT())
31+
mockCreator = mocks.NewMockCreator(ctrl)
32+
})
33+
AfterEach(func() {
34+
ctrl.Finish()
35+
})
3336

34-
It("Should create package", func() {
35-
buf := NopWriterCloser{&bytes.Buffer{}}
37+
It("Should create package", func() {
38+
buf := NopWriterCloser{&bytes.Buffer{}}
3639

37-
mockCreator.EXPECT().Create(gomock.Eq("scatter_gen.go")).Return(buf, nil).Times(1)
40+
mockCreator.EXPECT().Create(gomock.Eq("scatter_gen.go")).Return(buf, nil).Times(1)
3841

39-
root, err := generator.LoadSchema(bytes.NewReader(schema))
40-
Expect(err).To(BeNil())
42+
root, err := generator.LoadSchema(bytes.NewReader(schema))
43+
Expect(err).To(BeNil())
4144

42-
r, err := generator.NewRenderer(mockCreator, root)
43-
Expect(err).To(BeNil())
45+
r, err := generator.NewRenderer(mockCreator, root)
46+
Expect(err).To(BeNil())
4447

45-
err = r.CreateTrace(".", "scatter")
46-
Expect(err).To(BeNil())
48+
err = r.CreateTrace(".", "scatter")
49+
Expect(err).To(BeNil())
4750

48-
formatted, err := format.Source(buf.Bytes())
49-
Expect(err).To(BeNil())
51+
formatted, err := format.Source(buf.Bytes())
52+
Expect(err).To(BeNil())
5053

51-
Expect(string(formatted)).To(ContainSubstring(`package grob`))
52-
// Type is defined
53-
Expect(string(formatted)).To(ContainSubstring(`type Scatter struct`))
54-
// Implements interface GetType()
55-
Expect(string(formatted)).To(ContainSubstring(`func (trace *Scatter) GetType() TraceType`))
54+
Expect(string(formatted)).To(ContainSubstring(`package grob`))
55+
// Type is defined
56+
Expect(string(formatted)).To(ContainSubstring(`type Scatter struct`))
57+
// Implements interface GetType()
58+
Expect(string(formatted)).To(ContainSubstring(`func (trace *Scatter) GetType() TraceType`))
5659

60+
})
61+
})
62+
Describe("When writing", func() {
63+
var r *generator.Renderer
64+
65+
BeforeEach(func() {
66+
root, err := generator.LoadSchema(bytes.NewReader(schema))
67+
Expect(err).To(BeNil())
68+
69+
r, err = generator.NewRenderer(nil, root)
70+
Expect(err).To(BeNil())
71+
})
72+
73+
Describe("The config", func() {
74+
It("Should be consistent", func() {
75+
76+
original := &bytes.Buffer{}
77+
err := r.WriteConfig(original)
78+
Expect(err).To(BeNil())
79+
80+
for i := 0; i < 10; i++ {
81+
attempt := &bytes.Buffer{}
82+
err = r.WriteConfig(attempt)
83+
Expect(err).To(BeNil())
84+
Expect(attempt).To(Equal(original))
85+
}
86+
87+
})
88+
})
89+
Describe("The Layout", func() {
90+
// TruncatedDiff = false
91+
// MaxLength = 0
92+
It("Should be consistent", func() {
93+
94+
original := &bytes.Buffer{}
95+
err := r.WriteLayout(original)
96+
Expect(err).To(BeNil())
97+
98+
for i := 0; i < 10; i++ {
99+
attempt := &bytes.Buffer{}
100+
err = r.WriteLayout(attempt)
101+
Expect(err).To(BeNil())
102+
Expect(attempt.String()).To(Equal(original.String()))
103+
}
104+
105+
})
106+
})
57107
})
58108
})
59109

generator/templates/plotly.tmpl

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
2+
package grob
3+
4+
import (
5+
"encoding/json"
6+
)
7+
8+
// Generate the files
9+
//go:generate go run ../generator/cmd/generator/main.go --schema ../generator/schema.json --output-directory .
10+
11+
// TraceType is the type for the TraceType field on every trace
12+
type TraceType string
13+
14+
// Trace Every trace implements this interface
15+
// It is useful for autocompletion, it is a better idea to use
16+
// type assertions/switches to identify trace types
17+
type Trace interface {
18+
GetType() TraceType
19+
}
20+
21+
// Traces is a slice of Traces
22+
type Traces []Trace
23+
24+
// Fig is the base type for figures.
25+
type Fig struct {
26+
// Data The data to be plotted is described in an array usually called data, whose elements are trace objects of various types (e.g. scatter, bar etc) as documented in the Full Reference.
27+
// https://plotly.com/javascript/reference
28+
Data Traces `json:"data,omitempty"`
29+
30+
// Layout The layout of the plot – non-data-related visual attributes such as the title, annotations etc – is described in an object usually called layout, as documented in/ the Full Reference.
31+
// https://plotly.com/javascript/reference/layout
32+
Layout *Layout `json:"layout,omitempty"`
33+
34+
// Config High-level configuration options for the plot, such as the scroll/zoom/hover behaviour, is described in an object usually called config, as documented here. The difference between config and layout is that layout relates to the content of the plot, whereas config relates to the context in which the plot is being shown.
35+
// https://plotly.com/javascript/configuration-options
36+
Config *Config `json:"config,omitempty"`
37+
38+
// Animation is not yet implemented, feel free to insert custom a struct
39+
Animation interface{} `json:"animation,omitempty"`
40+
}
41+
42+
// AddTraces Is a shorthand to add figures to a given figure. It handles the case where the Traces value is nil.
43+
func (fig *Fig) AddTraces(traces ...Trace) {
44+
if fig.Data == nil {
45+
fig.Data = make(Traces, 0)
46+
}
47+
fig.Data = append(fig.Data, traces...)
48+
}
49+
50+
// UnmarshalJSON is a custom unmarshal function to properly handle special cases.
51+
func (fig *Fig) UnmarshalJSON(data []byte) error {
52+
var err error
53+
tmp := unmarshalFig{}
54+
err = json.Unmarshal(data, &tmp)
55+
if err != nil {
56+
return err
57+
}
58+
59+
fig.Layout = tmp.Layout
60+
fig.Config = tmp.Config
61+
62+
for i := range tmp.Data {
63+
trace, err := UnmarshalTrace(tmp.Data[i])
64+
if err != nil {
65+
return err
66+
}
67+
fig.AddTraces(trace)
68+
}
69+
return nil
70+
}
71+
72+
type unmarshalFig struct {
73+
Data []json.RawMessage `json:"data,omitempty"`
74+
Layout *Layout `json:"layout,omitempty"`
75+
Config *Config `json:"config,omitempty"`
76+
}
77+
78+
// Bool represents a *bool value. Needed to tell the differenc between false and nil.
79+
type Bool *bool
80+
81+
var (
82+
trueValue bool = true
83+
falseValue bool = false
84+
85+
// True is a *bool with true value
86+
True Bool = &trueValue
87+
// False is a *bool with false value
88+
False Bool = &falseValue
89+
)
90+
91+
// String is a string value, can be a []string if arrayOK is true.
92+
// numeric values are converted to string by plotly, so []<number> can work
93+
type String interface{}
94+
95+
// Color A string describing color. Supported formats: - hex (e.g. '#d3d3d3') - rgb (e.g. 'rgb(255, 0, 0)') - rgba (e.g. 'rgb(255, 0, 0, 0.5)') - hsl (e.g. 'hsl(0, 100%, 50%)') - hsv (e.g. 'hsv(0, 100%, 100%)') - named colors (full list: http://www.w3.org/TR/css3-color/#svg-color)",
96+
type Color interface{}
97+
98+
// ColorList A list of colors. Must be an {array} containing valid colors.
99+
type ColorList []Color
100+
101+
// ColorScale A Plotly colorscale either picked by a name: (any of Greys, YlGnBu, Greens, YlOrRd, Bluered, RdBu, Reds, Blues, Picnic, Rainbow, Portland, Jet, Hot, Blackbody, Earth, Electric, Viridis, Cividis ) customized as an {array} of 2-element {arrays} where the first element is the normalized color level value (starting at *0* and ending at *1*), and the second item is a valid color string.
102+
type ColorScale interface{}

generator/typefile.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ func (file *typeFile) parseFlaglist(name string, attr *Attribute) error {
360360
return nil
361361
}
362362

363-
func sortKeys(attr map[string]*Attribute) []string {
363+
func sortKeys[T any](attr map[string]*T) []string {
364364
keys := make([]string, 0, len(attr))
365365
for k := range attr {
366366
keys = append(keys, k)

0 commit comments

Comments
 (0)