Skip to main content

A lightweight DSL for scaffolding files and folders

Project description

🧱 Bytecraft

A human-readable DSL for scaffolding files and folders.

pip install bytecraft

Bytecraft lets you describe a project structure in plain, readable instructions and execute it with a single command — no Bash, no Python boilerplate, no mental overhead. Designed with data pipelines and scaffold-heavy workflows in mind.

# pipeline.bc

load-vars "pipeline.ebv"

set-working-folder "{{project}}"

for i in 1 to {{num_partitions}}
  make-file "data/partition_{{i:03}}.parquet"
  make-file "schemas/schema_{{i:03}}.json" with "{ \"partition\": {{i}}, \"next\": {{i + 1}} }"
end-for
py -m bytecraft pipeline.bc
[Bytecraft] Loaded 3 variable(s) from: pipeline.ebv
[Bytecraft] Working folder set: my-pipeline
[Bytecraft] Created file: my-pipeline/data/partition_001.parquet
[Bytecraft] Created file: my-pipeline/schemas/schema_001.json
...

Installation

Requires Python 3.10+

pip install bytecraft

Commands

set-working-folder

Sets the base directory for all subsequent relative paths. Created automatically if it doesn't exist.

set-working-folder "my-project"

make-folder

Creates a directory and any missing parent directories. Does nothing if the folder already exists.

make-folder "data/raw"

make-file

Creates a file. Parent directories are created automatically. Overwrites if the file already exists.

# Empty file
make-file "src/__init__.py"

# Inline content
make-file "VERSION" with "1.0.0"

# Multi-line content block
make-file "README.md" with ---
# My Project

Some description here.
---

append-file

Appends content to an existing file. Creates the file if it doesn't exist. A newline is automatically inserted before the new content if the file is non-empty.

make-file "pipeline.log" with "Pipeline started"
append-file "pipeline.log" with "Stage 1 complete"
append-file "pipeline.log" with "Stage 2 complete"

Multi-line blocks work too:

append-file "README.md" with ---

## Changelog

Added in v2.
---

copy-file

Copies a file or folder to a new location. Parent directories are created automatically. If copying a folder that already exists at the destination, it is replaced.

copy-file "src/app.py" to "backup/app.py"
copy-file "src" to "src_backup"

move-file

Moves a file or folder to a new location. Parent directories are created automatically.

move-file "build/output.js" to "dist/app.js"
move-file "temp" to "archive/temp"

delete-file

Deletes a file. Warns if the path doesn't exist or points to a folder.

delete-file "temp.log"
delete-file "{{build_dir}}/old_output.csv"

delete-folder

Deletes a folder and all its contents. Warns if the path doesn't exist or points to a file.

delete-folder "temp"
delete-folder "{{build_dir}}/cache"

make-zip

Creates a zip archive from a file or folder. Folder structure is preserved inside the zip.

make-zip "releases/v1.0.zip" from "dist"
make-zip "releases/data.zip" from "data/processed"

set

Defines a variable. Variables are referenced anywhere using {{name}} syntax — in paths, content, template calls, and loop bodies. Expressions and string operations are evaluated at assignment time.

set project "my-pipeline"
set version "1.0.0"
set label "{{project|upper}}"   # evaluated immediately → MY-PIPELINE

make-file "VERSION" with "{{version}}"
make-file "LABEL" with "{{label}}"

load-vars

Loads variables from an .ebv (External Bytecraft Variables) file. Useful for sharing config across multiple scripts or driving loops from external values.

load-vars "config.ebv"

config.ebv:

# Pipeline config
project = my-pipeline
version = 1.0.0
author = Sourasish Das
env = prod
num_partitions = 24
num_shards = 8

Lines starting with # are ignored. Values do not need quotes.


for

Loops over a list of values or an integer range. The loop variable is available inside the body via {{name}}. Range bounds can be variables. Loops can be nested.

# Value list
for env in "dev" "staging" "prod"
  make-file "config/{{env}}.json" with "{ \"env\": \"{{env}}\" }"
end-for

# Integer range
for i in 1 to 10
  make-file "logs/day_{{i}}.log"
end-for

# Variable range bounds (driven by .ebv)
load-vars "pipeline.ebv"
for i in 1 to {{num_partitions}}
  make-file "data/partition_{{i:03}}.parquet"
end-for

if / else-if / else

Conditionally executes a block. Supports file existence checks and variable comparisons. Supports else-if and else chains. Can be nested inside loops and other if blocks.

# File existence
if exists "data/processed"
  make-folder "data/archive"
end-if

if not exists "dist"
  make-folder "dist"
end-if

# Variable comparison with else-if / else
if "{{env}}" is "prod"
  make-file "config.json" with "{ \"debug\": false, \"strict\": true }"
else-if "{{env}}" is "staging"
  make-file "config.json" with "{ \"debug\": true, \"strict\": true }"
else
  make-file "config.json" with "{ \"debug\": true, \"strict\": false }"
end-if

# Negation
if "{{env}}" is not "dev"
  include "hardening.bc"
end-if

define-template / use-template

Templates let you define reusable scaffolding blocks and stamp them out with different values. Variables passed to use-template are local to that call and do not leak back into the outer script.

define-template "dataset"
  make-folder "data/{{name}}/raw"
  make-folder "data/{{name}}/processed"
  make-file "data/{{name}}/README.md" with "# {{name}} dataset"
end-template

use-template "dataset" name "customers"
use-template "dataset" name "orders"
use-template "dataset" name "products"

include

Runs another .bc file inline with fully shared state. Variables, templates, the working folder, and strict mode all carry across in both directions. Paths are resolved relative to the calling script's directory.

include "base.bc"
include "templates/data-pipeline.bc"

strict on / strict off

In strict mode, warnings become fatal errors — undefined variables, unknown commands, and missing source files all halt execution immediately. Can be toggled on and off within the same script.

strict on

make-file "VERSION" with "{{version}}"   # fine if version is set
make-file "bad.txt" with "{{typo}}"      # ERROR: halts execution

strict off

Comments

Lines starting with # are ignored.

# This is a comment

Expressions

Variables support arithmetic and string operations directly inside {{ }}.

Arithmetic

All four operations are supported. Operands can be variable names or numeric literals.

{{i + 1}}
{{total - 2}}
{{count * 3}}
{{total / 4}}

Arithmetic composes with format specs:

for i in 1 to {{count}}
  make-file "part_{{i:02}}_of_{{count + 0:02}}.csv" with "next={{i + 1}}"
end-for
# → part_01_of_05.csv, part_02_of_05.csv, ...

String operations

String operations use a pipe | syntax.

Operation Syntax Example output
Uppercase {{name|upper}} MY_DATASET
Lowercase {{name|lower}} my_dataset
Trim whitespace {{name|trim}} my_dataset
Replace {{name|replace:_:-}} my-dataset
set name "my_dataset"

make-file "{{name|upper}}.txt"           # MY_DATASET.txt
make-file "{{name|replace:_:-}}.txt"     # my-dataset.txt

String ops work in paths, content, and set values:

set tag "{{name|upper}}"
make-folder "datasets/{{name|replace:_:/}}"   # datasets/my/dataset/

Format specs

Numeric variables and arithmetic results support Python-style format specs using {{var:fmt}}.

Syntax Example output
{{i:02}} 01, 02, ... 10
{{i:03}} 001, 002, ... 100
{{i:>5}} 1 (right-aligned, width 5)

Forgiving Syntax

Bytecraft is intentionally forgiving. Quotes are optional — if they're missing, Bytecraft will recover and interpret your intent:

make-file hello.txt with Hello World

is treated the same as:

make-file "hello.txt" with "Hello World"

Unknown commands print a warning and are skipped rather than crashing the script. Use strict on if you want the opposite behaviour.


Full Example

# Data pipeline scaffold

strict on

load-vars "pipeline.ebv"

set-working-folder "{{project}}"
set project_upper "{{project|upper}}"

# Dataset template
define-template "dataset"
  make-folder "data/{{name}}/raw"
  make-folder "data/{{name}}/processed"
  make-folder "data/{{name}}/archive"
  make-file "data/{{name}}/README.md" with "# {{name|upper}} — {{project}}"
end-template

use-template "dataset" name "customers"
use-template "dataset" name "orders"
use-template "dataset" name "products"

# Generate partitioned output files
for i in 1 to {{num_partitions}}
  make-file "output/partition_{{i:03}}_of_{{num_partitions:03}}.parquet"
end-for

# Per-environment configs
for env in "dev" "staging" "prod"
  if "{{env}}" is "prod"
    make-file "config/{{env}}.json" with "{ \"debug\": false, \"strict\": true }"
  else-if "{{env}}" is "staging"
    make-file "config/{{env}}.json" with "{ \"debug\": true, \"strict\": true }"
  else
    make-file "config/{{env}}.json" with "{ \"debug\": true, \"strict\": false }"
  end-if
end-for

# Build log
make-file "pipeline.log" with "Scaffold started for {{project_upper}}"
append-file "pipeline.log" with "Datasets created"
append-file "pipeline.log" with "{{num_partitions}} partitions generated"
append-file "pipeline.log" with "Configs written"

# Package for release in prod only
if "{{env}}" is "prod"
  copy-file "output" to "dist/output"
  make-zip "releases/{{project}}-{{version}}.zip" from "dist"
  append-file "pipeline.log" with "Release packaged"
end-if

pipeline.ebv:

project = my-pipeline
version = 1.0.0
author = Sourasish Das
env = prod
num_partitions = 24

Roadmap

  • Variables and interpolation
  • Multi-line file content blocks
  • copy-file and move-file
  • make-zip
  • append-file
  • Templates (define-template / use-template)
  • include
  • Strict mode
  • for loops (value lists and ranges)
  • Variable range bounds
  • Zero-padding and format specs ({{i:03}})
  • if / else-if / else
  • .ebv external variable files
  • delete-file and delete-folder
  • Arithmetic expressions ({{i + 1}}, {{count * 2}})
  • String operations ({{name|upper}}, {{name|replace:_:-}})

License

Server-Lab Open-Control License (SOCL) 1.0
Copyright (c) 2025 Sourasish Das

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

bytecraft-0.5.0.tar.gz (18.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bytecraft-0.5.0-py3-none-any.whl (14.6 kB view details)

Uploaded Python 3

File details

Details for the file bytecraft-0.5.0.tar.gz.

File metadata

  • Download URL: bytecraft-0.5.0.tar.gz
  • Upload date:
  • Size: 18.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for bytecraft-0.5.0.tar.gz
Algorithm Hash digest
SHA256 3e68a3eb02cd6b3f7a531e848e22deb7e0984a2888a79a795739f2840e2cef0d
MD5 f138908c1d975cb95f11b5a768080e82
BLAKE2b-256 51270d96b4fada1662c991f5eb31f3b8824a33a6a387950a636a0df18627ee0f

See more details on using hashes here.

File details

Details for the file bytecraft-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: bytecraft-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 14.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for bytecraft-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bd1cf14bf11ca7e0b41a178794a440d39e49ba19d8cab7fed05b6324114a4415
MD5 1cacbf05f48cbfeae13da215d56cbaa6
BLAKE2b-256 cc09a5d0a38a752608d121a328ff5b05b3c1408999ee2d08530cdb0ff6ae514a

See more details on using hashes here.

Supported by

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