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
119 changes: 85 additions & 34 deletions ruff/main.go
Original file line number Diff line number Diff line change
@@ -1,52 +1,103 @@
// A generated module for Ruff functions
// A Dagger module for linting and formatting Python code and Jupyter Notebooks using Ruff.
//
// 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.

// Ruff is an extremely fast Python linter and code formatter written in Rust.
// This module provides functions to run lint checks and formatting validations.
package main

import (
"dagger/ruff/internal/dagger"
)

type Ruff struct{}
const (
defaultImageRepository = "ghcr.io/astral-sh/ruff:latest"
)

// Ruff provides functions for running the Ruff linter and formatter.
type Ruff struct {
// +private
Image string
// +private
Ctr *dagger.Container
}

// New creates a new instance of the Ruff struct.
func New(
// Custom image reference in "repository:tag" format to use as a base container.
// +optional
image string,
) *Ruff {
return &Ruff{
Image: image,
}
}

// container returns the underlying Dagger container, lazily initialized.
func (m *Ruff) container() *dagger.Container {
if m.Ctr != nil {
return m.Ctr
}

image := m.Image
if image == "" {
image = defaultImageRepository
}

m.Ctr = dag.Container().From(image)
return m.Ctr
}

// Check runs ruff check on the target directory.
// It supports standalone checks or using a custom ruff.toml / pyproject.toml configuration.
func (m *Ruff) Check(
// Source directory containing Python files or Jupyter notebooks.
source *dagger.Directory,
// Optional configuration file (ruff.toml or pyproject.toml).
// +optional
config *dagger.File,
) *dagger.Container {
ctr := m.container().
WithMountedDirectory("/work", source).
WithWorkdir("/work")

args := []string{"/ruff", "check"}

if config != nil {
ctr = ctr.WithFile("/etc/ruff.toml", config)
args = append(args, "--config", "/etc/ruff.toml")
}

// CheckWithConfig runs the ruff check command with a configuration file.
return ctr.WithExec(args)
}

// CheckWithConfig runs ruff check with a configuration file.
// Maintained for explicit backwards compatibility.
func (m *Ruff) CheckWithConfig(
// source is an optional argument that specifies a directory.
// Source directory containing Python files or Jupyter notebooks.
source *dagger.Directory,
// file is an optional argument that specifies ruff configuration file.
// Configuration file for ruff.
file *dagger.File,
) *dagger.Container {
return base().
WithMountedDirectory("/tmp", source).
WithWorkdir("/tmp").
WithFile("/.config/.ruff.toml", file).
WithExec([]string{"/ruff", "check", "--config", "/.config/.ruff.toml"})
return m.Check(source, file)
}

// Check runs the ruff check command.
func (m *Ruff) Check(
// source is an optional argument that specifies a directory.
// FormatCheck runs ruff format --check to verify code formatting without modifying files.
func (m *Ruff) FormatCheck(
// Source directory containing Python files or Jupyter notebooks.
source *dagger.Directory,
// Optional configuration file (ruff.toml or pyproject.toml).
// +optional
config *dagger.File,
) *dagger.Container {
return base().
WithMountedDirectory("/tmp", source).
WithWorkdir("/tmp").
WithExec([]string{"/ruff", "check"})
}
ctr := m.container().
WithMountedDirectory("/work", source).
WithWorkdir("/work")

args := []string{"/ruff", "format", "--check"}

if config != nil {
ctr = ctr.WithFile("/etc/ruff.toml", config)
args = append(args, "--config", "/etc/ruff.toml")
}

// base returns the ruff base container
func base() *dagger.Container {
return dag.Container().
From("ghcr.io/astral-sh/ruff:latest")
return ctr.WithExec(args)
}
92 changes: 61 additions & 31 deletions ruff/test/main.go
Original file line number Diff line number Diff line change
@@ -1,65 +1,95 @@
// A generated module for Rufftest 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 Ruff Dagger module.
package main

import (
"context"
"regexp"
"dagger/ruff/test/internal/dagger"
"errors"
"strings"

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

// Rufftest provides test functions for the Ruff module.
type Rufftest struct{}

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

p.Go(m.Check)
p.Go(m.CheckWithConfig)
p.Go(m.CheckWithOptionalConfig)
p.Go(m.FormatCheck)

return p.Wait()
}

// CheckWithConfig runs the ruff check command with a configuration file.
func (m *Rufftest) CheckWithConfig(ctx context.Context) error {
// Check tests that ruff check properly fails on invalid Python files.
func (m *Rufftest) Check(ctx context.Context) error {
dir := dag.CurrentModule().Source().Directory("./testdata")

_, err := dag.Ruff().Check(dir).Stderr(ctx)
if err == nil {
return errors.New("expected ruff check to fail on invalid python testdata, but it succeeded")
}

// Ruff exits with code 1 when lint violations are found
if strings.Contains(err.Error(), "exit code: 1") {
return nil
}

return err
}

// CheckWithOptionalConfig tests calling Check with the new optional config argument.
func (m *Rufftest) CheckWithOptionalConfig(ctx context.Context) error {
dir := dag.CurrentModule().Source().Directory("./testdata")
file := dag.CurrentModule().Source().File("./testdata/.config/.ruff.toml")
_, err := dag.Ruff().CheckWithConfig(dir, file).Stderr(ctx)

if err != nil {
re := regexp.MustCompile("exit code: 1")
if re.MatchString(err.Error()) {
return nil
}
_, err := dag.Ruff().Check(dir, dagger.RuffCheckOpts{
Config: file,
}).Stderr(ctx)
if err == nil {
return errors.New("expected ruff check with optional config to fail, but it succeeded")
}

if strings.Contains(err.Error(), "exit code: 1") {
return nil
}

return err
}

// Check runs the ruff check command.
func (m *Rufftest) Check(ctx context.Context) error {
// CheckWithConfig tests the backwards-compatible CheckWithConfig wrapper function.
func (m *Rufftest) CheckWithConfig(ctx context.Context) error {
dir := dag.CurrentModule().Source().Directory("./testdata")
file := dag.CurrentModule().Source().File("./testdata/.config/.ruff.toml")

_, err := dag.Ruff().CheckWithConfig(dir, file).Stderr(ctx)
if err == nil {
return errors.New("expected ruff check with config to fail, but it succeeded")
}

if strings.Contains(err.Error(), "exit code: 1") {
return nil
}

return err
}

// FormatCheck tests that ruff format --check detects unformatted code in testdata.
func (m *Rufftest) FormatCheck(ctx context.Context) error {
dir := dag.CurrentModule().Source().Directory("./testdata")
_, err := dag.Ruff().Check(dir).Stderr(ctx)

if err != nil {
re := regexp.MustCompile("exit code: 1")
if re.MatchString(err.Error()) {
return nil
}
_, err := dag.Ruff().FormatCheck(dir).Stderr(ctx)
if err == nil {
return errors.New("expected ruff format --check to fail on unformatted testdata, but it succeeded")
}

// Ruff format exits with code 1 if files would be reformatted
if strings.Contains(err.Error(), "exit code: 1") {
return nil
}

return err
Expand Down
6 changes: 6 additions & 0 deletions ruff/test/testdata/unformatted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# ruff/test/testdata/unformatted.py
# This file is intentionally unformatted to test `ruff format --check`
x=1+2
y = 'single quotes should be double quotes'
def bad_spacing( a,b ):
return a+b
Loading