From 239b9af35bbe85a397f751e4e1b2bf7c9c9cfe2f Mon Sep 17 00:00:00 2001 From: softwaredevelop <61334390+softwaredevelop@users.noreply.github.com> Date: Sun, 20 Sep 2026 09:41:31 +0200 Subject: [PATCH] refactor: ruff --- ruff/main.go | 119 +++++++++++++++++++++--------- ruff/test/main.go | 92 +++++++++++++++-------- ruff/test/testdata/unformatted.py | 6 ++ 3 files changed, 152 insertions(+), 65 deletions(-) create mode 100644 ruff/test/testdata/unformatted.py diff --git a/ruff/main.go b/ruff/main.go index 6199727..a9407ad 100644 --- a/ruff/main.go +++ b/ruff/main.go @@ -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) } diff --git a/ruff/test/main.go b/ruff/test/main.go index 63aa9c8..78834c9 100644 --- a/ruff/test/main.go +++ b/ruff/test/main.go @@ -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 diff --git a/ruff/test/testdata/unformatted.py b/ruff/test/testdata/unformatted.py new file mode 100644 index 0000000..ad627eb --- /dev/null +++ b/ruff/test/testdata/unformatted.py @@ -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