Skip to content

Commit

Permalink
Add viewer package (#401)
Browse files Browse the repository at this point in the history
* initial viewer support

* add finder

* initial viewer package with gohtml and wip svelte
  • Loading branch information
matthewmueller committed Apr 15, 2023
1 parent 0dd5ca5 commit e84c6d6
Show file tree
Hide file tree
Showing 22 changed files with 28,680 additions and 10 deletions.
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

0 comments on commit e84c6d6

Please sign in to comment.