Skip to content

obcode/generate_startercode

Repository files navigation

generate_startercode

Automatic generation of startercode and solution branches from a shared main branch.

This repository is now structured as a uv project, includes tests, and uses GitHub Actions for pre-commit, tests, and automatic releases with Semantic Release.

Installation (uv)

  1. Install uv: https://docs.astral.sh/uv/
  2. Install dependencies:
uv sync --extra dev
  1. Optionally install pre-commit hooks:
uv run pre-commit install --install-hooks

Local Usage

transform.py

uv run python transform.py --target solution --repo-root /path/to/repo
uv run python transform.py --target startercode --repo-root /path/to/repo

sync_issue.py

uv run python sync_issue.py

Note: sync_issue.py expects GitLab environment variables, especially GITLAB_TOKEN, CI_SERVER_URL, and CI_PROJECT_PATH.

Download Latest Release Version

You can download the scripts from the latest GitHub release tag (instead of main):

LATEST_TAG=$(curl -fsSL https://api.github.com/repos/obcode/generate_startercode/releases/latest | python3 -c "import json,sys; print(json.load(sys.stdin)['tag_name'])")
export GENERATE_STARTERCODE_VERSION="${LATEST_TAG#v}"

curl -fsSL "https://raw.githubusercontent.com/obcode/generate_startercode/${LATEST_TAG}/transform.py" -o /tmp/transform.py
curl -fsSL "https://raw.githubusercontent.com/obcode/generate_startercode/${LATEST_TAG}/sync_issue.py" -o /tmp/sync_issue.py

Replacement for Existing GitLab CI Usage

If your GitLab project currently downloads directly from main, replace that with a download from the latest GitHub release tag.

The following .gitlab-ci configuration is a direct replacement for your previous flow:

# -- Sync issue ------------------------------------------------
sync-issue:
  stage: sync
  image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/library/python:3.12-bookworm
  script:
    - set -eu
    - pip install "python-gitlab[graphql]" --quiet
    - LATEST_TAG=$(curl -fsSL https://api.github.com/repos/obcode/generate_startercode/releases/latest | python3 -c "import json,sys; print(json.load(sys.stdin)['tag_name'])")
    - export GENERATE_STARTERCODE_VERSION="${LATEST_TAG#v}"
    - echo "[sync-issue] Using release ${LATEST_TAG}"
    - curl -fsSL "https://raw.githubusercontent.com/obcode/generate_startercode/${LATEST_TAG}/sync_issue.py" -o /tmp/sync_issue.py
    - python /tmp/sync_issue.py
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      changes:
        - Aufgabenstellung/*
        - .gitlab/ci/teacher.yml
        - .gitlab-ci.yml

# -- Generate branches -----------------------------------------
publish-branches:
  stage: publish
  image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/library/python:3.12-bookworm
  before_script:
    - set -eu
    - pip install pyyaml --quiet
    - git config user.email "ci@gitlab"
    - git config user.name "CI"
    - git remote set-url origin "https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
  script:
    - LATEST_TAG=$(curl -fsSL https://api.github.com/repos/obcode/generate_startercode/releases/latest | python3 -c "import json,sys; print(json.load(sys.stdin)['tag_name'])")
    - export GENERATE_STARTERCODE_VERSION="${LATEST_TAG#v}"
    - echo "[publish-branches] Download transform.py from release ${LATEST_TAG}"
    - curl -fsSL "https://raw.githubusercontent.com/obcode/generate_startercode/${LATEST_TAG}/transform.py" -o /tmp/transform.py
    - echo "[publish-branches] Run target=solution"
    - python /tmp/transform.py --target solution --repo-root "$CI_PROJECT_DIR" --config .gitlab/ci/config.yml --no-skip-ci
    - echo "[publish-branches] Run target=startercode"
    - python /tmp/transform.py --target startercode --repo-root "$CI_PROJECT_DIR" --config .gitlab/ci/config.yml --no-skip-ci
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      changes:
        - "src/**/*.py"
        - "tests/**/*.py"
        - "src/**/*.go"
        - .gitlab/ci/config.yml
        - .gitlab/ci/teacher.yml
        - .gitlab-ci.yml

Note: At least one GitHub release must exist. The additional environment variable GENERATE_STARTERCODE_VERSION ensures that the downloaded standalone script reports the correct release version even without a local pyproject. If no release exists yet, initially load once from a fixed tag (for example v1.0.0) or temporarily from main.

Configuration: .gitlab/ci/config.yml

The file .gitlab/ci/config.yml controls what is included per target (solution/startercode) in generated branches. Code transformations (SOLUTION_BEGIN/END markers) are defined directly in source code. This config only controls path removals, file patches, and postprocess commands.

Complete Example

# .gitlab/ci/config.yml

solution:
  remove_paths:
    - Aufgabenstellung
    - .gitlab/ci
  patch_files:
    .gitlab-ci.yml:
      remove_line_containing:
        - ".gitlab/ci/teacher.yml"
        - "include:"
  postprocess_commands:
    - uvx ruff check --select I --fix .
    - uvx ruff format .

startercode:
  remove_paths:
    - Aufgabenstellung
    - .gitlab/ci
  patch_files:
    .gitlab-ci.yml:
      remove_line_containing:
        - ".gitlab/ci/teacher.yml"
        - "include:"
  postprocess_commands:
    - uvx ruff check --select I --fix .
    - uvx ruff format .

remove_paths

List of paths (files or directories) that are fully removed in the generated branch. Paths are relative to the repository root.

solution:
  remove_paths:
    - Aufgabenstellung        # full directory
    - .gitlab/ci              # subdirectory
    - src/secret_tests.py     # single file

patch_files

Allows line-based modifications for selected files in the generated branch. Currently supported operation: remove_line_containing - removes all lines containing the given substring.

solution:
  patch_files:
    .gitlab-ci.yml:
      remove_line_containing:
        - ".gitlab/ci/teacher.yml"   # removes every line containing this string
        - "include:"
    README.md:
      remove_line_containing:
        - "SOLUTION_ONLY"

postprocess_commands

Shell commands executed in the generated tree after all other transformations and before committing the branch. Useful for running formatters or linters after marker removal.

Python/Ruff:

solution:
  postprocess_commands:
    - uvx ruff check --select I --fix .
    - uvx ruff format .

Go:

solution:
  postprocess_commands:
    - find . -name "*.go" -exec goimports -w {} +
    - gofmt -w .

Note: goimports requires go install golang.org/x/tools/cmd/goimports@latest in the CI environment. If you only need formatting, gofmt is enough.

GitHub Actions

CI Workflow

File: .github/workflows/ci.yml

Runs on push to main and on pull requests:

  • pre-commit on all files
  • pytest test suite
  • Semantic Release dry-run on pull requests (--print)
  • Semantic Release on main, but only if pre-commit and tests succeed

Release Workflow

File: .github/workflows/release.yml

Optional manual workflow (workflow_dispatch) for release experiments.

Versioning Without Hardcoded Single Values

Scripts no longer read their version from a manually maintained fixed value. Instead, the version is resolved from package metadata or pyproject.toml. The version in pyproject.toml is managed by Semantic Release.

Tests

Tests are located under tests/ and can be run locally with uv:

uv run pytest

Pre-commit

The existing .pre-commit-config.yaml remains active and is executed in CI.

Run manually:

uv run pre-commit run --all-files

About

Generate Startercode and Solutions on GitLab for my courses

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages