Skip to main content

run dev scripts

Project description

ds: run dev scripts

ds
Dash the Sprinter

Build PyPI Supported Python Versions

Why?

I often need to run scripts to build my code, run my server, lint my files, etc. Every project seems to use a different tool (e.g., make, ant, npm, pnpm, pdm), yet the basic commands I use in each project are largely the same. So I was inspired to build a very simple task runner.

  • Minimal magic: Designed to use a familiar syntax and a few clear rules where possible. Not trying to recreate make.
  • Low surprise: checks for cycles and raises helpful error messages when things are wrong.
  • Works with existing files: including package.json, pyproject.toml, and Cargo.toml files.
  • Run multiple tasks: [npm|yarn|pnpm|bun|pdm] run can only run one task at a time.
  • Minimal dependencies: working on removing all of these (see #31)
    • python (3.8+)
    • tomli (for python < 3.11)
    • graphlib_backport (for python < 3.9)

Install

python -m pip install ds-run

# or, if you use uv:
uv pip install ds-run

Example

Create a ds.toml file in the top-level of your project or you can also put this configuration in package.json, pyproject.toml, or Cargo.toml to reduce cruft.

# Example: Basic `ds` configuration.

[scripts]
clean = "rm -rf build/"
build = "mkdir $@" # pass arguments
all = ["clean", "build -p build"] # a composite task

Now you can list the tasks with ds --list or just ds:

# Found 3 tasks in ds.toml

clean:
rm -rf build/

build:
mkdir $@

all:
['-clean', 'build -p build']

Run the tasks.

ds clean
# => rm -rf build/

ds build: some-folder
# => mkdir some-folder

ds all
# => rm -rf build/
# => mkdir -p build

Read more:

Usage

Usage: ds [--help | --version] [--debug]
          [--cwd PATH] [--file PATH]
          [--list | (<task>[: <options>... --])...]

Options:
  -h, --help
    Show this message and exit.

  --version
    Show program version and exit.

  --debug
    Show debug messages.

  --cwd PATH
    Set the working directory (default: task file parent).

  -f PATH, --file PATH
    File with task definitions (default: search in parents).

  -l, --list
    List available tasks and exit.

  <task>[: <options>... --]
    One or more tasks to run with task-specific arguments.
    Use a colon (`:`) to indicate start of arguments and
    double-dash (`--`) to indicate the end.

    If the first <option> starts with a hyphen (`-`), you may omit the
    colon (`:`). If there are no more tasks after the last option, you
    may omit the double-dash (`--`).

Configuration File

If you don't provide a config file using the --file flag, ds will search the current directory and all of its parents for files with these names in the following order:

  • ds.toml
  • .ds.toml
  • package.json
  • pyproject.toml
  • Cargo.toml

The first file found will be used.

ds currently supports json and toml files. It searches for the following keys in the following order:

  • scripts for ds.toml, .ds.toml, package.json
  • tool.ds.scripts for pyproject.toml
  • tool.pdm.scripts for pyproject.toml
  • package.metadata.scripts for Cargo.toml

The first key that is found is used and should be a mapping of task names to commands.

Where should I put my config?

To avoid making lots of top-level files, ds tries to use common project configuration files.

  • Node: package.json under scripts (see Lifecycle Events Not Supported)
  • Python: pyproject.toml under [tool.ds.scripts]
  • Rust: Cargo.toml under [package.metadata.scripts]
  • Other: ds.toml under [scripts]

Task Names

  • Task names are strings, that are usually short and all lowercase.
  • They can have a colon (:) in them, like py:build, or other punctuation, like py.build.
  • If the name starts with a hash (#) it is ignored. This comes from package.json in which its comment to add "comments" as JSON keys.
  • Don't start a task name with a hyphen (-); it usually indicates Error Suppression.
  • Don't end a task name with a colon (:); we use this to indicate Command-line Arguments

Commands

ds ultimately converts all commands into strings to be executed with subprocess.run.

Basic Command

# Example: Basic commands become strings.

[scripts]
ls = "ls -lah"
no_error = "-exit 1" # See "Error Suppression"

# We also support `pdm`-style commands.
# The following all produce the same command as `ls` above.
ls2 = { cmd = "ls -lah" }
ls3 = { cmd = ["ls", "-lah"] }
ls4 = { shell = "ls -lah" }

A basic command is just a string of what should be executed in a shell.

Composite Command

# Example: Composite commands call other tasks or shell commands.

[scripts]
build = "touch build/$1"
clean = "rm -rf build"
all = ["clean", "-mkdir build", "build foo", "build bar", "echo 'Done'"]

# We also support pdm-style composite commands.
# The following is equivalent to `all`.
all2 = { composite = [
  "clean",
  "-mkdir build",
  "build foo",
  "build bar",
  "echo 'Done'",
] }

A composite command consists of a series of steps where each step is the name of another task or a shell command.

Argument Interpolation

Commands can include parameters like $1 and $2 to indicate that the command accepts arguments. You can also use $@ for the "remaining" arguments (i.e. those you haven't yet interpolated yet).

Arguments from a composite command precede those from the command-line.

Command-line Arguments

When calling ds you can specify additional arguments to pass to commands.

ds build: foo -- build: bar

This would run the build task first with the argument foo and next with the argument bar.

A few things to note:

  • the colon (:) after the task name indicates the start of arguments
  • the double dash (--) indicates the end of arguments

If the first argument to the task starts with a hyphen, the colon can be omitted. If there are no more arguments, you can omit the double dash.

If you're not passing arguments, you can put tasks names next to each other:

ds clean test

Error Suppression

If a command starts with a hyphen (-), the leading hyphen is removed before the command is executed and the command will always produce an error code of 0 (i.e. it will always be considered to have completed successfully).

This is particularly useful in composite commands where you want subsequent steps to continue even if a particular step fails. For example:

# Example: Error suppression lets a command continue even if it fails.

[scripts]
cspell = "cspell --gitignore '**/*.{py,txt,md,markdown}'"
format = "ruff format ."
die = "-exit 1" # returns error code of 0
lint = ["-cspell", "format"] # run format even if cspell finds some misspelled words

Error suppression is currently only available in configuration files and not on the command-line API (see #22).

Lifecycle Events Not Supported

Some task runners support running additional pre- and post- tasks. However, this is currently not supported (see #24). Part of the reason is that it obscures the relationship between tasks and creates surprise if you have to have two tasks with unfortunate names.

As more explicit alternative is to use composite commands to clearly describe the relationship between a task and its pre- and post- tasks.

Inspirations

I've used several task runners, usually as part of build tools. Below is a list of tools used or read about when building ds.

  • 1976: make (C) - Together with its descendants, make is one of the most popular build & task running tools. It is fairly easy to make syntax errors and the tab-based indent drives me up the wall.

  • 2000: ant (Java) - an XML-based replacement for make. I actually liked using ant quite a bit until I stopped writing Java and didn't want to have java as a dependency for my python projects.

  • 2008: gradle (Groovy/Kotlin) - Written for the jvm, I pretty much only use this for Android development. Can't say I love it.

  • 2010: npm (JavaScript) - Being able to add a simple scripts field to package.json made it very easy to run dev scripts. Supports pre and post lifecycle tasks.

  • 2010: pdm (Python) - Supports 4 different types of tasks including cmd, shell, call, and composite.

  • 2016: yarn (JavaScript) - An alternative to npm which also supports command-line arguments.

  • 2016: pnpm (JavaScript) - Another alternative to npm which supports many more options including running tasks in parallel.

  • 2016: just (Rust) - Defines tasks in a justfile, similar to make. Supports detecting cycles, running parallel, and many other options.

  • 2016: cargo-run-script (Rust) - Uses Cargo.toml to configure scripts and supports argument substitution ($1, $2, etc.).

  • 2017: cargo-make (Rust) - Very extensive port of make to Rust defining tasks in Makefile.toml.

  • 2022: hatch (Python) - Defines environment-specific scripts with the ability to suppress errors, like make.

  • 2023: bun (Zig) - An alternative to node and npm.

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ds_run-0.1.3.tar.gz (19.1 kB view hashes)

Uploaded Source

Built Distribution

ds_run-0.1.3-py3-none-any.whl (12.2 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page