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
74 changes: 35 additions & 39 deletions quarto/main.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
// A generated module for Quarto functions
// A Dagger module for authoring, rendering, and publishing Quarto projects.
//
// This module has been generated via dagger init and serves as a reference to
// basic module structure as you get started with Dagger.
//
// Two functions have been pre-created. You can modify, delete, or add to them,
// as needed. They demonstrate usage of arguments and return types using simple
// echo and grep commands. The functions can be called from the dagger CLI or
// from one of the SDKs.
//
// The first line in this comment block is a short description line and the
// rest is a long description with more detail on the module's purpose or usage,
// if appropriate. All modules should have a short description.
// This module provides tools to render Quarto documents, books, websites,
// install custom Quarto extensions, and manage LaTeX packages via TeX Live.
package main

import (
Expand All @@ -19,11 +10,11 @@ import (
)

const (
defaultImageRepository = "ghcr.io/quarto-dev/quarto"
defaultImageRepository = "ghcr.io/quarto-dev/quarto:latest"
tlmgrUpdateURL = "https://mirror.ctan.org/systems/texlive/tlnet/update-tlmgr-latest.sh"
)

// Quarto is a module for running Quarto
// Quarto provides functions for running and compiling Quarto projects.
type Quarto struct {
// +private
Image string
Expand All @@ -35,15 +26,15 @@ type Quarto struct {
Ctr *dagger.Container
}

// New creates a new instance of the Quarto struct
// New creates a new instance of the Quarto module.
func New(
// Custom image reference in "repository:tag" format to use as a base container.
// +optional
image string,
// List of extensions to add to the container.
// List of Quarto extensions to install (e.g. "quarto-ext/lightbox").
// +optional
extensions []string,
// List of optional LaTeX packages to install.
// List of optional LaTeX packages to install via tlmgr (requires a TeX-enabled image like quarto-full).
// +optional
latexPackages []string,
) *Quarto {
Expand All @@ -54,7 +45,7 @@ func New(
}
}

// Container returns the underlying Dagger container
// container returns the underlying Dagger container, lazily initialized.
func (m *Quarto) container() *dagger.Container {
if m.Ctr != nil {
return m.Ctr
Expand All @@ -67,20 +58,19 @@ func (m *Quarto) container() *dagger.Container {

ctr := dag.Container().From(image)

if strings.Contains(image, "quarto-full") {
// Update tlmgr
// Only update tlmgr and install packages if LaTeX packages are explicitly requested
if len(m.LatexPackages) > 0 {
ctr = ctr.WithExec([]string{
"sh", "-c",
"curl -fsSL " + tlmgrUpdateURL + " -o update-tlmgr-latest.sh && sh update-tlmgr-latest.sh -- --update",
})

// Install specified LaTeX packages
for _, pkg := range m.LatexPackages {
ctr = ctr.WithExec([]string{"tlmgr", "install", pkg})
}
// Install all packages in a single command layer
installCmd := append([]string{"tlmgr", "install"}, m.LatexPackages...)
ctr = ctr.WithExec(installCmd)
}

// Add Quarto extensions
// Install Quarto extensions
for _, ext := range m.Extensions {
ctr = ctr.WithExec([]string{"quarto", "add", "--no-prompt", ext})
}
Expand All @@ -89,35 +79,41 @@ func (m *Quarto) container() *dagger.Container {
return m.Ctr
}

// Build runs the quarto render command exporting to a directory
// Build compiles the Quarto project and returns the exported output directory.
func (m *Quarto) Build(
// source directory.
// Source directory of the Quarto project.
source *dagger.Directory,
// Output directory name relative to source (e.g. "_output", "_site", "_book").
// +default="_output"
// +optional
outputDir string,
) *dagger.Directory {
workdir := "/work"
outPath := workdir + "/" + strings.TrimPrefix(outputDir, "/")

return m.container().
WithDirectory("/tmp", source).
WithWorkdir("/tmp").
WithExec([]string{"quarto", "render"}).Directory("/tmp/_output")
WithMountedDirectory(workdir, source).
WithWorkdir(workdir).
WithExec([]string{"quarto", "render"}).
Directory(outPath)
}

// Render runs the quarto render command
// Render runs the quarto render command and returns the container.
func (m *Quarto) Render(
// source directory.
// Source directory of the Quarto project.
source *dagger.Directory,
) *dagger.Container {
return m.container().
WithDirectory("/tmp", source).
WithWorkdir("/tmp").
WithMountedDirectory("/work", source).
WithWorkdir("/work").
WithExec([]string{"quarto", "render"})
}

// Cli runs the quarto cli
// Cli executes an arbitrary command in the Quarto container.
func (m *Quarto) Cli(
// commands to run
// Command string to execute (e.g. "quarto --version" or "quarto check").
args string,
) *dagger.Container {
parsedArgs := strings.Split(args, " ")

return m.container().
WithExec(parsedArgs)
WithExec([]string{"sh", "-c", args})
}
88 changes: 23 additions & 65 deletions quarto/test/main.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,19 @@
// A generated module for Quartotest functions
//
// This module has been generated via dagger init and serves as a reference to
// basic module structure as you get started with Dagger.
//
// Two functions have been pre-created. You can modify, delete, or add to them,
// as needed. They demonstrate usage of arguments and return types using simple
// echo and grep commands. The functions can be called from the dagger CLI or
// from one of the SDKs.
//
// The first line in this comment block is a short description line and the
// rest is a long description with more detail on the module's purpose or usage,
// if appropriate. All modules should have a short description.
// Package main provides test suites for the Quarto Dagger module.
package main

import (
"context"
"dagger/quarto/test/internal/dagger"
"os"
"regexp"
"errors"
"strings"

"github.com/sourcegraph/conc/pool"
)

// Quartotest is a Dagger module that provides functions for running Quarto.
// Quartotest provides tests for the Quarto module.
type Quartotest struct{}

// All runs all tests.
// All runs all tests concurrently.
func (m *Quartotest) All(ctx context.Context) error {
p := pool.New().WithErrors().WithContext(ctx)

Expand All @@ -38,9 +26,8 @@ func (m *Quartotest) All(ctx context.Context) error {
return p.Wait()
}

// Extensions add quarto extensions to the container.
// Extensions tests installing Quarto extensions.
func (m *Quartotest) Extensions(ctx context.Context) error {

_, err := dag.Quarto(
dagger.QuartoOpts{
Extensions: []string{
Expand All @@ -50,18 +37,14 @@ func (m *Quartotest) Extensions(ctx context.Context) error {
},
).Cli("quarto --version").Sync(ctx)

if err != nil {
return err
}

return nil
return err
}

// Build runs the quarto build command.
// Build tests compiling a document to PDF and verifies the output artifact.
func (m *Quartotest) Build(ctx context.Context) error {

dir := dag.CurrentModule().Source().Directory("./testdata")
outputdata, err := dag.Quarto(

outputDir, err := dag.Quarto(
dagger.QuartoOpts{
Image: "ghcr.io/quarto-dev/quarto-full",
},
Expand All @@ -70,73 +53,48 @@ func (m *Quartotest) Build(ctx context.Context) error {
return err
}

outputDir := "./outputdata"
_, err = outputdata.Export(ctx, outputDir)
if err != nil {
return err
}

files, err := os.ReadDir(outputDir)
// Verify generated files directly inside the Dagger Directory (no host disk writes needed)
entries, err := outputDir.Entries(ctx)
if err != nil {
return err
}

for _, file := range files {
re := regexp.MustCompile(`\.pdf$`)
if re.MatchString(file.Name()) {
return nil
for _, name := range entries {
if strings.HasSuffix(name, ".pdf") {
return nil // PDF successfully generated!
}
}

err = os.RemoveAll(outputDir)
if err != nil {
return err
}

return nil
return errors.New("quarto build succeeded, but no .pdf file was found in output directory")
}

// Render runs the quarto render command.
// Render tests running the render process directly on the container.
func (m *Quartotest) Render(ctx context.Context) error {

dir := dag.CurrentModule().Source().Directory("./testdata")

_, err := dag.Quarto(
dagger.QuartoOpts{
Image: "ghcr.io/quarto-dev/quarto-full",
},
).Render(dir).Sync(ctx)

if err != nil {
return err
}

return nil
return err
}

// FullVersion runs the quarto --version command.
// FullVersion tests the CLI against the quarto-full image.
func (m *Quartotest) FullVersion(ctx context.Context) error {

_, err := dag.Quarto(
dagger.QuartoOpts{
Image: "ghcr.io/quarto-dev/quarto-full",
},
).Cli("quarto --version").Sync(ctx)

if err != nil {
return err
}

return nil
return err
}

// CliVersion runs the quarto --version command.
// Version tests the CLI against the default lightweight image.
func (m *Quartotest) Version(ctx context.Context) error {

_, err := dag.Quarto().Cli("quarto --version").Sync(ctx)

if err != nil {
return err
}

return nil
return err
}
Loading