Content DSL
The Content DSL is the core innovation of ReadingBat. It provides an expressive Kotlin DSL for defining programming challenges that are parsed and evaluated at runtime via JSR-223 script engines.
Overview
Content is defined in a Content.kt file using the readingBatContent { } builder function.
The DSL supports three programming languages and can load challenge source files from local
directories or remote GitHub repositories.
Minimal Example
val basicContent =
readingBatContent {
repo = FileSystemSource("./")
java {
group("Warmup-1") {
packageName = "warmup1"
description = "Simple warmup problems to get started"
includeFiles = "*.java"
}
}
}
DSL Hierarchy
The DSL is organized as a four-level hierarchy:
| Level | Class | Purpose |
|---|---|---|
| 1 | ReadingBatContent |
Top-level container; holds all language groups |
| 2 | LanguageGroup<T> |
Groups challenges for a single language (Java, Python, Kotlin) |
| 3 | ChallengeGroup<T> |
A named set of challenges (e.g., "Warmup-1") |
| 4 | Challenge |
An individual programming challenge |
How It Works
- The DSL file is read and evaluated as a Kotlin script
- The resulting
ReadingBatContentobject is validated - Challenge source files are fetched (locally or from GitHub)
- Source code is parsed to extract function bodies and test invocations
- Test invocations are evaluated via script engines to compute correct answers
- The web server presents challenges and checks user answers in real time
Content Sources
Content can be loaded from two types of sources:
Local Filesystem
Use FileSystemSource to load challenges from your local project:
val basicContent =
readingBatContent {
repo = FileSystemSource("./")
java {
group("Warmup-1") {
packageName = "warmup1"
description = "Simple warmup problems to get started"
includeFiles = "*.java"
}
}
}
The FileSystemSource("./") points to the project root directory. Each language group's srcPath
property determines the subdirectory where source files are located.
GitHub Repositories
Use GitHubRepo to load challenges from a GitHub repository:
val gitHubContent =
readingBatContent {
repo = GitHubRepo(Organization, "readingbat", "readingbat-java-content")
branchName = "master"
java {
group("Warmup-1") {
packageName = "warmup1"
description = "Simple warmup problems"
includeFiles = "*.java"
}
}
}
The ownerType parameter distinguishes between GitHub organizations and individual users:
val userRepoContent =
readingBatContent {
repo = GitHubRepo(User, "my-username", "my-challenges")
branchName = "main"
kotlin {
srcPath = "src/main/kotlin"
group("Basics") {
packageName = "basics"
includeFilesWithType = "*.kt" returns IntType
}
}
}
Mixed Sources
Different languages can use different content sources within the same ReadingBatContent:
val mixedSourcesContent =
readingBatContent {
// Default repo for all languages
repo = FileSystemSource("./")
java {
// Java uses the default local repo
group("Warmup-1") {
packageName = "warmup1"
includeFiles = "*.java"
}
}
python {
// Python overrides with its own GitHub repo
repo = GitHubRepo(Organization, "readingbat", "readingbat-python-content")
group("Warmup-1") {
packageName = "warmup1"
includeFilesWithType = "*.py" returns BooleanType
}
}
}
Default Inheritance
The repo and branchName set at the ReadingBatContent level serve as defaults.
Each LanguageGroup can override these with its own values.
DSL Marker
The @ReadingBatDslMarker annotation restricts implicit receiver scope within DSL blocks,
preventing accidental access to outer-level properties. This is standard Kotlin DSL design
that keeps the builder type-safe and unambiguous.
What's Next
- Language Groups -- Configure per-language settings and content sources
- Challenge Groups -- Organize challenges into named sections
- Challenges -- Define individual challenges with descriptions and metadata
- Return Types -- Understand the type system for challenge answers
- Content Inclusion -- Merge content from multiple sources