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
10 changes: 5 additions & 5 deletions cmd/hype/cli/vscode.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (cmd *VSCode) Flags() (*flag.FlagSet, error) {
return cmd.flags, nil
}

func (cmd *VSCode) Main(ctx context.Context, pwd string, args []string) error {
func (cmd *VSCode) Main(ctx context.Context, _ string, args []string) error {
if err := cmd.validate(); err != nil {
return err
}
Expand Down Expand Up @@ -77,14 +77,14 @@ func (cmd *VSCode) Main(ctx context.Context, pwd string, args []string) error {

path := args[0]

pwd = filepath.Dir(path)
dir := filepath.Dir(path)
name := filepath.Base(path)

cmd.FS = os.DirFS(pwd)
cmd.FS = os.DirFS(dir)

err = WithTimeout(ctx, cmd.Timeout, func(ctx context.Context) error {
return WithinDir(pwd, func() error {
return cmd.execute(ctx, pwd, name)
return WithinDir(dir, func() error {
return cmd.execute(ctx, dir, name)
})
})

Expand Down
49 changes: 45 additions & 4 deletions mdx/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"regexp"
"strconv"
"strings"
"sync"

Expand Down Expand Up @@ -46,15 +47,19 @@ func New() *Parser {
return p
}

func (p *Parser) parse(lines []string) ([]byte, error) {
func (p *Parser) parse(lines []string, fenced []bool) ([]byte, error) {
bb := &bytes.Buffer{}

var chunk []string
var after string

var ind int
for _, line := range lines {
for i, line := range lines {
ind++
if fenced[i] {
chunk = append(chunk, line)
continue
}
if strings.HasPrefix(line, "<include") {
after = line
break
Expand All @@ -80,7 +85,7 @@ func (p *Parser) parse(lines []string) ([]byte, error) {
}

if ind < len(lines) {
b, err := p.parse(lines[ind:])
b, err := p.parse(lines[ind:], fenced[ind:])
if err != nil {
return nil, err
}
Expand All @@ -105,11 +110,47 @@ func (p *Parser) parse(lines []string) ([]byte, error) {
return act, nil
}

func isBreak(line string) bool {
return strings.HasPrefix(line, "<include") || strings.HasPrefix(line, "---")
}

var breakRx = regexp.MustCompile(`hypemdxbreak(\d+)x`)

func fencedLines(lines []string) []bool {
marked := make([]string, len(lines))
for i, line := range lines {
if isBreak(line) {
marked[i] = fmt.Sprintf("hypemdxbreak%dx", i)
continue
}
marked[i] = line
}

fenced := make([]bool, len(lines))

md := blackfriday.New(blackfriday.WithExtensions(extensions))
doc := md.Parse([]byte(strings.Join(marked, "\n")))
doc.Walk(func(n *blackfriday.Node, entering bool) blackfriday.WalkStatus {
if n.Type != blackfriday.CodeBlock || !n.IsFenced {
return blackfriday.GoToNext
}
for _, m := range breakRx.FindAllSubmatch(n.Literal, -1) {
i, err := strconv.Atoi(string(m[1]))
if err == nil && i < len(lines) && isBreak(lines[i]) {
fenced[i] = true
}
}
return blackfriday.GoToNext
})

return fenced
}

// Parse parses the Markdown and returns the HTML.
func (p *Parser) Parse(src []byte) ([]byte, error) {
p.Lock()
p.lines = strings.Split(string(src), "\n")
p.Unlock()

return p.parse(p.lines)
return p.parse(p.lines, fencedLines(p.lines))
}
75 changes: 75 additions & 0 deletions mdx/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,78 @@ func Test_Parser_Parse(t *testing.T) {
// fmt.Println(act)
r.Equal(exp, act)
}

func Test_Parser_Parse_PageBreakInsideFence(t *testing.T) {
t.Parallel()
r := require.New(t)

src := "# Title\n\n```shell\n$ go run .\n\nHello World\n\n--------\nGo Version: go1.25.0\n\n```\n\n## Next\n\n~~~\n<include src=\"x.md\"></include>\n~~~\n\n---\n\n# Second"

p := New()
out, err := p.Parse([]byte(src))
r.NoError(err)

act := string(out)
r.Equal(2, strings.Count(act, "<page>"))
r.Contains(act, "--------\nGo Version: go1.25.0\n\n</code></pre>")
r.Contains(act, "<h2>Next</h2>")
r.Contains(act, "&lt;include src=&quot;x.md&quot;&gt;&lt;/include&gt;")
r.Contains(act, "<h1>Second</h1>")
}

func Test_Parser_Parse_InlineCodeIsNotFence(t *testing.T) {
t.Parallel()
r := require.New(t)

src := "# Title\n\n``` use ``code`` ```\n\n---\n\n# Second"

p := New()
out, err := p.Parse([]byte(src))
r.NoError(err)

r.Equal(2, strings.Count(string(out), "<page>"))
}

func Test_Parser_Parse_FenceInsideHTMLBlock(t *testing.T) {
t.Parallel()
r := require.New(t)

src := "# Title\n\n<pre>\n```\n</pre>\n\n---\n\n# Second\n\n```go\nx := 1\n```\n"

p := New()
out, err := p.Parse([]byte(src))
r.NoError(err)

act := string(out)
r.Equal(2, strings.Count(act, "<page>"))
r.Contains(act, "<h1>Second</h1>")
}

func Test_Parser_Parse_FenceCloseMustMatchExactly(t *testing.T) {
t.Parallel()

tcs := []struct {
name string
close string
}{
{name: "longer marker", close: "````"},
{name: "trailing spaces", close: "``` "},
}

for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
r := require.New(t)

src := "# Title\n\n```\nbefore\n" + tc.close + "\n---\nafter\n```\n"

p := New()
out, err := p.Parse([]byte(src))
r.NoError(err)

act := string(out)
r.Equal(1, strings.Count(act, "<page>"))
r.Contains(act, "---\nafter\n</code></pre>")
})
}
}
Loading