Skip to content

Commit

Permalink
feat: v2.0 release (#49)
Browse files Browse the repository at this point in the history
* feat: support migration between v1 and v2
* docs: update for version 2
* fix: bug in Windows line end parsing affecting CSS
* feat: add a -w parameter to choose how many cores to use during templ generation
* feat: use the number of CPUs available to the machine
* feat: improve the formatting behaviour
* feat: add style and script elements that ignore the contents
* refactor: migrate to go.lsp.dev/protocol
* feat: migrate the LSP server and rewrite functionality to go.lsp.dev
* feat: add sourcemap visualisation
* security: CVE-2022-28948
* chore: upgrade goquery to 1.8.0
* chore: move example to _example to get Go tools to ignore from the top level
* feat: add LSO declaration and definition support
* feat: update LSP edits, replace SourceGraph code
* refactor: use an array of lines to store content, instead of storing the string, to make updates less expensive
* fix: update snippets for new syntax
* docs: add updated GIF
* fix: DidChange storage of updates
  • Loading branch information
a-h committed Jun 5, 2022
1 parent 64a72aa commit 1954d55
Show file tree
Hide file tree
Showing 155 changed files with 10,371 additions and 1,701 deletions.
349 changes: 182 additions & 167 deletions README.md

Large diffs are not rendered by default.

92 changes: 0 additions & 92 deletions cmd/templ/exportcmd/main.go

This file was deleted.

2 changes: 1 addition & 1 deletion cmd/templ/fmtcmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"time"

"github.com/a-h/templ/cmd/templ/processor"
"github.com/a-h/templ/parser"
parser "github.com/a-h/templ/parser/v2"
"github.com/hashicorp/go-multierror"
"github.com/natefinch/atomic"
)
Expand Down
144 changes: 131 additions & 13 deletions cmd/templ/generatecmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,59 @@ package generatecmd

import (
"bufio"
"context"
"fmt"
"html"
"io"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"

"github.com/a-h/templ/cmd/templ/processor"
"github.com/a-h/templ/generator"
"github.com/a-h/templ/parser"
"github.com/a-h/templ/parser/v2"
"github.com/hashicorp/go-multierror"
)

const workerCount = 4

type Arguments struct {
FileName string
Path string
FileName string
Path string
WorkerCount int
GenerateSourceMapVisualisations bool
}

var defaultWorkerCount = runtime.NumCPU()

func Run(args Arguments) (err error) {
if args.FileName != "" {
return processSingleFile(args.FileName)
return processSingleFile(args.FileName, args.GenerateSourceMapVisualisations)
}
return processPath(args.Path)
if args.WorkerCount == 0 {
args.WorkerCount = defaultWorkerCount
}
return processPath(args.Path, args.GenerateSourceMapVisualisations, args.WorkerCount)
}

func processSingleFile(fileName string) error {
func processSingleFile(fileName string, generateSourceMapVisualisations bool) error {
start := time.Now()
err := compile(fileName)
err := compile(fileName, generateSourceMapVisualisations)
if err != nil {
return err
}
fmt.Printf("Generated code for %q in %s\n", fileName, time.Since(start))
return err
}

func processPath(path string) (err error) {
func processPath(path string, generateSourceMapVisualisations bool, workerCount int) (err error) {
start := time.Now()
results := make(chan processor.Result)
go processor.Process(path, compile, workerCount, results)
p := func(fileName string) error {
return compile(fileName, generateSourceMapVisualisations)
}
go processor.Process(path, p, workerCount, results)
var successCount, errorCount int
for r := range results {
if r.Error != nil {
Expand All @@ -52,7 +69,7 @@ func processPath(path string) (err error) {
return err
}

func compile(fileName string) (err error) {
func compile(fileName string, generateSourceMapVisualisations bool) (err error) {
t, err := parser.Parse(fileName)
if err != nil {
return fmt.Errorf("%s parsing error: %w", fileName, err)
Expand All @@ -62,13 +79,114 @@ func compile(fileName string) (err error) {
if err != nil {
return fmt.Errorf("%s compilation error: %w", fileName, err)
}
defer w.Close()
b := bufio.NewWriter(w)
_, err = generator.Generate(t, b)
defer b.Flush()
sourceMap, err := generator.Generate(t, b)
if err != nil {
return fmt.Errorf("%s generation error: %w", fileName, err)
}
if b.Flush() != nil {
return fmt.Errorf("%s write file error: %w", targetFileName, err)
}
if generateSourceMapVisualisations {
err = generateSourceMapVisualisation(fileName, targetFileName, sourceMap)
}
return
}

func generateSourceMapVisualisation(templFileName, goFileName string, sourceMap *parser.SourceMap) error {
var templContents, goContents []byte
var templErr, goErr error
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
templContents, templErr = os.ReadFile(templFileName)
}()
go func() {
defer wg.Done()
goContents, goErr = os.ReadFile(goFileName)
}()
wg.Wait()
if templErr != nil {
return templErr
}
if goErr != nil {
return templErr
}
tl := templLines{contents: string(templContents), sourceMap: sourceMap}
gl := goLines{contents: string(goContents), sourceMap: sourceMap}
visualisationComponent := visualisation(templFileName, tl, gl)

targetFileName := strings.TrimSuffix(templFileName, ".templ") + "_templ_sourcemap.html"
w, err := os.Create(targetFileName)
if err != nil {
return fmt.Errorf("%s sourcemap visualisation error: %w", templFileName, err)
}
defer w.Close()
b := bufio.NewWriter(w)
defer b.Flush()

return visualisationComponent.Render(context.Background(), b)
}

type templLines struct {
contents string
sourceMap *parser.SourceMap
}

func (tl templLines) Render(ctx context.Context, w io.Writer) error {
templLines := strings.Split(tl.contents, "\n")
for lineIndex, line := range templLines {
w.Write([]byte("<span>" + strconv.Itoa(lineIndex) + "&nbsp;</span>\n"))
for colIndex, c := range line {
if _, m, ok := tl.sourceMap.TargetPositionFromSource(uint32(lineIndex), uint32(colIndex)); ok {
sourceID := fmt.Sprintf("src_%d_%d_%d", m.Source.Range.From.Index, m.Source.Range.From.Line, m.Source.Range.From.Col)
targetID := fmt.Sprintf("tgt_%d_%d_%d", m.Target.From.Index, m.Target.From.Index, m.Target.From.Col)
if err := mappedCharacter(string(c), sourceID, targetID).Render(ctx, w); err != nil {
return err
}
} else {
s := html.EscapeString(string(c))
s = strings.ReplaceAll(s, "\t", "&nbsp;")
s = strings.ReplaceAll(s, " ", "&nbsp;")
if _, err := w.Write([]byte(s)); err != nil {
return err
}
}
}
w.Write([]byte("\n<br/>\n"))
}
return nil
}

type goLines struct {
contents string
sourceMap *parser.SourceMap
}

func (gl goLines) Render(ctx context.Context, w io.Writer) error {
templLines := strings.Split(gl.contents, "\n")
for lineIndex, line := range templLines {
w.Write([]byte("<span>" + strconv.Itoa(lineIndex) + "&nbsp;</span>\n"))
for colIndex, c := range line {
if _, m, ok := gl.sourceMap.SourcePositionFromTarget(uint32(lineIndex), uint32(colIndex)); ok {
sourceID := fmt.Sprintf("src_%d_%d_%d", m.Source.Range.From.Index, m.Source.Range.From.Line, m.Source.Range.From.Col)
targetID := fmt.Sprintf("tgt_%d_%d_%d", m.Target.From.Index, m.Target.From.Index, m.Target.From.Col)
if err := mappedCharacter(string(c), sourceID, targetID).Render(ctx, w); err != nil {
return err
}
} else {
s := html.EscapeString(string(c))
s = strings.ReplaceAll(s, "\t", "&nbsp;")
s = strings.ReplaceAll(s, " ", "&nbsp;")
if _, err := w.Write([]byte(s)); err != nil {
return err
}
}
}
w.Write([]byte("\n<br/>\n"))
}
return nil
}
63 changes: 63 additions & 0 deletions cmd/templ/generatecmd/sourcemapvisualisation.templ
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package generatecmd

css row() {
display: flex;
}

css column() {
flex: 50%;
}

css code() {
font-family: monospace;
}

templ visualisation(templFileName string, left, right templ.Component) {
<html>
<head>
<title>{ templFileName } - Source Map Visualisation</title>
<style type="text/css">
.mapped { background-color: green }
.highlighted { background-color: yellow }
</style>
</head>
<body>
<h1>{ templFileName }</h1>

<div class={ templ.Classes(row()) }>
<div class={ templ.Classes(column(), code()) }>
{! left }
</div>
<div class={ templ.Classes(column(), code()) }>
{! right }
</div>
</div>
</body>
</html>
}

script highlight(sourceId, targetId string) {
let items = document.getElementsByClassName(sourceId);
for(let i = 0; i < items.length; i ++) {
items[i].classList.add("highlighted");
}
items = document.getElementsByClassName(targetId);
for(let i = 0; i < items.length; i ++) {
items[i].classList.add("highlighted");
}
}

script removeHighlight(sourceId, targetId string) {
let items = document.getElementsByClassName(sourceId);
for(let i = 0; i < items.length; i ++) {
items[i].classList.remove("highlighted");
}
items = document.getElementsByClassName(targetId);
for(let i = 0; i < items.length; i ++) {
items[i].classList.remove("highlighted");
}
}

templ mappedCharacter(s string, sourceID, targetID string) {
<span class={ templ.Classes(templ.Class("mapped"), templ.Class(sourceID), templ.Class(targetID)) } onMouseOver={ highlight(sourceID, targetID) } onMouseOut={ removeHighlight(sourceID, targetID) }>{ s }</span>
}
Loading

0 comments on commit 1954d55

Please sign in to comment.