Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.18

require (
github.com/PuerkitoBio/goquery v1.8.0
github.com/RyanCarrier/dijkstra v1.1.0
github.com/ajg/form v1.5.2-0.20200323032839-9aeb3cf462e1
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59
github.com/bep/debounce v1.2.1
Expand All @@ -18,6 +19,7 @@ require (
github.com/keegancsmith/rpc v1.3.0
github.com/lithammer/dedent v1.1.0
github.com/livebud/bud-test-plugin v0.0.9
github.com/livebud/js v0.0.0-20221112072017-b9e63b92aad5
github.com/livebud/transpiler v0.0.3
github.com/matthewmueller/diff v0.0.0-20220104030700-cb2fe910d90c
github.com/matthewmueller/gotext v0.0.0-20210424201144-265ed61725ac
Expand All @@ -39,9 +41,11 @@ require (

require (
github.com/BurntSushi/toml v0.4.1 // indirect
github.com/RyanCarrier/dijkstra v1.1.0 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/dop251/goja v0.0.0-20220730095050-d11430fb5f72 // indirect
github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
Expand All @@ -54,6 +58,7 @@ require (
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
mvdan.cc/gofumpt v0.2.0 // indirect
)
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20220730095050-d11430fb5f72 h1:Q4Q/b1gCC2X1b+/RRhZPTcWlJoNE9DpMKt1etq6erWU=
github.com/dop251/goja v0.0.0-20220730095050-d11430fb5f72/go.mod h1:1jWwHOtOkEqsfX6tYsufUc7BBTuGHH2ekiJabpkN4CA=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
github.com/evanw/esbuild v0.14.11 h1:bw50N4v70Dqf/B6Wn+3BM6BVttz4A6tHn8m8Ydj9vxk=
github.com/evanw/esbuild v0.14.11/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
Expand All @@ -38,6 +46,8 @@ github.com/gitchander/permutation v0.0.0-20201214100618-1f3e7285f953 h1:+rJDfq6w
github.com/gitchander/permutation v0.0.0-20201214100618-1f3e7285f953/go.mod h1:lP+DW8LR6Rw3ru9Vo2/y/3iiLaLWmofYql/va+7zJOk=
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
Expand Down Expand Up @@ -66,6 +76,8 @@ github.com/livebud/bud-test-nested-plugin v0.0.5 h1:MpPp20Gng0F+Kvl+L9kttu6nsJIY
github.com/livebud/bud-test-nested-plugin v0.0.5/go.mod h1:M3QujkGG4ggZ6h75t5zF8MEJFrLTwa2USeIYHQdO2YQ=
github.com/livebud/bud-test-plugin v0.0.9 h1:JmS4aj+NV52RUroteLs+ld6rcbkBwio7p9qPNutTsqM=
github.com/livebud/bud-test-plugin v0.0.9/go.mod h1:GTxMZ8W4BIyGIOgAA4hvPHMDDTkaZtfcuhnOcSu3y8M=
github.com/livebud/js v0.0.0-20221112072017-b9e63b92aad5 h1:7fYJMOnT4WrnjRhJ8B6HiJBGcDVFd9jam0205gn9y1k=
github.com/livebud/js v0.0.0-20221112072017-b9e63b92aad5/go.mod h1:TDkks+mVAlB96mxcbIAftAxpGteCxoBYGVF1JThjcLk=
github.com/livebud/transpiler v0.0.3 h1:OFKPsmfTOywBDoOE/ZFMVjAupk/xVXFlXoAgZNRhh5Q=
github.com/livebud/transpiler v0.0.3/go.mod h1:vQYMN//Y2cnM55tw0lOmLGbEETugP7alxTyhQHzNdTI=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
Expand Down Expand Up @@ -153,6 +165,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
Expand All @@ -167,9 +180,12 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
Expand Down
26 changes: 17 additions & 9 deletions package/es/es.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,24 @@ import (
"github.com/livebud/bud/package/log"
)

func New(flag *framework.Flag, log log.Log, module *gomod.Module) *Builder {
return &Builder{flag, module}
type Builder interface {
Serve(serve *Serve) (*File, error)
Bundle(bundle *Bundle) ([]File, error)
}

type Builder struct {
func New(flag *framework.Flag, log log.Log, module *gomod.Module) Builder {
return &builder{flag, module}
}

type builder struct {
flag *framework.Flag
module *gomod.Module
}

var _ Builder = (*builder)(nil)

type File = esbuild.OutputFile
type Plugin = esbuild.Plugin

type Platform uint8

Expand All @@ -35,7 +43,7 @@ type Serve struct {
Platform Platform
}

func (b *Builder) serveOptions(serve *Serve) esbuild.BuildOptions {
func (b *builder) serveOptions(serve *Serve) esbuild.BuildOptions {
switch serve.Platform {
case DOM:
return b.dom([]string{serve.Entry}, serve.Plugins)
Expand All @@ -46,7 +54,7 @@ func (b *Builder) serveOptions(serve *Serve) esbuild.BuildOptions {

var ErrNotRelative = fmt.Errorf("es: entry must be relative")

func (b *Builder) Serve(serve *Serve) (*File, error) {
func (b *builder) Serve(serve *Serve) (*File, error) {
if !isRelativeEntry(serve.Entry) {
return nil, fmt.Errorf("%w %q", ErrNotRelative, serve.Entry)
}
Expand Down Expand Up @@ -75,7 +83,7 @@ type Bundle struct {
Platform Platform
}

func (b *Builder) bundleOptions(bundle *Bundle) esbuild.BuildOptions {
func (b *builder) bundleOptions(bundle *Bundle) esbuild.BuildOptions {
switch bundle.Platform {
case DOM:
return b.dom(bundle.Entries, bundle.Plugins)
Expand All @@ -84,7 +92,7 @@ func (b *Builder) bundleOptions(bundle *Bundle) esbuild.BuildOptions {
}
}

func (b *Builder) Bundle(bundle *Bundle) ([]File, error) {
func (b *builder) Bundle(bundle *Bundle) ([]File, error) {
for _, entry := range bundle.Entries {
if !isRelativeEntry(entry) {
return nil, fmt.Errorf("%w %q", ErrNotRelative, entry)
Expand All @@ -108,7 +116,7 @@ const outDir = "./"
const globalName = "bud"

// SSR creates a server-rendered preset
func (b *Builder) ssr(entries []string, plugins []esbuild.Plugin) esbuild.BuildOptions {
func (b *builder) ssr(entries []string, plugins []esbuild.Plugin) esbuild.BuildOptions {
options := esbuild.BuildOptions{
EntryPoints: entries,
Plugins: plugins,
Expand All @@ -129,7 +137,7 @@ func (b *Builder) ssr(entries []string, plugins []esbuild.Plugin) esbuild.BuildO
}

// DOM creates a dom-rendered preset
func (b *Builder) dom(entries []string, plugins []esbuild.Plugin) esbuild.BuildOptions {
func (b *builder) dom(entries []string, plugins []esbuild.Plugin) esbuild.BuildOptions {
options := esbuild.BuildOptions{
EntryPoints: entries,
Plugins: plugins,
Expand Down
222 changes: 222 additions & 0 deletions package/transpiler/transpiler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package transpiler

import (
"errors"
"fmt"
"path/filepath"
"sort"
"strings"
"sync"

"github.com/RyanCarrier/dijkstra"
)

// ErrNoPath is returned when there is no path between two extensions.
var ErrNoPath = dijkstra.ErrNoPath

type File struct {
base string
ext string
Data []byte
}

// Path returns the current file path that's being transpiled.
func (f *File) Path() string {
return f.base + f.ext
}

// Interface for transpiling and testing if you can transpile from one extension
// to another. This interface is read-only. If you'd like to add extensions, use
// the Transpiler struct.
type Interface interface {
Best(fromExt string, accepts []string) (string, error)
Transpile(fromPath, toExt string, code []byte) ([]byte, error)
}

func New() *Transpiler {
return &Transpiler{
ids: map[string]int{},
exts: map[int]string{},
fns: map[string][]func(file *File) error{},
graph: dijkstra.NewGraph(),
}
}

// Transpiler is a generic multi-step tool for transpiling code from one
// language to another.
type Transpiler struct {
ids map[string]int // ext -> id
exts map[int]string // id -> ext
fns map[string][]func(file *File) error // map["ext>ext"][]fns

mu sync.RWMutex
graph *dijkstra.Graph
}

var _ Interface = (*Transpiler)(nil)

// edgekey returns a key for the edge between two extensions.
// (e.g. edgeKey("svelte", "html") => "svelte>html")
func edgeKey(fromExt, toExt string) string {
return fromExt + ">" + toExt
}

// Add a tranpile function to go from one extension to another.
func (t *Transpiler) Add(fromExt, toExt string, transpile func(file *File) error) {
t.mu.Lock()
defer t.mu.Unlock()
t.add(fromExt, toExt, transpile)
}

func (t *Transpiler) add(fromExt, toExt string, transpile func(file *File) error) {
// Add the "from" extension to the graph
if _, ok := t.ids[fromExt]; !ok {
id := len(t.ids)
t.ids[fromExt] = id
t.exts[id] = fromExt
t.graph.AddVertex(id)
}
edge := edgeKey(fromExt, toExt)
// If the "from" and "to" extensions are the same, add the function and return
if fromExt == toExt {
t.fns[edge] = append(t.fns[edge], transpile)
return
}
// Add the "to" extension to the graph
if _, ok := t.ids[toExt]; !ok {
id := len(t.ids)
t.ids[toExt] = id
t.exts[id] = toExt
t.graph.AddVertex(id)
}
// Add the edge with a cost of 1
t.graph.AddArc(t.ids[fromExt], t.ids[toExt], 1)
// Add the function to a list of transpilers
t.fns[edge] = append(t.fns[edge], transpile)
}

func (t *Transpiler) Path(fromExt, toExt string) (hops []string, err error) {
t.mu.RLock()
defer t.mu.RUnlock()
return t.path(fromExt, toExt)
}

// Best returns the best extension to transpile to from the given extension
func (t *Transpiler) Best(fromExt string, accepts []string) (toExt string, err error) {
t.mu.RLock()
defer t.mu.RUnlock()
return t.best(fromExt, accepts)
}

// Candidate is a candidate extension we can transpile to with the number of
// hops it would take to get there.
// type candidate struct {
// Ext string
// Hops int
// }

func (t *Transpiler) best(fromExt string, accepts []string) (string, error) {
fromID, ok := t.ids[fromExt]
if !ok {
return "", fmt.Errorf("transpiler: %w for %q", ErrNoPath, fromExt)
}
buckets := map[int][]string{}
for _, id := range t.ids {
if id == fromID {
continue
}
best, err := t.graph.Shortest(fromID, id)
if err != nil {
if errors.Is(err, ErrNoPath) {
continue
}
return "", fmt.Errorf("transpiler: unable to get shorted path for %q. %w", fromExt, err)
}
distance := int(best.Distance)
buckets[distance] = append(buckets[distance], t.exts[id])
}
distances := []int{}
for distance := range buckets {
distances = append(distances, distance)
}
sort.Ints(distances)
hops := [][]string{}
for _, distance := range distances {
hops = append(hops, buckets[distance])
}
// Within each hop bucket, look for the most acceptable extension
for _, exts := range hops {
for _, accept := range accepts {
for _, ext := range exts {
if ext == accept {
return ext, nil
}
}
}
}
return "", fmt.Errorf("transpiler: no acceptable path for %q. %w", fromExt, ErrNoPath)
}

// Path to go from one extension to another.
func (t *Transpiler) path(fromExt, toExt string) (hops []string, err error) {
if fromExt == toExt {
return []string{fromExt}, nil
}
if _, ok := t.ids[fromExt]; !ok {
return nil, fmt.Errorf("transpiler: %w from %q to %q", ErrNoPath, fromExt, toExt)
}
if _, ok := t.ids[toExt]; !ok {
return nil, fmt.Errorf("transpiler: %w from %q to %q", ErrNoPath, fromExt, toExt)
}
best, err := t.graph.Shortest(t.ids[fromExt], t.ids[toExt])
if err != nil {
return nil, fmt.Errorf("transpiler: %w", err)
}
for _, id := range best.Path {
hops = append(hops, t.exts[id])
}
return hops, nil
}

func (t *Transpiler) Transpile(fromPath, toExt string, code []byte) ([]byte, error) {
t.mu.Lock()
defer t.mu.Unlock()
return t.transpile(fromPath, toExt, code)
}

// Transpile the code from one extension to another.
func (t *Transpiler) transpile(fromPath, toExt string, code []byte) ([]byte, error) {
fromExt := filepath.Ext(fromPath)
// Find the shortest path
hops, err := t.path(fromExt, toExt)
if err != nil {
return nil, err
}
// Create the file
file := &File{
base: strings.TrimSuffix(fromPath, filepath.Ext(fromPath)),
ext: fromExt,
Data: code,
}
// For each hop run the functions
for i, ext := range hops {
// Call the transition functions (e.g. svelte => html)
if i > 0 {
prevExt := hops[i-1]
edge := edgeKey(prevExt, ext)
for _, fn := range t.fns[edge] {
if err := fn(file); err != nil {
return nil, err
}
}
}
file.ext = ext
// Call the loops (e.g. svelte => svelte)
for _, fn := range t.fns[edgeKey(ext, ext)] {
if err := fn(file); err != nil {
return nil, err
}
}
}
return file.Data, nil
}
Loading