Skip to content

Commit

Permalink
Use file directive
Browse files Browse the repository at this point in the history
Signed-off-by: Jack Baldry <jack.baldry@grafana.com>
  • Loading branch information
jdbaldry committed Jun 27, 2024
1 parent f3a41d3 commit ab43c05
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 248 deletions.
186 changes: 71 additions & 115 deletions tools/transformer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,7 @@ import (
"github.com/yuin/goldmark/util"
)

const (
command = "transformer"

indexFilename = "index.json"
introFilename = "intro.md"
finishFilename = "finish.md"
)
const command = "transformer"

func usage(w io.Writer, fs *flag.FlagSet) {
fmt.Fprintf(w, "Usage of %s:\n", command)
Expand All @@ -40,103 +34,53 @@ func usage(w io.Writer, fs *flag.FlagSet) {
fmt.Fprintln(w, " Path to the Killercoda output directory")
}

func writeIntro(dstDirPath string, data []byte, transformers []util.PrioritizedValue, renderer renderer.Renderer) error {
md := goldmark.NewMarkdown()
md.Parser().AddOptions(
parser.WithASTTransformers(
append(transformers, util.Prioritized(&IntroTransformer{}, 0))...),
func main() {
const (
requiredSrcFilePath = iota
requiredDstDirPath
requiredTotal
)
md.SetRenderer(renderer)

root := md.Parser().Parse(text.NewReader(data))
fs := flag.NewFlagSet(command, flag.ExitOnError)
flag.Parse()

out, err := os.Create(filepath.Join(dstDirPath, introFilename))
if err != nil {
return fmt.Errorf("couldn't create intro file: %w", err)
}
if flag.NArg() != requiredTotal {
usage(os.Stderr, fs)

if err := md.Renderer().Render(out, data, root); err != nil {
return fmt.Errorf("couldn't render intro output: %w", err)
os.Exit(2)
}

return nil
}

func writeFinish(dstDirPath string, data []byte, transformers []util.PrioritizedValue, renderer renderer.Renderer) error {
md := goldmark.NewMarkdown()
md.Parser().AddOptions(
parser.WithASTTransformers(
append(transformers, util.Prioritized(&FinishTransformer{}, 0))...),
)
md.SetRenderer(renderer)

root := md.Parser().Parse(text.NewReader(data))
srcFilePath := flag.Arg(requiredSrcFilePath)
dstDirPath := flag.Arg(requiredDstDirPath)

out, err := os.Create(filepath.Join(dstDirPath, finishFilename))
if err != nil {
return fmt.Errorf("couldn't create finish file: %w", err)
}
if err := transform(srcFilePath, dstDirPath); err != nil {
if e, ok := err.(interface{ Unwrap() []error }); ok {
for _, err := range e.Unwrap() {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
} else {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}

if err := md.Renderer().Render(out, data, root); err != nil {
return fmt.Errorf("couldn't render finish output: %w", err)
os.Exit(1)
}

return nil
}

func writeStep(dstDirPath string, n int, data []byte, transformers []util.PrioritizedValue, renderer renderer.Renderer) error {
func writeFile(dstDirPath, filename string, transformers []util.PrioritizedValue, renderer renderer.Renderer, data []byte) error {
md := goldmark.NewMarkdown()
md.Parser().AddOptions(
parser.WithASTTransformers(
append(transformers, util.Prioritized(&StepTransformer{Step: n}, 0))...),
)
md.Parser().AddOptions(parser.WithASTTransformers(transformers...))

md.SetRenderer(renderer)

root := md.Parser().Parse(text.NewReader(data))

out, err := os.Create(filepath.Join(dstDirPath, fmt.Sprintf("step%d.md", n)))
out, err := os.Create(filepath.Join(dstDirPath, filename))
if err != nil {
return fmt.Errorf("could create step file: %w", err)
return fmt.Errorf("couldn't create intro file: %w", err)
}

if err := md.Renderer().Render(out, data, root); err != nil {
return fmt.Errorf("couldn't render step %d output: %w", n, err)
}

return nil
}

func writeIndex(dstDirPath string, meta map[any]any, steps int, wroteIntro bool, wroteFinish bool) error {
index, err := killercoda.FromMeta(meta)
if err != nil {
return fmt.Errorf("couldn't parse metadata: %w", err)
}

if wroteIntro {
index.Details.Intro.Text = introFilename
}

for i := 0; i < steps; i++ {
index.Details.Steps = append(index.Details.Steps, killercoda.Text{
Text: fmt.Sprintf("step%d.md", i+1),
})
}

if wroteFinish {
index.Details.Finish.Text = finishFilename
}

f, err := os.Create(filepath.Join(dstDirPath, indexFilename))
if err != nil {
return fmt.Errorf("couldn't create index file: %w", err)
}
defer f.Close()

enc := json.NewEncoder(f)
enc.SetIndent("", " ")

if err := enc.Encode(index); err != nil {
return fmt.Errorf("couldn't encode index file: %w", err)
return fmt.Errorf("couldn't render intro output: %w", err)
}

return nil
Expand Down Expand Up @@ -198,16 +142,16 @@ func transform(srcFilePath, dstDirPath string) error {
wroteFinish bool
)

if bytes.Contains(data, []byte(introStartMarker)) {
if err := writeIntro(dstDirPath, data, transformers, renderer); err != nil {
if bytes.Contains(data, []byte(fileIntroStartMarker)) {
if err := writeFile(dstDirPath, "intro.md", append(transformers, util.Prioritized(&StepTransformer{StartMarker: fileIntroStartMarker, EndMarker: fileIntroEndMarker}, 0)), renderer, data); err != nil {
return err
}

wroteIntro = true
}

if bytes.Contains(data, []byte(finishStartMarker)) {
if err := writeFinish(dstDirPath, data, transformers, renderer); err != nil {
if bytes.Contains(data, []byte(fileFinishStartMarker)) {
if err := writeFile(dstDirPath, "finish.md", append(transformers, util.Prioritized(&StepTransformer{StartMarker: fileFinishStartMarker, EndMarker: fileFinishEndMarker}, 0)), renderer, data); err != nil {
return err
}

Expand All @@ -219,49 +163,61 @@ func transform(srcFilePath, dstDirPath string) error {
steps int
)

for i := 1; i <= 5; i++ {
if regexp.MustCompile(fmt.Sprintf(`<!-- INTERACTIVE step%d.md START -->`, i)).Match(data) {
steps++
for i := 1; i <= 20; i++ {
startMarker := fmt.Sprintf(`<!-- INTERACTIVE file step%d.md START -->`, i)
endMarker := fmt.Sprintf(`<!-- INTERACTIVE file step%d.md END -->`, i)

if err := writeStep(dstDirPath, i, data, transformers, renderer); err != nil {
if regexp.MustCompile(startMarker).Match(data) {
steps++
if err := writeFile(dstDirPath, fmt.Sprintf("step%d.md", i), append(transformers, util.Prioritized(&StepTransformer{StartMarker: startMarker, EndMarker: endMarker}, 0)), renderer, data); err != nil {
errs = errors.Join(errs, err)
}

continue
}

break
}

writeIndex(dstDirPath, meta, steps, wroteIntro, wroteFinish)
if err := writeIndex(dstDirPath, meta, steps, wroteIntro, wroteFinish); err != nil {
return err
}

return errs
}

func main() {
const (
requiredSrcFilePath = iota
requiredDstDirPath
requiredTotal
)
func writeIndex(dstDirPath string, meta map[any]any, steps int, wroteIntro bool, wroteFinish bool) error {
index, err := killercoda.FromMeta(meta)
if err != nil {
return fmt.Errorf("couldn't parse metadata: %w", err)
}

fs := flag.NewFlagSet(command, flag.ExitOnError)
flag.Parse()
if wroteIntro {
index.Details.Intro.Text = "intro.md"
}

if flag.NArg() != requiredTotal {
usage(os.Stderr, fs)
for i := 0; i < steps; i++ {
index.Details.Steps = append(index.Details.Steps, killercoda.Text{
Text: fmt.Sprintf("step%d.md", i+1),
})
}

os.Exit(2)
if wroteFinish {
index.Details.Finish.Text = "finish.md"
}

srcFilePath := flag.Arg(requiredSrcFilePath)
dstDirPath := flag.Arg(requiredDstDirPath)
f, err := os.Create(filepath.Join(dstDirPath, "index.json"))
if err != nil {
return fmt.Errorf("couldn't create index file: %w", err)
}
defer f.Close()

if err := transform(srcFilePath, dstDirPath); err != nil {
if e, ok := err.(interface{ Unwrap() []error }); ok {
for _, err := range e.Unwrap() {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
} else {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
enc := json.NewEncoder(f)
enc.SetIndent("", " ")

os.Exit(1)
if err := enc.Encode(index); err != nil {
return fmt.Errorf("couldn't encode index file: %w", err)
}

return nil
}
100 changes: 5 additions & 95 deletions tools/transformer/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,50 +194,6 @@ func (t *FigureTransformer) Transform(node *ast.Document, reader text.Reader, _
}
}

type FinishTransformer struct{}

// Transform implements the parser.ASTTransformer interface and keeps only the sibling nodes within the finish start and end markers.
// It removes all other nodes resulting in a document that only contains the content between the markers.
// It removes the markers themselves.
func (t *FinishTransformer) Transform(root *ast.Document, reader text.Reader, _ parser.Context) {
source := reader.Source()

err := ast.Walk(root, func(node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}

if isMarker(node, source, finishStartMarker) {
var toKeep []ast.Node
for sibling := node.NextSibling(); ; sibling = sibling.NextSibling() {
if sibling == nil {
return ast.WalkStop, fmt.Errorf("%w: %s", errNoEndMarker, finishEndMarker)
}

if isMarker(sibling, source, finishEndMarker) {
break
}

toKeep = append(toKeep, sibling)
}

root.RemoveChildren(root)

for _, node := range toKeep {
root.AppendChild(root, node)
node.SetParent(root)
}

return ast.WalkStop, nil
}

return ast.WalkContinue, nil
})
if err != nil {
fmt.Fprintf(os.Stderr, "Error transforming AST: %v\n", err)
}
}

type HeadingTransformer struct{}

// Transform implements the parser.ASTTransformer interface and ensures the heading hierarchy begins at H1.
Expand Down Expand Up @@ -389,50 +345,6 @@ func (t *InlineActionTransformer) Transform(node *ast.Document, _ text.Reader, _
}
}

type IntroTransformer struct{}

// Transform implements the parser.ASTTransformer interface and keeps only the sibling nodes within the intro start and end markers.
// It removes all other nodes resulting in a document that only contains the content between the markers.
// It removes the markers themselves.
func (t *IntroTransformer) Transform(root *ast.Document, reader text.Reader, _ parser.Context) {
source := reader.Source()

err := ast.Walk(root, func(node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}

if isMarker(node, source, introStartMarker) {
var toKeep []ast.Node
for sibling := node.NextSibling(); ; sibling = sibling.NextSibling() {
if sibling == nil {
return ast.WalkStop, fmt.Errorf("%w: %s", errNoEndMarker, introStartMarker)
}

if isMarker(sibling, source, introEndMarker) {
break
}

toKeep = append(toKeep, sibling)
}

root.RemoveChildren(root)

for _, node := range toKeep {
root.AppendChild(root, node)
node.SetParent(root)
}

return ast.WalkStop, nil
}

return ast.WalkContinue, nil
})
if err != nil {
fmt.Fprintf(os.Stderr, "Error transforming AST: %v\n", err)
}
}

type LinkTransformer struct{}

// Transform implements the parser.ASTTransformer interface and replaces version substitution syntax (<SOMETHING_VERSION>) with 'latest' in links.
Expand Down Expand Up @@ -487,31 +399,29 @@ func (t *LinkTransformer) Transform(root *ast.Document, _ text.Reader, _ parser.
}

type StepTransformer struct {
Step int
StartMarker string
EndMarker string
}

// Transform implements the parser.ASTTransformer interface and keeps only the sibling nodes within the step start and end markers.
// It removes all other nodes resulting in a document that only contains the content between the markers.
// It removes the markers themselves.
func (t *StepTransformer) Transform(root *ast.Document, reader text.Reader, _ parser.Context) {
stepStartMarker := fmt.Sprintf("<!-- INTERACTIVE step%d.md START -->", t.Step)
stepEndMarker := fmt.Sprintf("<!-- INTERACTIVE step%d.md END -->", t.Step)

source := reader.Source()

err := ast.Walk(root, func(node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}

if isMarker(node, source, stepStartMarker) {
if isMarker(node, source, t.StartMarker) {
var toKeep []ast.Node
for sibling := node.NextSibling(); ; sibling = sibling.NextSibling() {
if sibling == nil {
return ast.WalkStop, fmt.Errorf("%w: %s", errNoEndMarker, stepStartMarker)
return ast.WalkStop, fmt.Errorf("%w: %s", errNoEndMarker, t.StartMarker)
}

if isMarker(sibling, source, stepEndMarker) {
if isMarker(sibling, source, t.EndMarker) {
break
}

Expand Down
Loading

0 comments on commit ab43c05

Please sign in to comment.