diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..d131c49 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,59 @@ +# === BUILDER STAGE: Install fonts with fontist === +FROM ruby:slim AS fonts-builder + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +# Install fontist and required fonts +RUN gem install fontist --no-document + +COPY fontist-manifest.yml . + +RUN fontist update --quiet \ + && fontist manifest-install --quiet fontist-manifest.yml \ + && fontist cache clear --quiet \ + && chmod -R 644 /root/.fontist/fonts + +# === FINAL STAGE: Alpine with copied fonts === +FROM alpine:3.21 + +RUN apk add --no-cache \ + bash \ + git \ + pandoc \ + typst \ + shellcheck \ + just \ + poppler-utils \ + python3 \ + uv \ + pre-commit \ + nodejs \ + npm \ + fontconfig \ + font-awesome \ + font-awesome-free \ + font-awesome-brands \ + && rm -rf /var/cache/apk/* + +# Copy all fonts from the builder stage at once +COPY --from=fonts-builder /root/.fontist/fonts/ /usr/share/fonts/fontist/ + +ENV TYPST_FONT_PATHS=/usr/share/fonts + +# Update font cache +RUN fc-cache -fv + +# --- Setup Local Typst Package Symlink for Development --- +# Define the target package path using the standard ENV var name +ENV TYPST_PACKAGE_PATH=/usr/local/share/typst/packages +ENV DEV_PKG_DIR=$TYPST_PACKAGE_PATH/local/pandoc-cv/0.1.0 +# Create the directory structure +RUN mkdir -p $DEV_PKG_DIR +# Create symlinks from the workspace files to the package directory +# This assumes the devcontainer mounts the workspace at /workspaces/typstCV +# Use ln -sf to force link creation/overwrite if it exists +RUN ln -sf /workspaces/typstCV/style.typ $DEV_PKG_DIR/style.typ \ + && ln -sf /workspaces/typstCV/typst.toml $DEV_PKG_DIR/typst.toml diff --git a/.devcontainer/Dockerfile.ubuntu b/.devcontainer/Dockerfile.ubuntu new file mode 100644 index 0000000..88db411 --- /dev/null +++ b/.devcontainer/Dockerfile.ubuntu @@ -0,0 +1,103 @@ +# Use a Microsoft base image with common dev tools pre-installed +FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 + +# Install system dependencies using apt-get +# Need: git, pandoc, typst (manual?), shellcheck, just (manual?), poppler-utils, +# python3, pipx (for uv, pre-commit), nodejs, npm, fontconfig, ruby, build-essential (for fontist) +USER root + +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get install -y --no-install-recommends \ + git \ + file \ + pandoc \ + shellcheck \ + poppler-utils \ + python3 \ + python3-pip \ + pipx \ + pre-commit \ + nodejs \ + npm \ + fontconfig \ + ruby \ + ruby-dev \ + build-essential \ + wget \ + ca-certificates \ + unzip \ + just \ + bats \ + bats-assert \ + bats-support \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Just is installed via apt-get above + +# --- Install Font Awesome 6 --- +RUN export FA_VERSION=6.7.2 \ + && export FA_DIR=/usr/local/share/fonts/fontawesome6 \ + && mkdir -p ${FA_DIR} \ + && wget -qO /tmp/fontawesome.zip https://github.com/FortAwesome/Font-Awesome/releases/download/${FA_VERSION}/fontawesome-free-${FA_VERSION}-desktop.zip \ + && unzip -q /tmp/fontawesome.zip -d /tmp/fontawesome-extract 'fontawesome-free-*-desktop/otfs/*' \ + && mv /tmp/fontawesome-extract/fontawesome-free-*-desktop/otfs/*.otf ${FA_DIR}/ \ + && fc-cache -f -v \ + && rm -rf /tmp/fontawesome.zip /tmp/fontawesome-extract + +# Install Typst manually (using musl build might work, or check for glibc build) +# Let's try the musl build first as it's known +ARG TYPST_VERSION=v0.12.0 +ARG TARGET=x86_64-unknown-linux-musl +RUN wget -qO typst.tar.xz https://github.com/typst/typst/releases/download/${TYPST_VERSION}/typst-${TARGET}.tar.xz \ + && tar xf typst.tar.xz \ + && mv typst-${TARGET}/typst /usr/local/bin/typst \ + && chmod +x /usr/local/bin/typst \ + && rm -rf typst.tar.xz typst-${TARGET} \ + && typst --version + +# Install fontist +RUN gem install fontist --no-document --version "~> 1.21.1" + +# --- Setup Pandoc Symlinks for Development/Testing --- +# Create standard pandoc directories and symlink workspace files into them +# This makes the devcontainer environment match the production container's setup +# where these files are copied into standard locations. +# Needs to run as root before switching to vscode user. +ENV PANDOC_DATA_DIR=/usr/share/pandoc +RUN mkdir -p $PANDOC_DATA_DIR/filters && \ + mkdir -p $PANDOC_DATA_DIR/templates && \ + ln -sf /workspaces/typstCV/linkify.lua $PANDOC_DATA_DIR/filters/linkify.lua && \ + ln -sf /workspaces/typstCV/typst-cv.lua $PANDOC_DATA_DIR/filters/typst-cv.lua && \ + ln -sf /workspaces/typstCV/typst-cv.typ $PANDOC_DATA_DIR/templates/typst-cv.typ && \ + ln -sf /workspaces/typstCV/typst-letter.typ $PANDOC_DATA_DIR/templates/typst-letter.typ + +# Switch back to vscode user provided by base image +USER vscode + +# --- Setup Local Typst Package Symlink for Development/Testing --- +# Define the target package path using the standard ENV var name +# Use the user's local share directory now +ENV TYPST_PACKAGE_PATH=/home/vscode/.local/share/typst/packages +ENV DEV_PKG_DIR=$TYPST_PACKAGE_PATH/local/pandoc-cv/0.1.0 +# Create the directory structure and symlinks during build +# These point to the workspace location, which will be mounted at runtime +RUN mkdir -p $DEV_PKG_DIR && \ + ln -sf /workspaces/typstCV/style.typ $DEV_PKG_DIR/style.typ && \ + ln -sf /workspaces/typstCV/typst.toml $DEV_PKG_DIR/typst.toml + +# Install fonts using fontist (run as vscode user) +COPY --chown=vscode:vscode fontist-manifest.yml /tmp/fontist-manifest.yml +RUN fontist update --quiet \ + && fontist manifest-install --quiet /tmp/fontist-manifest.yml \ + && fontist cache clear --quiet \ + && rm /tmp/fontist-manifest.yml \ + && sudo fc-cache -fv # Update system font cache + +# Set Font Path (includes fontist user dir and system FA6 dir) +ENV TYPST_FONT_PATHS=/home/vscode/.fontist/fonts:/usr/local/share/fonts/fontawesome6 + +# Ensure pipx path is in PATH +ENV PATH="/home/vscode/.local/bin:${PATH}" + +RUN mkdir -p /home/vscode/.vscode-server/data/User/globalStorage diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 07c2e71..932b76b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,18 +1,21 @@ { - "name": "typst", + "name": "${localWorkspaceFolderBasename}-devcontainer-ubuntu", "build": { - "dockerfile": "../Dockerfile" + "dockerfile": "./Dockerfile.ubuntu", + "context": ".." }, + "runArgs": [ + "--env-file", + ".devcontainer/devcontainer.env" + ], "features": { "ghcr.io/devcontainers/features/common-utils:2": { - "installZsh": "true", - "username": "vscode", - "userUid": "1000", - "userGid": "1000", - "upgradePackages": "true" + "username": "vscode" }, - "ghcr.io/kadykov/devcontainer-features/pre-commit:1": {}, - "ghcr.io/lukewiwa/features/shellcheck:0": {} + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "version": "latest", + "moby": true + } }, "postCreateCommand": "pre-commit install", "remoteUser": "vscode", @@ -22,12 +25,19 @@ "ms-azuretools.vscode-docker", "myriad-dreamin.tinymist", "skellock.just", - "ritwickdey.LiveServer", "GitHub.vscode-github-actions", - "eamodio.gitlens", "sumneko.lua", - "yinfei.luahelper" + "yinfei.luahelper", + "saoudrizwan.claude-dev", + "jetmartin.bats" ] } - } + }, + "mounts": [ + { + "source": "${localWorkspaceFolderBasename}-vscode-global-storage", + "target": "/home/vscode/.vscode-server/data/User/globalStorage", + "type": "volume" + } + ] } diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ebba0a2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +# Git related +.git +.gitignore +.gitmodules + +# Development and CI/CD +.devcontainer +.github +.vscode +.pre-commit-config.yaml +.yamlfmt.yml +justfile + +# Tests (not needed in production image) +tests/ + +# Examples (built separately if needed, not part of base image) +examples/ + +# Documentation / Metadata +CHANGELOG.md +README.md +LICENSE +memory-bank/ + +# Temporary / Output files +*.pdf +temp.* + +# Environment files (keep example) +.env diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..26da543 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,34 @@ +--- +# Basic Dependabot configuration file + +version: 2 +updates: + # Maintain dependencies for Docker + - package-ecosystem: "docker" + directory: "/" # Location of Dockerfile + schedule: + interval: "weekly" + day: "sunday" # Check on Sundays + # Add reviewers, assignees, labels as needed + # reviewers: + # - "octocat" + # assignees: + # - "octocat" + # labels: + # - "dependencies" + # - "docker" + + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" # Location of workflow files + schedule: + interval: "weekly" + day: "sunday" # Check on Sundays + # Add reviewers, assignees, labels as needed + # reviewers: + # - "octocat" + # assignees: + # - "octocat" + # labels: + # - "dependencies" + # - "github_actions" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43c9db4..f18ff19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,9 +11,10 @@ on: env: IMAGE: ${{ vars.DOCKERHUB_USERNAME }}/${{ vars.DOCKERHUB_REPOSITORY }} IMAGE_TAG_TESTING: ${{ vars.DOCKERHUB_USERNAME }}/${{ vars.DOCKERHUB_REPOSITORY }}:testing - OUTPUT_DIR: public OUTPUT_ARTIFACTS_NAME: pdf - SOURCE_ARTIFACTS_NAME: source + EXAMPLE_ARTIFACTS_NAME: examples + TEST_DIR: tests # Directory containing test scripts and fixtures + # DEV_IMAGE_NAME will be set dynamically in the job jobs: pre_commit: @@ -28,32 +29,27 @@ jobs: - uses: pre-commit-ci/lite-action@v1.0.3 if: always() - source: - name: Extract example source files - needs: pre_commit - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Upload source files - uses: actions/upload-artifact@v4 - with: - name: ${{ env.SOURCE_ARTIFACTS_NAME }} - path: | - ./kadykov-*.md - ./photo.jpg - ./justfile - if-no-files-found: error + # The 'source' job previously here is removed as it's no longer needed. docker: - name: Build and push testing Docker image - needs: source + name: Build, test, and push Docker image + needs: pre_commit # Now only depends on pre_commit runs-on: ubuntu-latest + permissions: + contents: read # Needed for checkout + packages: write # Needed to push to GHCR steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to Docker Hub + - name: Login to GitHub Container Registry # For devcontainer cache + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Docker Hub # For production image testing cache pull uses: docker/login-action@v3 with: username: ${{ vars.DOCKERHUB_USERNAME }} @@ -64,29 +60,84 @@ jobs: with: load: true tags: ${{ env.IMAGE_TAG_TESTING }} - cache-from: type=registry,ref=${{ env.IMAGE }}:${{ github.ref_name }} + cache-from: type=registry,ref=${{ env.IMAGE }}:testing # Cache from previous testing tag if exists cache-to: type=inline - - name: Download source files - uses: actions/download-artifact@v4 - with: - name: ${{ env.SOURCE_ARTIFACTS_NAME }} + - name: Checkout code + uses: actions/checkout@v4 + # Submodules are no longer used for testing dependencies + # submodules: recursive # Keep commented out as reference if needed - - name: Render with testing Docker image - run: > - docker container run - -v "${PWD}:/data" - --user "$(id -u):$(id -g)" - ${{ env.IMAGE_TAG_TESTING }} + - name: Set Devcontainer Image Name Env Var + run: echo "DEV_IMAGE_NAME=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')/devcontainer" >> "$GITHUB_ENV" + shell: bash - - name: Upload rendered PDFs + - name: Devcontainer Docker meta # For devcontainer cache image + id: devmeta # Ensure this ID is unique + uses: docker/metadata-action@v5 + with: + images: ${{ env.DEV_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=raw,value=latest + type=ref,event=pr + + - name: Build and Cache Devcontainer Image + id: build_devcontainer + uses: docker/build-push-action@v6 + with: + context: . + file: .devcontainer/Dockerfile.ubuntu + tags: ${{ steps.devmeta.outputs.tags }} + push: true # Always push cache to GHCR + cache-from: type=registry,ref=${{ env.DEV_IMAGE_NAME }}:buildcache + cache-to: type=registry,ref=${{ env.DEV_IMAGE_NAME }}:buildcache,mode=max + + - name: Install Bats, Helpers, and File command (for Docker usage tests on host) + run: sudo apt-get update && sudo apt-get install -y bats bats-support bats-assert file + + # The explicit 'docker pull' step previously here is removed as it's redundant. + + - name: Run internal tests (unit, filter, e2e) inside Devcontainer Image + run: | + echo "Running internal tests..." + # Run using the pulled devcontainer image from GHCR, mounting the full workspace + docker run --rm -v "${PWD}:/workspaces/typstCV" --workdir /workspaces/typstCV ${{ env.DEV_IMAGE_NAME }}:latest just test-internal + echo "Internal tests finished." + + - name: Run Docker usage tests (on runner host against production image) + run: | + echo "Running Docker usage tests..." + # These tests run on the host, interacting with the built testing image + # Assumes 'bats' is installed on the runner (installed in previous step) + # Assumes docker is available on the runner + # Pass the correct image tag to the test script via env var + DOCKER_IMAGE_TAG=${{ env.IMAGE_TAG_TESTING }} bats ${{ env.TEST_DIR }}/docker.bats + echo "Docker usage tests finished." + + - name: Build Example PDFs for Release Artifacts (using production image, mounting only fixtures) + run: | + echo "Building example PDFs..." + # Create host output directory first + mkdir -p ./examples + # Mount fixtures to /test-fixtures, mount host output dir to /output + # Pass arguments directly to the ENTRYPOINT (build.sh) + docker run --rm -v "${PWD}/${{ env.TEST_DIR }}/fixtures:/test-fixtures:ro" -v "${PWD}/examples:/output" --workdir /data ${{ env.IMAGE_TAG_TESTING }} \ + --output-dir /output /test-fixtures/example-cv.md + docker run --rm -v "${PWD}/${{ env.TEST_DIR }}/fixtures:/test-fixtures:ro" -v "${PWD}/examples:/output" --workdir /data ${{ env.IMAGE_TAG_TESTING }} \ + --output-dir /output /test-fixtures/example-letter.md --type letter + echo "Example PDFs built." + + - name: Upload Example PDFs uses: actions/upload-artifact@v4 with: - name: ${{ env.OUTPUT_ARTIFACTS_NAME }} - path: ./*.pdf + name: ${{ env.OUTPUT_ARTIFACTS_NAME }} # Use the same name expected by release job + path: ./examples/*.pdf if-no-files-found: error - - name: Login to Docker Hub + # Only push final images if tests passed and it's a push to main or a tag + - name: Login to Docker Hub (for push) + if: success() && (github.event_name == 'push') # Only push on push events uses: docker/login-action@v3 with: username: ${{ vars.DOCKERHUB_USERNAME }} @@ -102,44 +153,47 @@ jobs: type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} + type=ref,event=pr - - name: Build and push + - name: Build and push # Production image to Docker Hub + if: success() && (github.event_name == 'push') # Only push on push events uses: docker/build-push-action@v6 with: - push: true + push: true # This step only runs on push events anyway due to the 'if' above tags: ${{ steps.meta.outputs.tags }} - cache-from: type=registry,ref=${{ env.IMAGE }}:${{ github.ref_name }} + cache-from: type=registry,ref=${{ env.IMAGE }}:testing # Use testing cache cache-to: type=inline - publish: - name: Publish to GitHub pages - needs: docker + # The duplicate devmeta block was here and is now removed by not including it in the REPLACE section. + + scan-image: + name: Scan Docker image with Trivy + needs: docker # Run after the image is built and tested runs-on: ubuntu-latest permissions: - contents: write - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + contents: read # Needed for checkout + security-events: write # Needed for reporting findings (optional) steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 - - name: Download rendered PDFs - uses: actions/download-artifact@v4 + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master # Use master for latest features/fixes with: - name: ${{ env.OUTPUT_ARTIFACTS_NAME }} - path: ${{ env.OUTPUT_DIR }}/ - - - name: Add HTML for GitHub Pages - run: cp ./*.html ${{ env.OUTPUT_DIR }}/ + image-ref: ${{ env.IMAGE_TAG_TESTING }} # Scan the testing image + format: 'table' + exit-code: '1' # Fail the workflow if vulnerabilities are found + ignore-unfixed: true # Don't fail on vulnerabilities without known fixes + vuln-type: 'os,library' # Scan OS packages and language-specific packages + severity: 'HIGH,CRITICAL' # Fail only on HIGH or CRITICAL severity vulnerabilities + # Optional: Upload results to GitHub Security tab + # github-pat: ${{ secrets.GITHUB_TOKEN }} # Requires security-events: write permission - - name: Deploy CV to GitHub pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./${{ env.OUTPUT_DIR }} + # Removed the 'publish' job for GitHub Pages CV deployment release: name: Create a GitHub release - needs: docker + needs: scan-image # Now depends on the scan job passing runs-on: ubuntu-latest if: ${{ startsWith(github.ref, 'refs/tags/') }} permissions: @@ -153,11 +207,11 @@ jobs: run: echo "current_version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" shell: bash - - name: Download rendered PDFs + - name: Download Example PDFs uses: actions/download-artifact@v4 with: - name: ${{ env.OUTPUT_ARTIFACTS_NAME }} - path: ${{ env.OUTPUT_DIR }}/ + name: ${{ env.OUTPUT_ARTIFACTS_NAME }} # Artifact containing example-cv.pdf, example-letter.pdf + path: ./release-assets/ # Download to a specific directory - name: Get Changelog Entry id: changelog_reader @@ -170,6 +224,7 @@ jobs: - name: Create release on GitHub uses: ncipollo/release-action@v1 with: + # Ensure no duplicate keys exist below tag: ${{ steps.changelog_reader.outputs.version }} name: Release ${{ steps.changelog_reader.outputs.version }} body: ${{ steps.changelog_reader.outputs.changes }} @@ -177,4 +232,4 @@ jobs: draft: ${{ steps.changelog_reader.outputs.status == 'unreleased' }} allowUpdates: true token: ${{ secrets.GITHUB_TOKEN }} - artifacts: ${{ env.OUTPUT_DIR }}/*.pdf + artifacts: ./release-assets/*.pdf diff --git a/.gitignore b/.gitignore index 8b19f98..824b37c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pdf .env temp.* +devcontainer.env diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 179a789..dc4c45b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,11 +25,16 @@ repos: # requires `shellcheck` to work properly # https://github.com/rhysd/actionlint/issues/477 - repo: https://github.com/rhysd/actionlint - rev: v1.7.4 + rev: v1.7.7 hooks: - id: actionlint - repo: https://github.com/google/yamlfmt - rev: v0.14.0 + rev: v0.16.0 hooks: - id: yamlfmt + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck diff --git a/Dockerfile b/Dockerfile index bc31160..127cbaf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,52 +1,94 @@ -ARG FEDORA_VERSION=41 -ARG ALPINE_VERSION=3.20 -ARG TYPST_FONTAWESOME_VERSION=0.5.0 - -FROM alpine:${ALPINE_VERSION} AS builder +# === BUILDER STAGE: Install fonts with fontist === +FROM ruby:3.2-slim AS fonts-builder +RUN apt-get update && apt-get install -y --no-install-recommends \ + git build-essential \ + && rm -rf /var/lib/apt/lists/* +RUN gem install fontist --no-document --version "~> 1.21.1" +COPY fontist-manifest.yml . +RUN fontist update --quiet \ + && fontist manifest-install --quiet fontist-manifest.yml \ + && fontist cache clear --quiet \ + && chmod -R 644 /root/.fontist/fonts +# === BUILDER STAGE: Install Typst and packages === +FROM alpine:3.21 AS typst-builder +# Install dependencies needed for download (wget), extraction (tar with xz support), and git (for packages) +RUN apk add --no-cache wget tar xz git +# Install Typst v0.12.0 manually ARG TYPST_VERSION=v0.12.0 +ARG TARGET=x86_64-unknown-linux-musl +RUN wget -qO typst.tar.xz https://github.com/typst/typst/releases/download/${TYPST_VERSION}/typst-${TARGET}.tar.xz \ + && tar xf typst.tar.xz \ + && mv typst-${TARGET}/typst /usr/local/bin/typst \ + && chmod +x /usr/local/bin/typst \ + && rm -rf typst.tar.xz typst-${TARGET} \ + && typst --version +# Download and extract typst-fontawesome v0.5.0 manually +ARG TYPST_FONTAWESOME_VERSION=0.5.0 +ENV TYPST_PACKAGE_PATH=/root/.local/share/typst/packages +RUN mkdir -p $TYPST_PACKAGE_PATH/preview/fontawesome/${TYPST_FONTAWESOME_VERSION}/ \ + && wget -qO typst-fontawesome.tar.gz https://packages.typst.org/preview/fontawesome-${TYPST_FONTAWESOME_VERSION}.tar.gz \ + && tar xf typst-fontawesome.tar.gz -C $TYPST_PACKAGE_PATH/preview/fontawesome/${TYPST_FONTAWESOME_VERSION}/ \ + && rm typst-fontawesome.tar.gz -RUN wget -qO typst.tar.xz https://github.com/typst/typst/releases/download/${TYPST_VERSION}/typst-x86_64-unknown-linux-musl.tar.xz \ - && tar xf typst.tar.xz - -ARG TYPST_FONTAWESOME_VERSION -RUN wget -qO typst-fontawesome.tar.gz https://packages.typst.org/preview/fontawesome-${TYPST_FONTAWESOME_VERSION}.tar.gz \ - && mkdir /fontawesome \ - && tar xf typst-fontawesome.tar.gz -C /fontawesome - -FROM fedora:${FEDORA_VERSION} +# === FINAL STAGE: Alpine Production Image === +FROM alpine:3.21 -ARG PANDOC_VERSION=3.1.11.1 -ARG JUST_VERSION=1.35.0 -ARG FIRA_SANS_VERSION=4.202 -ARG IBM_PLEX_VERSION=6.4.0 -ARG FONTAWESOME_VERSION=6.6.0 -RUN dnf update -yq \ - && dnf install -yq \ - just-${JUST_VERSION} \ - pandoc-${PANDOC_VERSION} \ - mozilla-fira-sans-fonts-${FIRA_SANS_VERSION} \ - ibm-plex-serif-fonts-${IBM_PLEX_VERSION} \ - fontawesome-6-brands-fonts-${FONTAWESOME_VERSION} \ - fontawesome-6-free-fonts-${FONTAWESOME_VERSION} \ - && dnf clean all +# Install runtime dependencies: bash, pandoc, fontconfig, font-awesome +# Git is NOT needed here anymore as packages are copied from builder +# wget/tar are NOT needed here anymore as Typst is copied from builder +RUN apk add --no-cache \ + bash \ + pandoc \ + fontconfig \ + font-awesome \ + font-awesome-free \ + font-awesome-brands \ + && rm -rf /var/cache/apk/* -ENV PANDOC_DATA_DIR=/usr/share/pandoc-${PANDOC_VERSION}/ +# --- Copy Assets from Builders --- +# Copy Typst binary +COPY --from=typst-builder /usr/local/bin/typst /usr/local/bin/typst +# Copy Typst packages (fontawesome) +COPY --from=typst-builder /root/.local/share/typst/packages /usr/share/typst/packages +# Copy fonts +COPY --from=fonts-builder /root/.fontist/fonts/ /usr/share/fonts/fontist/ -COPY typst-cv.typ typst-letter.typ ${PANDOC_DATA_DIR}/data/templates/ -COPY *.lua ${PANDOC_DATA_DIR}/filters/ +# --- Configure Environment --- +# Define paths +ENV PANDOC_DATA_DIR=/usr/share/pandoc +ENV TYPST_PACKAGE_PATH=/usr/share/typst/packages +# Reset font paths to only include font directories +ENV TYPST_FONT_PATHS=/usr/share/fonts -COPY --from=builder typst-x86_64-unknown-linux-musl/typst /usr/bin/typst +# Create necessary directories (including the local package structure) +RUN mkdir -p $PANDOC_DATA_DIR/filters \ + $PANDOC_DATA_DIR/templates \ + $TYPST_PACKAGE_PATH/local/pandoc-cv/0.1.0 \ + /data -ARG PANDOC_CV_VERSION=0.1.0 -ENV TYPST_PACKAGE_PATH=/usr/local/share/typst/packages/ -COPY style.typ typst.toml ${TYPST_PACKAGE_PATH}/local/pandoc-cv/${PANDOC_CV_VERSION}/ +# Update font cache AFTER copying fonts and setting path +RUN fc-cache -fv -ARG TYPST_FONTAWESOME_VERSION -COPY --from=builder /fontawesome/*.typ /fontawesome/typst.toml ${TYPST_PACKAGE_PATH}/preview/fontawesome/${TYPST_FONTAWESOME_VERSION}/ +# --- Copy Project Files --- +COPY build.sh /usr/local/bin/build.sh +COPY linkify.lua $PANDOC_DATA_DIR/filters/linkify.lua +COPY typst-cv.lua $PANDOC_DATA_DIR/filters/typst-cv.lua +COPY typst-cv.typ $PANDOC_DATA_DIR/templates/typst-cv.typ +COPY typst-letter.typ $PANDOC_DATA_DIR/templates/typst-letter.typ +# Copy style.typ and typst.toml to the local package directory +COPY style.typ $TYPST_PACKAGE_PATH/local/pandoc-cv/0.1.0/style.typ +COPY typst.toml $TYPST_PACKAGE_PATH/local/pandoc-cv/0.1.0/typst.toml -ENV TYPST_FONT_PATHS=/usr/share/fonts/ +# Ensure build.sh is executable +RUN chmod +x /usr/local/bin/build.sh +# --- Final Setup --- +# Set working directory WORKDIR /data -ENTRYPOINT [ "just", "build" ] +# Set entrypoint +ENTRYPOINT ["build.sh"] + +# Default command (optional, can be overridden) +CMD ["--help"] diff --git a/README.md b/README.md index 3f6cff4..2e167fd 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,79 @@ # Create CVs and Cover Letters in Markdown and Render to PDF with Typst and Pandoc -[![Build Status](https://github.com/kadykov/typstCV/actions/workflows/ci.yml/badge.svg)](https://github.com/kadykov/typstCV/actions/workflows/ci.yml) - ## Overview This project allows you to write your CV and cover letter in Markdown and generate PDFs using Typst and Pandoc. -It supports both **public** and **private** versions -(includes private details like phone numbers). +It provides a flexible command-line interface and Docker support for easy integration. --- -## Getting Started +## Usage -1. Clone the repository and open it as a development container. -2. Use the following commands for rendering: - - `just build`: Generates the **public version** (excludes private details like phone numbers). - - `just build-private`: Generates the **private version** (includes private details). +The primary way to use this tool is via the `build.sh` script or the provided Docker image. ---- +### Command-Line Interface (CLI) + +```bash +./build.sh [--type ] [--output ] [--output-dir ] [--set KEY=VALUE]... +``` + +**Arguments & Options:** + +- ``: (Required) Path to the input Markdown file, or `-` to read from stdin. +- `--type `: (Optional) Explicitly set the document type. If omitted, it attempts to infer from the input filename (containing "cv" or "letter"). Defaults to `cv` if inference fails. +- `--output `: (Optional) Specify the output PDF file path, or `-` to write to stdout. If omitted, defaults to `.pdf` in the output directory. *Required if reading from stdin and not outputting to stdout.* +- `--output-dir `: (Optional) Specify the directory for the output PDF file. Defaults to the current directory (`.`). The directory will be created if it doesn't exist. +- `--set KEY=VALUE`: (Optional, repeatable) Override metadata values. The `KEY` is converted to lowercase and passed to Typst. See [Metadata Overrides](#metadata-overrides). + +**Examples:** + +```bash +# Build a CV from a file, output to the same directory +./build.sh my-cv.md + +# Build a letter, specifying type and output directory +./build.sh my-letter.md --type letter --output-dir ./output + +# Build a CV, overriding email and adding phone number, output to specific file +./build.sh my-cv.md --set EMAIL=private@email.com --set PHONE="123 456 7890" --output my-private-cv.pdf + +# Build from stdin, output to stdout, overriding author +cat my-cv.md | ./build.sh - --set AUTHOR="Another Name" --output - > another-cv.pdf +``` + +### Docker Usage + +A Docker image containing all dependencies is available. + +1. **Pull the image from Docker Hub:** + ```bash + docker pull kadykov/typst-cv + ``` + *(Or build locally: `docker build -t kadykov/typst-cv .`)* + +2. **Run the container:** Mount your project directory (containing your `.md` files) to `/data` inside the container and pass arguments to `build.sh`. + + ```bash + docker run --rm -v "$(pwd):/data" kadykov/typst-cv [OPTIONS...] + ``` -## Generating Private Versions +**Docker Examples:** -By default, running `just build` creates a public version that omits sensitive details. -To create private versions: +```bash +# Build my-cv.md inside the container, output PDF appears in current host directory +docker run --rm -v "$(pwd):/data" kadykov/typst-cv my-cv.md -1. Rename `.env.example` to `.env`. -2. Enter your private details (phone number and email) in the `.env` file. -3. Run `just build-private` to render the private version. +# Build my-letter.md, output to ./output directory on host +docker run --rm -v "$(pwd):/data" kadykov/typst-cv my-letter.md --output-dir output + +# Build with overrides using environment variables (prefixed with TYPSTCV_) +docker run --rm -v "$(pwd):/data" \ + -e TYPSTCV_EMAIL="private@email.com" \ + -e TYPSTCV_PHONE="123 456 7890" \ + kadykov/typst-cv my-cv.md --output my-private-cv.pdf +``` --- @@ -41,18 +86,20 @@ Metadata and special features are defined in the YAML header. Include the following fields in the YAML header for your CV or cover letter: -**For both CV and cover letter:** +**Common Metadata Fields:** + +These fields are typically defined in the YAML frontmatter of your Markdown file. They can be overridden using the methods described in [Metadata Overrides](#metadata-overrides). -- `author`: Your name. -- `title`: Job title or position. -- `public-email`: Publicly available email address. -- `github`, `gitlab`, `linkedin`: Your usernames on these platforms. -- `website`: Your website URL (without the protocol). -- `date`: Custom creation date (format: `year: YYYY, month: M, day: D`). -- `keywords`: Keywords for PDF metadata. -- `links`: Dictionary of keywords and URLs to auto-convert keywords into hyperlinks. +- `author`: Your name. +- `title`: Document title (e.g., "Research Engineer" for a CV, "Letter to..." for a cover letter). +- `email`: Your primary email address. +- `github`, `gitlab`, `linkedin`: Your usernames on these platforms. +- `website`: Your website URL (e.g., `www.kadykov.com`). +- `date`: Custom creation date (format: `year: YYYY, month: M, day: D`). Defaults to today if omitted. +- `keywords`: List of keywords for PDF metadata. +- `links`: Dictionary mapping keywords to URLs for automatic hyperlinking in the text (see [Features](#features)). -**Additional fields for cover letters:** +**Additional Fields for Cover Letters:** - `to`: Recipient's address. - `from`: Your address. @@ -61,47 +108,82 @@ Include the following fields in the YAML header for your CV or cover letter: ## Features and Usage -### Adding Hyperlinks to Keywords +## Metadata Overrides -A Pandoc filter (`linkify.lua`) converts keywords in your document into hyperlinks. -To enable: +You can override values defined in the YAML frontmatter without editing the Markdown file using two methods. This is useful for including private information (like a phone number) or customizing documents for specific applications. -1. Add a `links` dictionary in the YAML metadata. -2. Define keywords and their corresponding URLs. See [CV example](#cv-example) +1. **Environment Variables:** Set environment variables prefixed with `TYPSTCV_`. The suffix will be used as the lowercase key for the override. + ```bash + export TYPSTCV_EMAIL="private@email.com" + export TYPSTCV_PHONE="+1 123 456 7890" + ./build.sh my-cv.md + # Or with Docker: + docker run --rm -v "$(pwd):/data" -e TYPSTCV_EMAIL="private@email.com" typst-cv-generator my-cv.md + ``` -The commands `just build` and `just build-private` apply this filter by default. +2. **`--set` Argument:** Use the `--set KEY=VALUE` argument when running `build.sh`. The `KEY` is converted to lowercase. + ```bash + ./build.sh my-cv.md --set email=private@email.com --set phone="+1 123 456 7890" --set title="Senior Engineer Application" + ``` -### Right-Side Content in CVs +**Priority & Workflow:** Values from `--set` arguments and `TYPSTCV_` environment variables take precedence over values defined in the YAML frontmatter. The Typst template checks for these overrides first. The recommended workflow is to keep only public, shareable information in your committed Markdown/YAML file and use overrides to supply private details (like phone numbers) or make temporary modifications for specific builds. -You can display content -(e.g., profile picture, location, dates) -on the right side of your CV. -To enable add metadata (`key="value"`) -to the heading you want to associate with side content. +**Note:** Fields like `phone` are typically *only* provided via overrides, as they are usually considered private information not suitable for version control. -**Supported keys:** +--- -- `photo`: Typst block with your profile image. -- `location`: Typst block with location details. -- `date`: Typst block with dates. +## Features and Usage + +### Automatic Hyperlinks + +A Pandoc filter (`linkify.lua`) automatically converts keywords found in your document text into hyperlinks if they are defined in the `links` dictionary in the YAML metadata. + +**Example YAML:** +```yaml +links: + TDS: https://en.wikipedia.org/wiki/Terahertz_time-domain_spectroscopy + THz: https://en.wikipedia.org/wiki/Terahertz_radiation +``` +Any occurrence of "TDS" or "THz" in the Markdown body will be linked accordingly. + +### Right-Side Content (CVs) + +You can display content (e.g., profile picture, location, dates) on the right side of your CV headings using Markdown attributes. The `typst-cv.lua` filter translates these attributes into specific Typst layout functions. + +**Recommended Structure & Supported Attributes:** + +- **`# Level 1 Heading {photo='...'}`:** Typically used for the main title/name with a profile photo. The value should be a Typst `image()` call (e.g., `{photo='image("./placeholder-photo.png", width: 120pt)'}`). +- **`### Level 3 Heading {location="..."}`:** Recommended for Company or Institution names. The value is the location text. +- **`#### Level 4 Heading {date="..."}`:** Recommended for Position or Degree titles. The value is the date range text. + +**Important Notes:** + +- The filter processes only the *first* supported attribute (`photo`, `location`, or `date`) it finds on a heading. Do not combine them on the same heading. +- Use `\\` for line breaks within attribute values if needed (e.g., `{location="City \\ Country"}`). Pandoc requires double backslashes for a literal newline in Typst output. **Example:** ```markdown -## Multitel ASBL {location="Mons \\ Belgium"} +# Jane Doe {photo='image("./placeholder-photo.png", width: 120pt)'} -_Non-profit innovation center_ +## Experience {.hidden} -### Research Engineer {date="Jul.~2021 \\ Aug.~2024"} +### Example Corp {location="Remote"} +*Leading provider of innovative examples* -Developed a THz time-domain spectroscopy (THz-TDS) data pipeline with an improved signal-to-noise ratio using sensitivity profile-shaped filtering. -``` +#### Senior Example Engineer {date="2022 - Present"} +- Developed key features... -**Note:** Use `\\` for line breaks in Typst blocks (Pandoc strips single `\`). +### Another Company Inc. {location="Example City"} +*Pioneers in fictional services* + +#### Example Intern {date="Summer 2021"} +- Assisted the team... +``` #### Known Issue -Side content automatically starts a new paragraph after the heading. To avoid extra spacing, structure your content accordingly. +Side content might visually appear slightly detached or start a new paragraph relative to the heading text depending on Typst's layout decisions. Structure your content accordingly. --- @@ -137,49 +219,45 @@ and they automatically span the full width of the document. ### CV Example -Below is a minimal example of a CV in Markdown. -For a detailed example, see [`kadykov-cv-en.md`](kadykov-cv-en.md). -Rendered PDFs are [hosted](http://github.kadykov.com/typstCV/) on GitHub Pages. +Below is a minimal example of a CV in Markdown, demonstrating the recommended structure. +For a runnable example, see [`tests/fixtures/example-cv.md`](tests/fixtures/example-cv.md). ```markdown --- -author: Aleksandr KADYKOV -title: Research Engineer -public-email: cv@kadykov.com -github: kadykov -gitlab: kadykov -linkedin: aleksandr-kadykov -website: www.kadykov.com -keywords: - - résumé - - resume - - CV +author: Jane Doe +title: Curriculum Vitae +email: jane.doe@example.com +github: janedoe +linkedin: janedoe +website: example.com +# YAML contains public data. Private data (e.g., phone) is added via overrides. links: - TDS: https://en.wikipedia.org/wiki/Terahertz_time-domain_spectroscopy - THz: https://en.wikipedia.org/wiki/Terahertz_radiation - + website: https://example.com + Typst: https://typst.app/ + Pandoc: https://pandoc.org/ --- -# Research Engineer {photo='image("./photo.jpg", width: 120pt)'} +# Jane Doe {photo='image("./placeholder-photo.png", width: 120pt)'} -Headline of your CV +A highly motivated professional... Check my website. -# Core Competencies {.hidden} +## Experience {.hidden} -- Data analysis & presentation -- Experimental design & execution -- Instrumentation integration & orchestration -- Scientific Python development +### Example Corp {location="Remote"} +*Leading provider of innovative examples* -# Professional Experience {.hidden} +#### Senior Example Engineer {date="2022 - Present"} +- Developed key features... ---- +## Education {.hidden} -## Multitel ASBL {location="Mons \\ Belgium"} +### University of Example {location="Example City, EX"} -_Company description._ +#### M.S. in Computer Science {date="2020 - 2022"} +*Thesis: Advanced Topics in Placeholders* -### Research Engineer {date="Jul.~2021 \\ Aug.~2024"} +## Skills {.hidden} -Your explanation of what you have done. +- Programming: Python, JavaScript, Typst, Shell Scripting +- Tools: Git, Docker, Pandoc ``` diff --git a/action.yml b/action.yml deleted file mode 100644 index 8a3d789..0000000 --- a/action.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: 'Typst CV' -description: 'Render PDF files with Typst' -inputs: - output-folder: - description: 'Output folder' - required: false - default: 'public' -runs: - using: 'docker' - image: 'Dockerfile' - entrypoint: './entrypoint.sh' - env: - OUTPUT_FOLDER: ${{ inputs.output-folder }} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..f741f25 --- /dev/null +++ b/build.sh @@ -0,0 +1,260 @@ +#!/bin/sh +set -e + +# --- Default values --- +doc_type="" # cv or letter, inferred if empty +output_file="" +output_dir="." +input_file="" +# output_format="pdf" # Reverted: Removed output_format +typst_input_args="" +use_override_pipeline=false + +# --- Helper Functions --- +usage() { + # Reverted: Removed --output-format from usage + echo "Usage: $0 [--type ] [--output ] [--output-dir ] [--set KEY=VALUE]..." + echo "" + echo " : Path to input Markdown file, or '-' for stdin." + echo " --type : Explicitly set document type (default: inferred from input filename, fallback 'cv')." + echo " --output : Output PDF file path, or '-' for stdout (default: based on input filename)." + echo " --output-dir : Directory for output file (default: '.'). Created if it doesn't exist." + # echo " --output-format : Format for the output file (default: 'pdf')." + echo " --set KEY=VALUE : Override metadata. KEY is converted to lowercase for Typst." + echo "" + echo "Overrides can also be provided via environment variables prefixed with TYPSTCV_" + echo "(e.g., TYPSTCV_EMAIL=me@private.com)." + exit 1 +} + +# --- Argument Parsing --- +while [ $# -gt 0 ]; do + case "$1" in + -) # Handle stdin explicitly + if [ -n "$input_file" ]; then echo "Error: Only one input file allowed ('-' or filename)." >&2; usage; fi + input_file="-" + shift + ;; + --type) + if [ -z "$2" ]; then echo "Error: --type requires an argument." >&2; usage; fi + if [ "$2" != "cv" ] && [ "$2" != "letter" ]; then echo "Error: --type must be 'cv' or 'letter'." >&2; usage; fi + doc_type="$2" + shift 2 + ;; + --output) + if [ -z "$2" ]; then echo "Error: --output requires an argument." >&2; usage; fi + output_file="$2" + shift 2 + ;; + --output-dir) + if [ -z "$2" ]; then echo "Error: --output-dir requires an argument." >&2; usage; fi + output_dir="$2" + shift 2 + ;; + # Reverted: Removed --output-format parsing + # --output-format) + # if [ -z "$2" ]; then echo "Error: --output-format requires an argument." >&2; usage; fi + # if [ "$2" != "pdf" ] && [ "$2" != "typst" ]; then echo "Error: --output-format must be 'pdf' or 'typst'." >&2; usage; fi + # output_format="$2" + # shift 2 + # ;; + --set) + if [ -z "$2" ]; then echo "Error: --set requires an argument (KEY=VALUE)." >&2; usage; fi + key=$(echo "$2" | cut -d= -f1 | tr '[:upper:]' '[:lower:]') + value=$(echo "$2" | cut -d= -f2-) + if [ -z "$key" ] || [ -z "$value" ]; then echo "Error: Invalid --set format. Use KEY=VALUE." >&2; usage; fi + # shellcheck disable=SC2089 + typst_input_args="$typst_input_args --input $key=\"$value\"" + use_override_pipeline=true + shift 2 + ;; + --) # End of options + shift + break + ;; + -*) # Unknown option + echo "Error: Unknown option $1" >&2 + usage + ;; + *) # Input file + if [ -n "$input_file" ]; then echo "Error: Only one input file allowed." >&2; usage; fi + input_file="$1" + shift + ;; + esac +done + +# --- Input Validation --- +if [ -z "$input_file" ]; then + echo "Error: Input file or '-' (for stdin) is required." >&2 + usage +fi + +input_arg="$input_file" +if [ "$input_file" != "-" ] && [ ! -f "$input_file" ]; then + echo "Error: Input file not found: $input_file" >&2 + exit 1 +fi + +# --- Detect Environment Variable Overrides --- +for var in $(env | grep '^TYPSTCV_'); do + key=$(echo "$var" | cut -d= -f1 | sed 's/^TYPSTCV_//' | tr '[:upper:]' '[:lower:]') + value=$(echo "$var" | cut -d= -f2-) + if [ -n "$key" ] && [ -n "$value" ]; then + typst_input_args="$typst_input_args --input $key=\"$value\"" + use_override_pipeline=true + fi +done + +# --- Determine Document Type --- +if [ -z "$doc_type" ]; then + if echo "$input_file" | grep -q -i "cv"; then + doc_type="cv" + elif echo "$input_file" | grep -q -i "letter"; then + doc_type="letter" + else + echo "Warning: Could not infer document type from filename '$input_file'. Defaulting to 'cv'." >&2 + doc_type="cv" + fi +fi +# Use relative template filename +template_file="typst-${doc_type}.typ" +echo "Info: Using document type: $doc_type (template: $template_file)" >&2 + +# Force override pipeline for CVs to handle potential photo paths correctly via input_dir_abs +if [ "$doc_type" = "cv" ]; then + echo "Info: Forcing override pipeline for CV to handle potential relative image paths." >&2 + use_override_pipeline=true +fi + +# --- Determine Output Path --- +# Reverted: Simplified output path logic +output_arg="" # Will be '-o path' or '-' for stdout + +if [ "$output_file" = "-" ]; then + echo "Info: Outputting PDF to stdout." >&2 + output_arg="-" + # PDF to stdout requires the typst compile step, even if no other overrides exist + use_override_pipeline=true +elif [ -n "$output_file" ]; then + # User specified exact output file path relative to output_dir + output_path="${output_dir}/${output_file}" + output_arg="-o $output_path" +else + # Default output filename based on input (only if input is not stdin) + if [ "$input_file" != "-" ]; then + base_name=$(basename "$input_file" .md) + output_path="${output_dir}/${base_name}.pdf" # Always .pdf now + output_arg="-o $output_path" + # If input is stdin, output MUST be stdout ('-') + elif [ "$input_file" = "-" ] && [ "$output_file" != "-" ]; then + # This case is only reached if input is '-' and output was NOT specified as '-' OR was empty + echo "Error: Output must be explicitly specified as stdout ('--output -') when reading from stdin ('-')." >&2 + usage + fi + # If input is stdin and output is stdout, output_arg remains "-" + # If input is file and output is file, output_arg is "-o path" +fi + +# Create output directory if needed and not outputting to stdout +if [ "$output_arg" != "-" ]; then + mkdir -p "$(dirname "$output_path")" + echo "Info: Output PDF path: $output_path" >&2 +fi + +# Reverted: Removed adjustment logic based on output_format + + +# --- Build Commands --- +# Check if PANDOC_DATA_DIR is set, provide default if not (useful if run outside Docker) +# Ensure PANDOC_DATA_DIR is exported so subshells (like in pipes) might see it, though direct arg is better. +export PANDOC_DATA_DIR="${PANDOC_DATA_DIR:-/usr/share/pandoc}" +# Determine resource path (input file's directory or '.' for stdin) +resource_path="." +if [ "$input_file" != "-" ]; then + # Handle potential edge case where input file is in root (dirname returns '.') + dir=$(dirname "$input_file") + if [ "$dir" != "." ]; then + resource_path="$dir" + fi +fi +# Add --data-dir to the base command - resource path added per-command +# Use relative Lua filter names +# shellcheck disable=SC2089 +pandoc_base="pandoc --data-dir $PANDOC_DATA_DIR --wrap=preserve --pdf-engine=typst --lua-filter=linkify.lua --lua-filter=typst-cv.lua" + +# --- Calculate Absolute Input Directory for Typst --- +input_dir_abs="" +if [ "$input_file" != "-" ]; then + # Get absolute path of the input file + # Handle relative paths by prepending PWD if needed + case "$input_file" in + /*) input_file_abs="$input_file" ;; + *) input_file_abs="$PWD/$input_file" ;; + esac + # Get the directory part + input_dir_abs=$(dirname "$input_file_abs") + # Add to typst args if not empty (should always be set if input is a file) + if [ -n "$input_dir_abs" ]; then + # shellcheck disable=SC2089 + typst_input_args="$typst_input_args --input input_dir_abs=\"$input_dir_abs\"" + # Note: This variable is ONLY used by the override pipeline's typst compile step. + # The direct pandoc pipeline cannot use it. + fi +fi + + +# --- Execute Pipeline --- +# Reverted: Simplified execution logic back to original override check +echo "Info: Starting build..." >&2 +if [ "$use_override_pipeline" = true ]; then + echo "Info: Using override pipeline (Pandoc -> Typst -> PDF)." >&2 + # Construct the pandoc part of the pipe + pandoc_cmd_part="$pandoc_base --to=typst --template=$template_file $input_arg" + + # Prepare typst output argument. + # Typst compile takes the output path directly as the last argument, + # or '-' for stdout. It does NOT use '-o'. + typst_output_arg="" + if [ "$output_arg" = "-" ]; then + typst_output_arg="-" # Pass '-' for stdout + elif echo "$output_arg" | grep -q -e '^-o '; then + # Extract path after '-o ' for the argument + typst_output_arg=$(echo "$output_arg" | sed 's/^-o //') + fi + + # Determine the root directory for Typst. Use absolute path if available, else default to /data for stdin. + typst_root_dir="/data" # Default for stdin + if [ -n "$input_dir_abs" ]; then + typst_root_dir="$input_dir_abs" + fi + + # Execute pandoc part directly and pipe to typst compile + # Add --root argument. Pass calculated output argument ($typst_output_arg) directly to typst. + # shellcheck disable=SC2086,SC2090 # We want word splitting for $typst_input_args; quotes are handled there. $typst_output_arg is single path or '-'. + pandoc_pipe_cmd="$pandoc_cmd_part | typst compile --root \"$typst_root_dir\" $typst_input_args - $typst_output_arg" + + # Execute the combined pipeline command using sh -c for potentially better handling of pipes/quotes. + sh -c "$pandoc_pipe_cmd" + # Check exit status of the pipe (specifically the last command, typst compile) + pipe_status=$? + if [ $pipe_status -ne 0 ]; then + echo "Error: Typst compilation failed with status $pipe_status." >&2 + exit $pipe_status + fi + +else + echo "Info: Using direct pipeline (Pandoc -> PDF)." >&2 + # Construct the full pandoc command parts, adding resource path here + # shellcheck disable=SC2089 + pandoc_cmd_part1="$pandoc_base --resource-path \"$resource_path\" --template=$template_file" + pandoc_cmd_part2="$input_arg" + # $output_arg contains '-o path' or '-' for stdout + # Execute directly, let shell handle splitting of $output_arg + # shellcheck disable=SC2086,SC2090 # We want word splitting; quotes in var are for target cmd + $pandoc_cmd_part1 "$pandoc_cmd_part2" $output_arg +fi + + +echo "Info: Build finished." >&2 +exit 0 diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index 43b6457..0000000 --- a/entrypoint.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -l - -set -e - -# Render default PDF files -just build - -# Use the correct environment variable for the input -output_folder="${OUTPUT_FOLDER}" - -# Ensure the output folder exists -mkdir -p "$output_folder" - -# Copy generated files to the output folder -cp *.pdf *.html "$output_folder/" - -# List files in the output folder and print them -files=$(ls "$output_folder/") -echo -e "Files in the $output_folder folder:\n$files" diff --git a/fontist-manifest.yml b/fontist-manifest.yml new file mode 100644 index 0000000..5db8c25 --- /dev/null +++ b/fontist-manifest.yml @@ -0,0 +1,8 @@ +--- +IBM Plex Serif: + - Regular + - Italic +IBM Plex Serif SemiBold: + - Regular +Fira Sans Medium: + - Regular diff --git a/justfile b/justfile index 532662d..564898d 100644 --- a/justfile +++ b/justfile @@ -1,72 +1,52 @@ -set dotenv-load -filename := "kadykov" -cv := "cv" -letter := "letter" -english := "en" -french := "fr" -output-dir := "." -typst := "typst compile" -pandoc := "pandoc --data-dir=$PANDOC_DATA_DIR --wrap=preserve --pdf-engine=typst --lua-filter=linkify.lua --lua-filter=typst-cv.lua" -pandoc-to-typst := "--to=typst | typst compile -" -private-args := '--input EMAIL="$EMAIL" --input PHONE="$PHONE"' +# Justfile for common development tasks +# Variables +bats_executable := "bats" # Use system-installed bats +unit_tests_file := "tests/unit/build_sh.bats" +filter_tests_file := "tests/filter/filters.bats" +e2e_tests_file := "tests/test_e2e.bats" # Changed from script to file -build: - just english - just french +# Default task +default: + @just --list -build-private: - just english-private - just french-private +# Run all linters/formatters +lint: + pre-commit run --all-files -english: - mkdir -p {{output-dir}} - {{pandoc}} \ - {{filename}}-{{cv}}-{{english}}.md \ - -o {{output-dir}}/{{filename}}-{{cv}}-{{english}}.pdf \ - --template=typst-{{cv}}.typ - {{pandoc}} \ - {{filename}}-{{letter}}-{{english}}.md \ - -o {{output-dir}}/{{filename}}-{{letter}}-{{english}}.pdf \ - --template=typst-{{letter}}.typ +# Run unit tests +test-unit: + {{bats_executable}} {{unit_tests_file}} -english-private: - mkdir -p {{output-dir}} - {{pandoc}} \ - {{filename}}-{{cv}}-{{english}}.md \ - --template=typst-{{cv}}.typ \ - {{pandoc-to-typst}} \ - {{output-dir}}/{{filename}}-{{cv}}-{{english}}.pdf \ - {{private-args}} - {{pandoc}} \ - {{filename}}-{{letter}}-{{english}}.md \ - --template=typst-{{letter}}.typ \ - {{pandoc-to-typst}} \ - {{output-dir}}/{{filename}}-{{letter}}-{{english}}.pdf \ - {{private-args}} +# Run filter tests (Pandoc Lua filters) +test-filter: + {{bats_executable}} {{filter_tests_file}} # Use bats now -french: - mkdir -p {{output-dir}} - {{pandoc}} \ - {{filename}}-{{cv}}-{{french}}.md \ - -o {{output-dir}}/{{filename}}-{{cv}}-{{french}}.pdf \ - --template=typst-{{cv}}.typ - {{pandoc}} \ - {{filename}}-{{letter}}-{{french}}.md \ - -o {{output-dir}}/{{filename}}-{{letter}}-{{french}}.pdf \ - --template=typst-{{letter}}.typ +# Run E2E smoke tests (PDF generation) +test-e2e: + {{bats_executable}} {{e2e_tests_file}} # Use bats now -french-private: - mkdir -p {{output-dir}} - {{pandoc}} \ - {{filename}}-{{cv}}-{{french}}.md \ - --template=typst-{{cv}}.typ \ - {{pandoc-to-typst}} \ - {{output-dir}}/{{filename}}-{{cv}}-{{french}}.pdf \ - {{private-args}} - {{pandoc}} \ - {{filename}}-{{letter}}-{{french}}.md \ - --template=typst-{{letter}}.typ \ - {{pandoc-to-typst}} \ - {{output-dir}}/{{filename}}-{{letter}}-{{french}}.pdf \ - {{private-args}} +# Run Docker usage tests (requires image built, e.g., via `just build-docker`) +test-docker: + {{bats_executable}} tests/docker.bats + +# Run all tests (including Docker tests) +test: test-internal test-docker + @echo "All tests passed!" + +# Run internal tests (unit, filter, e2e) - suitable for CI container execution +test-internal: test-unit test-filter test-e2e + +# Clean temporary files (if any - currently handled by tests) +clean: + @echo "No specific clean actions defined." + +# Build example files (useful for quick checks) +build-examples: + ./build.sh --output-dir examples tests/fixtures/example-cv.md + ./build.sh --output-dir examples tests/fixtures/example-letter.md --type letter + @echo "Example PDFs built in ./examples/" + +# Build the default Docker image +build-docker tag='latest': + docker build --pull -t typst-cv:{{tag}} -f Dockerfile . diff --git a/kadykov-cv-en.md b/kadykov-cv-en.md deleted file mode 100644 index 0e8c97f..0000000 --- a/kadykov-cv-en.md +++ /dev/null @@ -1,471 +0,0 @@ ---- -author: Aleksandr KADYKOV -title: Research Engineer -public-email: cv@kadykov.com -github: kadykov -gitlab: kadykov -linkedin: aleksandr-kadykov -website: www.kadykov.com -date: "year: 1970, month: 1, day: 1" -keywords: - - résumé - - resume - - CV - - Curriculum vitae - - Research Software Engineer - - Research Engineer - - Software Engineer - - THz - - terahertz - - THz-TDS - - spectroscopy - - cryogenics - - optics - - photonics - - measurements - - data analysis - - programming - - DevOps - - CI/CD - - TDD - - Test-Driven Development - - Python - - Jupyter - - NumPy - - Pandas - - Xarray - - Scipy - - Python array API - - scikit-learn - - PyTorch - - MATLAB - - Matplotlib - - hvPlot - - Plotly - - Bokeh - - Panel - - holoviz - - OriginPro - - PyMeasure - - Bluesky - - yaq - - LabVIEW - - Intake - - SQL - - Quarto - - Typst - - Pandoc - - LaTeX - - VSCode - - Git - - Linux - - Docker - - Docker-compose - - Zotero -links: - Multitel: https://www.multitel.eu/expertise/applied-photonics/terahertz-spectroscopy-and-imaging/ - ASBL: https://fr.wikipedia.org/wiki/Association_sans_but_lucratif - SAPHIRE: https://www.multitel.eu/projects/saphire/ - LNE: https://www.lne.fr/en/research-and-development - EPIC: https://en.wikipedia.org/wiki/%C3%89tablissement_public_%C3%A0_caract%C3%A8re_industriel_et_commercial - I2S: https://edi2s.umontpellier.fr/ - L2C: https://coulomb.umontpellier.fr/?lang=en - IPM: http://www.ipmras.ru/en/institute/scientific-departments/department-110/ - RAS: https://en.wikipedia.org/wiki/Russian_Academy_of_Sciences - TDS: https://en.wikipedia.org/wiki/Terahertz_time-domain_spectroscopy - THz: https://en.wikipedia.org/wiki/Terahertz_radiation - hBN: https://en.wikipedia.org/wiki/Boron_nitride - HgCdTe: https://en.wikipedia.org/wiki/Mercury_cadmium_telluride - graphene: https://en.wikipedia.org/wiki/Graphene - helium: https://en.wikipedia.org/wiki/Liquid_helium - Python: https://github.com/search?q=language%3APython+author%3Akadykov&type=pullrequests - wavelength: https://doi.org/10.1063/1.4996966 - FTIR: https://en.wikipedia.org/wiki/Fourier-transform_infrared_spectroscopy - FAIR: https://en.wikipedia.org/wiki/FAIR_data - Mons: https://www.openstreetmap.org/#map=19/50.45756/3.92540 - Trappes: https://www.openstreetmap.org/#map=17/48.76090/1.98370 - Nizhny: https://www.openstreetmap.org/#map=17/56.29878/43.97990 - Novgorod: https://www.openstreetmap.org/#map=17/56.29878/43.97990 - Thesis: https://www.theses.fr/en/2017MONTS086 ---- - -# Research Engineer {photo='image("./photo.jpg", width: 120pt)'} - -Proven ability to -design and execute experiments, -analyse and present data, -develop scientific Python software. - -Strong background in -applied and basic research -in THz photonics -and -magnetotransport -in 2D~materials. - -# Core competencies {.hidden} - -- Data analysis & presentation -- Experimental design & execution -- Instrumentation integration & orchestration -- Scientific Python development - -# Professional experience {.hidden} - ---- - -## Multitel ASBL {location="Mons \\ Belgium"} - -_Non-profit -innovation center specializing in -applied photonics, AI, etc._ - -### Research Engineer in THz Spectroscopy and Imaging {date="Jul.~2021 \\ Aug.~2024"} - -Developed -a THz time-domain spectroscopy (THz-TDS) -data pipeline -with an improved signal-to-noise ratio -by utilizing sensitivity profile-shaped filtering. - -Developed -a computationally cheap -THz-TDS data processing method -for refractive index and thickness extraction -in low-absorption materials. - -Streamlined -refractive index profile reconstruction -from THz-TDS data -by offloading calculations -to a GPU -and utilizing backpropagation-based -optimization algorithms. - - - -Automated laboratory workflows -by implementing Python tools -for measurement orchestration, -data management, -analysis, -and result presentation. - -Ensured best software development practices -by implementing unit testing, -CI/CD pipelines, -and documentation. - - - - - ---- - -## Laboratoire National de Métrologie et d'Essais {location="Trappes \\ France"} - -_French National Laboratory of Metrology and Testing (LNE)_ - -### Research Engineer in Quantum Hall Effect Metrology {date="Sep.~2018 \\ Sep.~2020"} - -Led low-noise -cryogenic -magnetotransport measurements -on graphene, -exploring its potential -as a resistance standard. - -Designed -a flexible Python software -package, - -optimizing scientific equipment orchestration. - -Participated -in the nanofabrication -of hBN-encapsulated graphene stacks. - -Improved performance -of a helium gas recuperation system. - ---- - -## Institute for Physics of Microstructures (IPM RAS) {location="Nizhny Novgorod \\ Russia"} - -_State-owned research institute -specializing in solid state physics._ - -### Research Engineer in Photonics of 2D Narrow-Gap Heterostructures {date="May~2017 \\ Sep.~2018"} - -Led -photoluminescence and photoconductivity -FTIR cryogenic measurements -of HgTe/HgCdTe quantum wells. - -Achieved -laser emission -in HgCdTe heterostructures at -a record wavelength. - ---- - -# Education {.hidden} - -## Laboratoire Charles Coulomb (L2C) & IPM RAS {location="Montpellier, France \\ Nizhny Novgorod, Russia"} - -I2S Doctorlal School -at the University of Montpellier -& L2C - -### Ph.D.~in Solid State Physics {date="Sep.~2014 \\ Dec.~2017"} - -Thesis: -[ - "Physical properties of HgCdTe-based heterostructures: - towards terahertz emission and detection" -](https://www.theses.fr/en/2017MONTS086) - -Implemented -a double-modulation technique, -enabling the extraction of critical magnetic fields -in a topological insulator. - -First to observe -[a temperature-driven phase transition](https://dx.doi.org/10.1103/PhysRevLett.120.086401) -in a HgTe/CdHgTe topological insulator -using magnetotransport. - -# Technical stack {.hidden} - ---- - -**Data analysis & presentation**: -[Python](https://www.python.org/), -[NumPy](https://numpy.org/), -[Pandas](https://pandas.pydata.org/), -[Xarray](https://xarray.dev/), -[SciPy](https://scipy.org/), -[Matplotlib](https://matplotlib.org/), -[hvPlot](https://hvplot.holoviz.org/), -[Plotly](https://plotly.com/python/), -[Bokeh](https://bokeh.org/), -[Panel](https://panel.holoviz.org/), -[Intake](https://intake.readthedocs.io) - -**Instrumentation integration & orchestration**: -[PyMeasure](https://pymeasure.readthedocs.io), -[Bluesky](https://blueskyproject.io/), -[yaq](https://yaq.fyi/), -[LabVIEW](https://www.ni.com/en/shop/labview.html) - -**Reporting**: -[Quarto](https://quarto.org/), -[Jupyter](https://jupyter.org/), -[Typst](https://typst.app/), -[LaTeX](https://www.latex-project.org/) - -**Programming**: -[VSCode](https://code.visualstudio.com/), -[Git](https://git-scm.com/), -[Linux](https://www.linux.com/what-is-linux/), -[Docker](https://www.docker.com/), -[PyTest](https://docs.pytest.org/), -[Pre-Commit](https://pre-commit.com/), -[GitLab CI/CD](https://gitlab.com/kadykov/), -[GitHub Actions](https://github.com/kadykov/), -[TDD](https://en.wikipedia.org/wiki/Test-driven_development), -[Devcontainers](https://containers.dev/) - -**Languages**: -English (upper-intermediate), -French ([upper-intermediate](https://www.duolingo.com/profile/aleksandrkadykov)), -Russian (native) - ---- - -# Selected publications {.hidden} - -1. Kadykov, A.M., Krishtopenko, S.S., Jouault, B. et al., - [*Temperature-Induced Topological Phase Transition in HgTe Quantum - Wells*](https://dx.doi.org/10.1103/PhysRevLett.120.086401), - **Physical Review Letters**, 120(8), *086401*, 2018 - -1. Kadykov, A.M., Torres, J., Krishtopenko, S.S. et al., [*Terahertz - imaging of Landau levels in HgTe-based topological - insulators*](https://dx.doi.org/10.1063/1.4955018), **Applied - Physics Letters**, 108(26), *262102*, 2016 - -1. Teppe, F., Marcinkiewicz, M., Krishtopenko, S.S. et al., - [*Temperature-driven massless Kane fermions in HgCdTe - crystals*](https://dx.doi.org/10.1038/ncomms12576), **Nature - Communications**, 7, *12576*, 2016 - -1. Kadykov, A.M., Teppe, F., Consejo, C. et al., [*Terahertz detection - of magnetic field-driven topological phase transition in HgTe-based - transistors*](https://dx.doi.org/10.1063/1.4932943), **Applied - Physics Letters**, 107(15), *152101*, 2015 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/kadykov-cv-fr.md b/kadykov-cv-fr.md deleted file mode 100644 index b046d91..0000000 --- a/kadykov-cv-fr.md +++ /dev/null @@ -1,464 +0,0 @@ ---- -author: Aleksandr KADYKOV -title: Ingénieur de Recherche -public-email: cv@kadykov.com -github: kadykov -gitlab: kadykov -linkedin: aleksandr-kadykov -website: www.kadykov.com -date: "year: 1970, month: 1, day: 1" -keywords: - - résumé - - resume - - CV - - Curriculum vitae - - Research Software Engineer - - Research Engineer - - Ingénieur de Recherche - - Software Engineer - - THz - - terahertz - - THz-TDS - - spectroscopy - - cryogenics - - optics - - photonics - - measurements - - data analysis - - programming - - DevOps - - CI/CD - - TDD - - Test-Driven Development - - Python - - Jupyter - - NumPy - - Pandas - - Xarray - - Scipy - - Python array API - - scikit-learn - - PyTorch - - MATLAB - - Matplotlib - - hvPlot - - Plotly - - Bokeh - - Panel - - holoviz - - OriginPro - - PyMeasure - - Bluesky - - yaq - - LabVIEW - - Intake - - SQL - - Quarto - - Typst - - Pandoc - - LaTeX - - VSCode - - Git - - Linux - - Docker - - Docker-compose - - Zotero -links: - Multitel: https://www.multitel.be/expertises/photonique-appliquee/imagerie-spectroscopie-terahertz/ - ASBL: https://fr.wikipedia.org/wiki/Association_sans_but_lucratif - SAPHIRE: https://www.multitel.be/projets/saphire/ - LNE: https://www.lne.fr/fr/recherche-et-developpement/activites-r-et-d - EPIC: https://fr.wikipedia.org/wiki/%C3%89tablissement_public_%C3%A0_caract%C3%A8re_industriel_et_commercial_en_France - I2S: https://edi2s.umontpellier.fr/ - L2C: https://coulomb.umontpellier.fr/ - IPM: http://www.ipmras.ru/en/institute/scientific-departments/department-110/ - RAS: https://fr.wikipedia.org/wiki/Acad%C3%A9mie_des_sciences_de_Russie - TDS: https://fr.wikipedia.org/wiki/Spectroscopie_t%C3%A9rahertz_dans_le_domaine_temporel - THz: https://fr.wikipedia.org/wiki/T%C3%A9rahertz - térahertz: https://fr.wikipedia.org/wiki/T%C3%A9rahertz - hBN: https://fr.wikipedia.org/wiki/Nitrure_de_bore - HgCdTe: https://fr.wikipedia.org/wiki/Tellurure_de_mercure-cadmium - graphène: https://fr.wikipedia.org/wiki/Graph%C3%A8ne - hélium: https://fr.wikipedia.org/wiki/H%C3%A9lium_liquide - Python: https://github.com/search?q=language%3APython+author%3Akadykov&type=pullrequests - d'onde: https://doi.org/10.1063/1.4996966 - FTIR: https://fr.wikipedia.org/wiki/Spectroscopie_infrarouge_%C3%A0_transform%C3%A9e_de_Fourier - FAIR: https://fr.wikipedia.org/wiki/Fair_data - Mons: https://www.openstreetmap.org/#map=19/50.45756/3.92540 - Trappes: https://www.openstreetmap.org/#map=17/48.76090/1.98370 - Nizhnij: https://www.openstreetmap.org/#map=17/56.29878/43.97990 - Novgorod: https://www.openstreetmap.org/#map=17/56.29878/43.97990 - Thèse: https://www.theses.fr/en/2017MONTS086 ---- - -# Ingénieur de Recherche {photo='image("./photo.jpg", width: 120pt)'} - -Concevoir et exécuter des expériences, -analyser et présenter des données, -développer des logiciels scientifiques Python. - -Connaissances en -recherche appliquée et fondamentale -en photonique THz -et -magnéto-transport -dans les matériaux 2D. - -# Compétences clés {.hidden} - -- Analyse et présentation de données -- Conception et exécution d'expériences -- Intégration et orchestration d'instrumentation -- Développement de logiciels scientifiques Python - -# Expérience professionnelle {.hidden} - ---- - -## Multitel ASBL {location="Mons \\ Belgique"} - -_Centre d'innovation sans but lucratif -en photonique appliquée, IA, etc._ - -### Ingénieur de Recherche en Spectroscopie et Imagerie THz {date="Juil.~2021 \\ Août~2024"} - -Développé -un pipeline de données de spectroscopie THz en domaine temporel (THz-TDS) -avec un rapport signal-bruit amélioré, -en utilisant un filtrage avancé. - - -Développé -une méthode de traitement de données THz-TDS - -pour l'extraction de l'indice de réfraction et de l'épaisseur -dans les matériaux à faible absorption. - -Optimisé -la reconstruction de profils d'indice de réfraction -à partir de données THz-TDS -en déchargeant les calculs -sur une carte graphique (GPU) -et en utilisant des algorithmes avancés. - - - - -Automatisé -les flux de travail de laboratoire -en mettant en œuvre des outils Python -pour l'orchestration des mesures, -la gestion des données, -l'analyse, -et la présentation des résultats. - -Garanti les meilleures pratiques de développement logiciel -en mettant en œuvre des tests unitaires, -des pipelines CI/CD, -et de la documentation. - - - - - ---- - -## Laboratoire National de Métrologie et d'Essais {location="Trappes \\ France"} - -_Établissement public à caractère industriel et commercial (LNE)_ - -### Ingénieur de Recherche en Métrologie Électrique Quantique {date="Sept.~2018 \\ Sept.~2020"} - -Dirigé des mesures de magnéto-transport à faible bruit -et à basses températures -sur du graphène. - - - -Conçu -un package logiciel Python flexible, -optimisant l'orchestration de l'équipement scientifique. - -Participé -à la nano-fabrication -de piles de graphène/hBN. - - - ---- - -## Institute for Physics of Microstructures (IPM RAS) {location="Nijni Novgorod \\ Russie"} - -_Institut de recherche public -spécialisé en physique de l'état solide_ - -### Ingénieur de Recherche en Photonique de Hétérostructures à 2D à Faible Écart d'Énergie {date="Mai~2017 \\ Sept.~2018"} - -Dirigé -des mesures cryogéniques de photoluminescence et de photoconductivité -par spectroscopie infrarouge à transformée de Fourier (FTIR) -de puits quantiques HgTe/HgCdTe. - -A obtenu -une émission laser -dans des hétérostructures HgCdTe -à une longueur d'onde record. - ---- - -# Éducation {.hidden} - -## Laboratoire Charles Coulomb (L2C) & IPM RAS {location="Montpellier, France \\ Nijni Novgorod, Russie"} - -École doctorale I2S -de l'Université de Montpellier - -### Doctorat en Physique de l'État Solide {date="Sept.~2014 \\ Déc.~2017"} - -Thèse: -[ - Propriétés physiques d'hétérostructures à base de HgCdTe : - vers l'émission et la détection Terahertz -]("https://theses.fr/2017MONTS086/) - -A mis en œuvre -une technique de double modulation, -permettant l'extraction des champs magnétiques critiques -dans un isolant topologique. - -Premier à observer une -[transition de phase thermique](https://dx.doi.org/10.1103/PhysRevLett.120.086401) -dans un isolant topologique HgTe/CdHgTe -à l'aide de la magnéto-transport. - ---- - -# Compétences techniques {.hidden} - -**Analyse et présentation de données**: -[Python](https://www.python.org/), -[NumPy](https://numpy.org/), -[Pandas](https://pandas.pydata.org/), -[Xarray](https://xarray.dev/), -[SciPy](https://scipy.org/), -[Matplotlib](https://matplotlib.org/), -[hvPlot](https://hvplot.holoviz.org/), -[Plotly](https://plotly.com/python/), -[Bokeh](https://bokeh.org/), -[Panel](https://panel.holoviz.org/), -[Intake](https://intake.readthedocs.io) - -**Intégration et orchestration d'instrumentation**: -[PyMeasure](https://pymeasure.readthedocs.io), -[Bluesky](https://blueskyproject.io/), -[yaq](https://yaq.fyi/), -[LabVIEW](https://www.ni.com/en/shop/labview.html) - -**Élaboration de rapports**: -[Quarto](https://quarto.org/), -[Jupyter](https://jupyter.org/), -[Typst](https://typst.app/), -[LaTeX](https://www.latex-project.org/) - -**Développement de logiciels**: -[VSCode](https://code.visualstudio.com/), -[Git](https://git-scm.com/), -[Linux](https://www.linux.com/what-is-linux/), -[Docker](https://www.docker.com/), -[PyTest](https://docs.pytest.org/), - -[GitLab CI/CD](https://gitlab.com/kadykov/), -[GitHub Actions](https://github.com/kadykov/), -[TDD](https://en.wikipedia.org/wiki/Test-driven_development), -[Devcontainers](https://containers.dev/) - -**Langues**: -Anglais (avancé), -Français ([avancé](https://www.duolingo.com/profile/aleksandrkadykov)), -Russe - ---- - -# Sélection de publications {.hidden} - -1. Kadykov, A.M., Krishtopenko, S.S., Jouault, B. et al., - [*Temperature-Induced Topological Phase Transition in HgTe Quantum - Wells*](https://dx.doi.org/10.1103/PhysRevLett.120.086401), - **Physical Review Letters**, 120(8), *086401*, 2018 - -1. Kadykov, A.M., Torres, J., Krishtopenko, S.S. et al., [*Terahertz - imaging of Landau levels in HgTe-based topological - insulators*](https://dx.doi.org/10.1063/1.4955018), **Applied - Physics Letters**, 108(26), *262102*, 2016 - -1. Teppe, F., Marcinkiewicz, M., Krishtopenko, S.S. et al., - [*Temperature-driven massless Kane fermions in HgCdTe - crystals*](https://dx.doi.org/10.1038/ncomms12576), **Nature - Communications**, 7, *12576*, 2016 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/kadykov-letter-en.md b/kadykov-letter-en.md deleted file mode 100644 index cf22e05..0000000 --- a/kadykov-letter-en.md +++ /dev/null @@ -1,223 +0,0 @@ ---- -author: Aleksandr KADYKOV -title: Letter to Future Employer -public-email: cv@kadykov.com -github: kadykov -gitlab: kadykov -linkedin: aleksandr-kadykov -website: www.kadykov.com -date: "year: 1970, month: 1, day: 1" -from: Mons, Belgium -# to: | -# Future Employer - -# Future Company - -# Future Country -keywords: - - CV - - Cover Letter - - résumé - - resume - - Curriculum vitae - - Research Software Engineer - - Research Engineer - - Software Engineer - - THz - - terahertz - - THz-TDS - - spectroscopy - - cryogenics - - optics - - photonics - - measurements - - data analysis - - programming - - DevOps - - CI/CD - - TDD - - Test-Driven Development - - Python - - Jupyter - - NumPy - - Pandas - - Xarray - - Scipy - - Python array API - - scikit-learn - - PyTorch - - MATLAB - - Matplotlib - - hvPlot - - Plotly - - Bokeh - - Panel - - holoviz - - OriginPro - - PyMeasure - - Bluesky - - yaq - - LabVIEW - - Intake - - SQL - - Quarto - - Typst - - Pandoc - - LaTeX - - VSCode - - Git - - Linux - - Docker - - Docker-compose - - Zotero -links: - Multitel: https://www.multitel.eu/expertise/applied-photonics/terahertz-spectroscopy-and-imaging/ - ASBL: https://fr.wikipedia.org/wiki/Association_sans_but_lucratif - SAPHIRE: https://www.multitel.eu/projects/saphire/ - LNE: https://www.lne.fr/en/research-and-development - EPIC: https://en.wikipedia.org/wiki/%C3%89tablissement_public_%C3%A0_caract%C3%A8re_industriel_et_commercial - I2S: https://edi2s.umontpellier.fr/ - L2C: https://coulomb.umontpellier.fr/?lang=en - IPM: http://www.ipmras.ru/en/institute/scientific-departments/department-110/ - RAS: https://en.wikipedia.org/wiki/Russian_Academy_of_Sciences - TDS: https://en.wikipedia.org/wiki/Terahertz_time-domain_spectroscopy - THz: https://en.wikipedia.org/wiki/Terahertz_radiation - hBN: https://en.wikipedia.org/wiki/Boron_nitride - HgCdTe: https://en.wikipedia.org/wiki/Mercury_cadmium_telluride - graphene: https://en.wikipedia.org/wiki/Graphene - helium: https://en.wikipedia.org/wiki/Liquid_helium - Python: https://github.com/search?q=language%3APython+author%3Akadykov&type=pullrequests - wavelength: https://doi.org/10.1063/1.4996966 - FTIR: https://en.wikipedia.org/wiki/Fourier-transform_infrared_spectroscopy - FAIR: https://en.wikipedia.org/wiki/FAIR_data - Mons: https://www.openstreetmap.org/#map=19/50.45756/3.92540 - Trappes: https://www.openstreetmap.org/#map=17/48.76090/1.98370 - Nizhny: https://www.openstreetmap.org/#map=17/56.29878/43.97990 - Novgorod: https://www.openstreetmap.org/#map=17/56.29878/43.97990 - Thesis: https://www.theses.fr/en/2017MONTS086 ---- - -# Letter to Future Employer - -Dear Future Employer, - -I'm writing -to offer you -a high-level view -of my career path, -highlighting my expertise -in photonics, -magnetotransport, -high-precision measurements, -and THz technology. - -## Photonics & Magnetotransport at L2C & IPM RAS - -I began my career in basic research -as a Research Engineer. - -My Ph.D. work, -a collaboration between -Laboratoire Charles Coulomb (L2C) -and the Institute for Physics of Microstructures (IPM RAS), -focused on photonics and magnetotransport -in HgTe/HgCdTe quantum wells. - -These heterostructures could serve dual purposes -depending on their configuration: -far-infrared emitters and detectors, -or topological insulators -with protected edge states. - -At IPM RAS, -I studied their potential -as far-infrared emitters and detectors, -using - -photoconductivity and photoluminescence. - -At L2C, -I explored their topological insulator states and transitions, -using THz detection and magnetotransport measurements. - -This work led -to the first observation of a topological phase transition -in these heterostructures -by magnetotransport, -as well as record-breaking laser emission wavelengths, -with results published in journals -like Nature Communications, PRL, PRB, and APL. - -## High-Precision Measurements at LNE - -After completing my Ph.D., -I moved to applied research -and worked for two years -at the Laboratoire national de métrologie et d'essais (LNE) -as a Research Engineer. - -Here, -I continued to explore magnetotransport properties of 2D systems, -and conducted low-noise, -high-precision -quantum Hall effect measurements in graphene. - -My contributions included -automating measurements -using a Python-based orchestration system, -nanofabrication of graphene/hBN stacks, -and optimizing cryogenic measurement systems -to reduce costs -by improving helium recuperation -and implementing a reliable dry helium-free cryogenic system. - -## THz Innovation at Multitel ASBL - -Next, -I spent over three years -as a Research Engineer at Multitel ASBL, -leading THz time-domain spectroscopy (THz-TDS) -and imaging activities. - -There, -I developed THz-based solutions -for industrial applications -such as non-destructive quality control -of humidity, thickness, or composition - -in industries such as pharmaceuticals, polymers, and biotechnology. - -As a result, -I developed new and improved existing methods -for extracting information from THz-TDS data, -including computationally cheap -preliminary estimation of thickness and refractive index -in low-absorption samples -and sensitivity curve-shaped -filtering with enhanced signal-to-noise ratio. - -I also implemented infrastructure -for reproducible research, -including Python tools for instrument integration, -FAIR -(findable, accessible, interoperable, and reusable) -data management, -and automated data analysis pipelines. - -Through this work, -I gained hands-on experience in software development, -including test-driven development, -CI/CD pipeline automation, -and Docker-based containerization. - -## What's Next? - -I'm now seeking new opportunities -to leverage my skills as a Research Engineer -to contribute to impactful projects. - -Could this be with your team? - -Thank you for your time and consideration. - -Best regards, diff --git a/kadykov-letter-fr.md b/kadykov-letter-fr.md deleted file mode 100644 index 621be18..0000000 --- a/kadykov-letter-fr.md +++ /dev/null @@ -1,227 +0,0 @@ ---- -author: Aleksandr KADYKOV -title: Lettre au Futur Employeur -public-email: cv@kadykov.com -github: kadykov -gitlab: kadykov -linkedin: aleksandr-kadykov -website: www.kadykov.com -date: "year: 1970, month: 1, day: 1" -from: Mons, Belgique -# to: | -# Future Employer - -# Future Company - -# Future Country -keywords: - - CV - - Cover Letter - - résumé - - resume - - Curriculum vitae - - Research Software Engineer - - Research Engineer - - Ingénieur de Recherche - - Software Engineer - - THz - - terahertz - - THz-TDS - - spectroscopy - - cryogenics - - optics - - photonics - - measurements - - data analysis - - programming - - DevOps - - CI/CD - - TDD - - Test-Driven Development - - Python - - Jupyter - - NumPy - - Pandas - - Xarray - - Scipy - - Python array API - - scikit-learn - - PyTorch - - MATLAB - - Matplotlib - - hvPlot - - Plotly - - Bokeh - - Panel - - holoviz - - OriginPro - - PyMeasure - - Bluesky - - yaq - - LabVIEW - - Intake - - SQL - - Quarto - - Typst - - Pandoc - - LaTeX - - VSCode - - Git - - Linux - - Docker - - Docker-compose - - Zotero -links: - Multitel: https://www.multitel.be/expertises/photonique-appliquee/imagerie-spectroscopie-terahertz/ - ASBL: https://fr.wikipedia.org/wiki/Association_sans_but_lucratif - SAPHIRE: https://www.multitel.be/projets/saphire/ - LNE: https://www.lne.fr/fr/recherche-et-developpement/activites-r-et-d - EPIC: https://fr.wikipedia.org/wiki/%C3%89tablissement_public_%C3%A0_caract%C3%A8re_industriel_et_commercial_en_France - I2S: https://edi2s.umontpellier.fr/ - L2C: https://coulomb.umontpellier.fr/ - IPM: http://www.ipmras.ru/en/institute/scientific-departments/department-110/ - RAS: https://fr.wikipedia.org/wiki/Acad%C3%A9mie_des_sciences_de_Russie - TDS: https://fr.wikipedia.org/wiki/Spectroscopie_t%C3%A9rahertz_dans_le_domaine_temporel - THz: https://fr.wikipedia.org/wiki/T%C3%A9rahertz - térahertz: https://fr.wikipedia.org/wiki/T%C3%A9rahertz - hBN: https://fr.wikipedia.org/wiki/Nitrure_de_bore - HgCdTe: https://fr.wikipedia.org/wiki/Tellurure_de_mercure-cadmium - graphène: https://fr.wikipedia.org/wiki/Graph%C3%A8ne - hélium: https://fr.wikipedia.org/wiki/H%C3%A9lium_liquide - Python: https://github.com/search?q=language%3APython+author%3Akadykov&type=pullrequests - d'onde: https://doi.org/10.1063/1.4996966 - FTIR: https://fr.wikipedia.org/wiki/Spectroscopie_infrarouge_%C3%A0_transform%C3%A9e_de_Fourier - FAIR: https://fr.wikipedia.org/wiki/Fair_data - Mons: https://www.openstreetmap.org/#map=19/50.45756/3.92540 - Trappes: https://www.openstreetmap.org/#map=17/48.76090/1.98370 - Nizhnij: https://www.openstreetmap.org/#map=17/56.29878/43.97990 - Novgorod: https://www.openstreetmap.org/#map=17/56.29878/43.97990 - Thèse: https://www.theses.fr/en/2017MONTS086 ---- - -# Lettre au Futur Employeur - -Cher Futur Employeur, - -Je vous écris -pour vous offrir une vue d'ensemble -de mon parcours professionnel, -en mettant en lumière mon expertise -en photonique, -magnetotransport, -mesures de haute précision, -et technologie THz. - -## Photonique et Magnetotransport au L2C et IPM~RAS - -J'ai commencé ma carrière dans la recherche fondamentale -en tant qu'Ingénieur de Recherche. - -Mon travail de thèse, -une collaboration entre -le Laboratoire Charles Coulomb (L2C) -et l'Institut de Physique des Microstructures (IPM~RAS), -portait sur la photonique et le magnetotransport -dans des puits quantiques HgTe/HgCdTe. - -Ces hétérostructures peuvent avoir une double utilité -selon leur configuration : -des émetteurs et détecteurs pour l'infrarouge lointain, -ou des isolants topologiques -avec des états de bord protégés. - -À l'IPM~RAS, -j'ai étudié leur potentiel -en tant qu'émetteurs et détecteurs pour l'infrarouge lointain, -en utilisant -la photoconductivité et la photoluminescence. - - -Au L2C, -j'ai exploré leurs états d'isolants topologiques et leurs transitions, -en utilisant la détection THz et des mesures de magnetotransport. - -Ce travail a conduit -à la première observation d'une transition de phase topologique -dans ces hétérostructures -par magnetotransport, -ainsi qu'à des longueurs d'émission laser record, -avec des résultats publiés dans des revues -telles que Nature Communications, PRL, PRB, et APL. - -## Mesures de Haute Précision au LNE - -Après avoir terminé ma thèse, -je suis passé à la recherche appliquée -et ai travaillé pendant deux ans -au Laboratoire national de métrologie et d'essais (LNE) -en tant qu'Ingénieur de Recherche. - -Là-bas, -j'ai continué à explorer les propriétés de magnetotransport des systèmes 2D, -et j'ai effectué des mesures de haute précision -et à faible bruit -de l'effet Hall quantique dans le graphène. - -Mes contributions comprenaient : - -- l'automatisation des mesures - à l'aide d'un système d'orchestration basé sur Python, -- la nanofabrication de structures graphène/hBN, -- et l'optimisation des systèmes de mesure cryogéniques - pour réduire les coûts, - en améliorant la récupération d'hélium - et en mettant en œuvre un système cryogénique fiable sans hélium liquide. - -## Innovation THz chez Multitel ASBL - -Ensuite, -j'ai passé plus de trois ans -en tant qu'Ingénieur de Recherche chez Multitel ASBL, -un centre d'innovation sans but lucratif, -où j'ai dirigé les activités térahertz -de spectroscopie dans le domaine temporel THz (THz-TDS) -et d'imagerie. - -Là-bas, -j'ai développé des solutions basées sur le THz -pour des applications industrielles -comme le contrôle qualité non destructif -de l'humidité, de l'épaisseur ou de la composition -dans des industries telles que la pharmacie, les polymères et la biotechnologie. - -À cette fin, -j'ai développé de nouvelles méthodes -et amélioré les méthodes existantes -pour extraire des informations à partir de données THz-TDS, -y compris : - -- une estimation préliminaire et rapide de l'épaisseur - et de l'indice de réfraction dans des échantillons faiblement absorbants, -- et un filtrage optimisé, basé sur la courbe de sensibilité, - avec un meilleur rapport signal/bruit. - -J'ai également mis en place une infrastructure -pour une recherche reproductible, -en développant des outils Python pour l'intégration d'instruments, -la gestion des données FAIR -(findable, accessible, interoperable, reusable), -et des pipelines d'analyse de données automatisés. - -Grâce à ce travail, -j'ai acquis une expérience pratique en développement logiciel, -y compris le développement piloté par les tests, -l'automatisation des pipelines CI/CD, -et la conteneurisation basée sur Docker. - -## Et ensuite ? - -Je suis maintenant à la recherche de nouvelles opportunités -pour mettre à profit mes compétences d'Ingénieur de Recherche -et contribuer à des projets impactants. - -Cela pourrait-il être avec votre équipe ? - - - -Cordialement, diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md new file mode 100644 index 0000000..4a7900f --- /dev/null +++ b/memory-bank/activeContext.md @@ -0,0 +1,98 @@ +# Active Context: CI Workflow Improvements (2025-05-01) + +## Current Focus + +Finalizing CI workflow improvements related to Docker image tagging, push logic, and caching. + +## Problem Identified & Resolved (Previous) + +1. **Initial Problem:** CI tests failed (`bats: not found`) because `actions/checkout@v4` wasn't initializing submodules. +2. **Intermediate Fix:** Added explicit `git submodule update --init --recursive` to CI. +3. **New Problem:** The `docker build` step for the *production* image failed with `git@github.com: Permission denied (publickey)`. This occurred because the build context included submodule definitions using SSH URLs, but the build environment lacked SSH keys. +4. **Root Cause:** The production Docker image build context included `.git` and `tests/` directories. The testing environment (devcontainer) relied on submodules. +5. **Solution Implemented:** + * Switched test dependencies (Bats, Bats-Support, Bats-Assert) from Git submodules to system packages (`apt install`) within the devcontainer (`.devcontainer/Dockerfile.ubuntu`) and for host-run tests (`tests/docker.bats` via CI step). + * Created `.dockerignore` to exclude `.git`, `tests/`, and other non-essential files from the production Docker build context, preventing the SSH key error. + * Updated Bats test files (`tests/unit/build_sh.bats`, `tests/filter/filters.bats`) to load helpers using `bats_load_library` instead of relative submodule paths. + * Updated `justfile` to use the system `bats` executable instead of the submodule path. + * Removed the explicit `git submodule update` step from the CI workflow (`.github/workflows/ci.yml`). +6. **New Problem (CI):** Tests run inside the devcontainer image (`just test-internal`) failed with `chmod: changing permissions of '/workspaces/typstCV/build.sh': Operation not permitted`. +7. **Root Cause (CI):** The test script (`tests/unit/build_sh.bats`) attempted to `chmod +x` the `build.sh` script on the host filesystem via the Docker volume mount, which failed due to permissions. Additionally, the symlinks for the local Typst package (`style.typ`, `typst.toml`), created by `postCreateCommand` in the devcontainer, were missing when running the image directly in CI. The host Bats tests (`tests/docker.bats`) were also missing helper packages (`bats-assert`, `bats-support`). +8. **Solution Implemented (CI):** + * Removed the `chmod +x` command from `tests/unit/build_sh.bats`. + * Added `RUN` commands to `.devcontainer/Dockerfile.ubuntu` to create the necessary package symlinks during the image build. + * Updated `.github/workflows/ci.yml` to install `bats-assert` and `bats-support` on the host runner for the `docker.bats` tests. +9. **New Problem (CI - Post Fixes):** Tests run inside the devcontainer image (`just test-internal`) failed again, this time with `error: failed to write PDF file (Permission denied (os error 13))`. +10. **Root Cause (CI - Post Fixes):** The test `build.sh: succeeds with valid input file` in `tests/unit/build_sh.bats` was attempting to write the output PDF (`dummy.pdf`) directly into the mounted workspace directory (`/workspaces/typstCV`), which the `vscode` user inside the container didn't have permission to do. +11. **Solution Implemented (CI - Post Fixes):** + * Modified the `build.sh: succeeds with valid input file` test in `tests/unit/build_sh.bats` to explicitly write its output PDF to a dedicated subdirectory within the Bats temporary directory (`$BATS_TMPDIR/test_output/`) instead of the current working directory. Added setup/teardown for this directory. +12. **New Problem (CI - E2E):** After fixing the unit tests, the E2E tests (`tests/test_e2e.sh`) failed with `pandoc: .: openTempFile: permission denied` and `Could not find data file ... typst-letter.typ`. +13. **Root Cause (CI - E2E):** The E2E test ran `build.sh` from the mounted workspace, but `build.sh` couldn't find templates/filters relative to the CWD, and Pandoc couldn't create temp files in the CWD. Also, the E2E test used a shell script instead of Bats. +14. **Final Solution Implemented:** + * Converted `tests/test_e2e.sh` to `tests/test_e2e.bats` for consistency. + * Modified `tests/test_e2e.bats` to run `build.sh` from within `$BATS_TMPDIR` to resolve Pandoc's temp file permission issue. + * Modified `.devcontainer/Dockerfile.ubuntu` to create symlinks for Pandoc templates (`*.typ`) and filters (`*.lua`) from the workspace into the standard system locations (`/usr/share/pandoc/...`), aligning the devcontainer environment with the production container. + * Reverted `build.sh` to use relative template/filter names, relying on Pandoc's `--data-dir` mechanism (which now works in both environments). + * Updated `justfile` to run the new `tests/test_e2e.bats`. + * User deleted the old `tests/test_e2e.sh`. +15. **New Problem (CI - Docker Tests):** The `test-docker` step failed with `!!! Docker image 'typst-cv:latest' not found`. +16. **Root Cause (CI - Docker Tests):** The test script (`tests/docker.bats`) hardcoded the image tag `typst-cv:latest`, but the CI workflow built and provided the image tagged as `kadykov/typst-cv:testing`. +17. **Solution Implemented (CI - Docker Tests):** + * Modified `tests/docker.bats` to read the image tag from an environment variable (`DOCKER_IMAGE_TAG`), defaulting to `typst-cv:latest` if unset. + * Modified `.github/workflows/ci.yml` to pass the correct tag (`${{ env.IMAGE_TAG_TESTING }}`) to the `bats` command via the `DOCKER_IMAGE_TAG` environment variable. +18. **New Problem (CI - Example Build):** The "Build Example PDFs for Release Artifacts" step failed with `Error: Only one input file allowed.` +19. **Root Cause (CI - Example Build):** The `docker run` commands in the CI workflow incorrectly included `build.sh` as part of the command being passed to the container. Since the container has `ENTRYPOINT ["build.sh"]`, the script name was passed as the first argument to the entrypoint script itself, leading to the argument parsing error. +20. **Solution Implemented (CI - Example Build):** + * Modified `.github/workflows/ci.yml` to remove the redundant `build.sh` from the `docker run` commands in the "Build Example PDFs" step, ensuring only the intended arguments are passed to the entrypoint. +21. **New Problem (CI - Devcontainer Tests):** The "Run internal tests" step failed with `docker: manifest unknown` for `ghcr.io/.../devcontainer:latest` during PR builds. +22. **Root Cause (CI - Devcontainer Tests):** The `devmeta` step in `.github/workflows/ci.yml` only added the `:latest` tag for the devcontainer image on the default branch (`main`), not on PRs. The subsequent test step explicitly tried to pull `:latest`, which didn't exist for PR builds. +23. **Solution Implemented (CI - Devcontainer Tests):** Modified the `devmeta` step in `.github/workflows/ci.yml` to remove the `enable={{is_default_branch}}` condition, ensuring the `:latest` tag is always generated and pushed for the devcontainer image, making it available for the test step during PR builds. +24. **New Problem (CI - Devcontainer Tests - Persistent):** Despite fix #23, the "Run internal tests" step *still* failed with `docker: manifest unknown` for `:latest` locally and in CI, even though logs showed `:latest` was tagged. The `:latest` tag on GHCR was not updating consistently. +25. **Root Cause (CI - Devcontainer Tests - Persistent):** Using the same `:latest` tag for both the image tag (`tags:`) and the cache reference (`cache-from`/`cache-to:`) in `docker/build-push-action` likely interfered with the proper updating of the `:latest` image manifest. +26. **Solution Implemented (CI - Devcontainer Tests - Persistent):** Modified the `Build and Cache Devcontainer Image` step in `.github/workflows/ci.yml` to use a dedicated cache tag (`:buildcache`) for `cache-from` and `cache-to`, separating cache management from the primary `:latest` image tag. + +## Recent Actions (This Session) + +- Diagnosed CI issues: missing tags on PR builds (`ERROR: tag is needed...`), incorrect push logic for production image on PRs, lack of devcontainer caching. +- Planned CI fixes: Add `type=ref,event=pr` tag for production image metadata, make production image push conditional on `github.event_name == 'push'`, implement devcontainer caching using GHCR. +- Modified `.github/workflows/ci.yml`: + * Added unconditional login step for GHCR. + * Added `type=ref,event=pr` to production image metadata (`id: meta`). + * Added metadata step for devcontainer image (`id: devmeta`). + * Replaced manual devcontainer build with `docker/build-push-action` using GHCR for caching (`id: build_devcontainer`). + * Made the final production image push step (`Build and push`) conditional: `if: success() && (github.event_name == 'push')`. + * Corrected step order for `devmeta`. + * Added `type=ref,event=pr` to devcontainer image metadata (`id: devmeta`). + * Corrected GHCR image name definition: removed `DEV_IMAGE_NAME` from top-level `env`, added a `run` step within the `docker` job to set `DEV_IMAGE_NAME` dynamically using `echo ... | tr '[:upper:]' '[:lower:]'` and `$GITHUB_ENV`. + * Resolved `shellcheck` warning by double-quoting `$GITHUB_ENV`. + * Added `permissions: packages: write` to the `docker` job to allow pushing to GHCR. + * Removed redundant `source` job. + * Removed redundant `docker pull ${{ env.DEV_IMAGE_NAME }}:latest` step. + * Modified `Build and Cache Devcontainer Image` step to use `:buildcache` for `cache-from`/`cache-to` instead of `:latest`. +- Updated this `activeContext.md`. + +## Decisions & Notes + +- Using system packages for test dependencies simplifies the CI build process. +- `.dockerignore` is crucial for keeping production build contexts clean. +- Aligning devcontainer resource locations (via symlinks) with production container locations (via copy) simplifies build scripts and testing. +- Running tests that write output from within `$BATS_TMPDIR` avoids container volume mount permission issues. +- Using an environment variable (`DOCKER_IMAGE_TAG`) allows the Docker usage tests (`tests/docker.bats`) to work correctly in both local (defaulting to `typst-cv:latest`) and CI environments (using the specific testing tag). +- When using `docker run` with an `ENTRYPOINT`, the command specified after the image name is passed as arguments *to* the entrypoint script. +- **Devcontainer Caching:** Use GHCR (`ghcr.io/${{ github.repository }}/devcontainer`, ensuring lowercase) for caching the devcontainer image. Use a dedicated tag (`:buildcache`) for `cache-from` and `cache-to` refs. Push cache on every run (PRs and pushes). Set the `DEV_IMAGE_NAME` env var dynamically within the job using a `run` step and `$GITHUB_ENV`. Requires `permissions: packages: write` on the job. The image itself should still be tagged with `:latest` (along with other dynamic tags like `pr-X`). +- **Production Image Push:** Only push the final tagged production image to Docker Hub on `push` events (to `main` or tags), not on `pull_request` events. Add `type=ref,event=pr` tag to metadata to ensure a tag always exists for PR builds, even though it won't be pushed. +- **Redundant Steps Removed:** The `source` job and the explicit `docker pull` for the devcontainer image were removed as they were unnecessary. + +## Recent Actions (This Session Continued) + +- **Attempted CodeQL:** Created `.github/workflows/codeql-analysis.yml` but removed it after discovering Lua is unsupported and Bash support caused errors (`Did not recognize the following languages: bash`). +- **Added Docker Image Scanning:** Added a `scan-image` job to `.github/workflows/ci.yml` using `aquasecurity/trivy-action` to scan the `${{ env.IMAGE_TAG_TESTING }}` image for HIGH/CRITICAL vulnerabilities after the `docker` job and before the `release` job. Updated `release` job dependency. +- **Added Dependabot:** Created `.github/dependabot.yml` to configure weekly checks for updates to the base Docker image and GitHub Actions used in workflows. +- Updated this `activeContext.md`. + +## Immediate Next Steps + +- Update `progress.md`. +- **User Action:** Commit the changes (including `.github/workflows/ci.yml`, `.github/dependabot.yml`, and Memory Bank files). +- **User Action:** Trigger the CI workflow (`ci.yml`) and verify it passes. Check Dependabot configuration in repository settings. +- Complete the task. diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md new file mode 100644 index 0000000..782da18 --- /dev/null +++ b/memory-bank/productContext.md @@ -0,0 +1,21 @@ +# Product Context: Typst CV/Letter Generator + +## Problem Solved + +The project aims to simplify the creation of professional CVs and cover letters. Users can focus on content by writing in familiar Markdown, while leveraging the powerful typesetting capabilities of Typst for high-quality PDF output. It avoids the need for users to learn complex Typst syntax directly for basic document creation. + +## Core User Need + +Users need a straightforward way to convert their Markdown CV/letter documents into PDFs without dealing with complex build commands or rigid file naming conventions. The original `justfile`-based system, while functional, was identified as too complex and inflexible for general use. + +## Desired User Experience + +- **Simplicity:** A single, intuitive command should handle the conversion process. +- **Flexibility:** Users should be able to name their Markdown files freely. +- **Customization:** Easy mechanism to provide private information (like phone/email) or override other metadata without editing core project files. +- **Standard Tooling:** Ability to integrate the tool into scripts (stdin/stdout) and use it easily within a Docker container. +- **Clear Documentation:** Instructions should be clear for both direct command-line usage and Docker usage. + +## Target Audience + +Individuals (developers, researchers, etc.) who are comfortable with Markdown and prefer text-based document creation workflows, but want polished PDF outputs without mastering a full typesetting system like LaTeX or Typst directly. diff --git a/memory-bank/progress.md b/memory-bank/progress.md new file mode 100644 index 0000000..ddeee38 --- /dev/null +++ b/memory-bank/progress.md @@ -0,0 +1,62 @@ +# Progress: Typst CV/Letter Generator (As of 2025-05-01) + +## What Works (Current State) + +- **Core Pipeline:** Markdown-to-PDF conversion using `build.sh`, Pandoc, Lua filters, and Typst functions correctly. +- **Interface:** Flexible `build.sh` CLI handles input/output options, type inference, stdin/stdout (for PDF), and metadata overrides (`--set`, `TYPSTCV_*`). +- **Features:** Automatic linkification (`linkify.lua`), CV side content (`{photo}`, `{location}`, `{date}` via `typst-cv.lua`), hidden headings (`.hidden`), and full-width ordered lists are functional. +- **Examples:** Generic, depersonalized example files (`example-cv.md`, `example-letter.md`) and placeholder image exist in `tests/fixtures/`. +- **Testing:** + - Unit tests (`bats`) for `build.sh` logic exist and pass (`tests/unit/build_sh.bats`). + - Filter tests (`bats`) comparing Pandoc output against snapshots exist and pass (`tests/filter/filters.bats`). + - E2E smoke tests converted to Bats (`tests/test_e2e.bats`), verifying PDF generation and basic content. Uses `cd $BATS_TMPDIR` workaround for Pandoc temp file permissions. + - Docker usage tests (`bats`) verify production container interaction (`tests/docker.bats`). CI host runner correctly installs helpers. CI execution fixed by passing the correct image tag via `DOCKER_IMAGE_TAG` environment variable. + - `justfile` provides convenient local test execution commands (`just test`, `just test-unit`, `just test-filter`, `just test-e2e`) using system-installed `bats`. +- **Docker:** + - Primary production image (`Dockerfile`) is Alpine-based, self-contained, uses multi-stage builds, includes pinned Typst v0.12.0 and necessary packages/fonts. Build context is cleaned via `.dockerignore`. Files (`*.lua`, `*.typ`) copied to standard Pandoc/Typst locations. + - Devcontainer (`.devcontainer/Dockerfile.ubuntu`) is Ubuntu-based with dev tools, Docker-in-Docker, system-installed Bats. Includes built-in symlinks for Pandoc templates/filters (`*.typ`, `*.lua`) into standard system locations, aligning it with the production environment. **CI now builds and caches this image to GHCR.** +- **CI:** GitHub Actions workflow: + - Lints code (`pre-commit`). + - Builds production Docker image (`Dockerfile`) and loads it locally for testing (`${{ env.IMAGE_TAG_TESTING }}`). + - **Sets lowercase GHCR image name (`DEV_IMAGE_NAME`) dynamically.** + - **Builds and caches devcontainer image to GHCR (`${{ env.DEV_IMAGE_NAME }}:latest`) on every run.** Includes PR tagging. Added `permissions: packages: write` to job. + - Runs internal tests (unit, filter, e2e) **inside the GHCR devcontainer image** (implicit pull via `docker run`). All known permission/path issues resolved. + - Runs Docker usage tests against the local production testing image (`${{ env.IMAGE_TAG_TESTING }}`). Fixed missing host Bats helpers and image tag mismatch. + - Builds example PDFs using the local production testing image. Fixed entrypoint argument issue. + - **Pushes final tagged production image to Docker Hub *only* on `push` events (main/tags).** Includes PR tagging. + - Handles GitHub Releases. + - Submodule handling removed. + - **Redundant `source` job and explicit `docker pull` step removed.** + - **Added Docker Image Scanning:** New `scan-image` job in `ci.yml` uses Trivy to check the testing image for HIGH/CRITICAL vulnerabilities before release. + - **Added Dependabot:** New configuration (`.github/dependabot.yml`) schedules weekly checks for Dockerfile and GitHub Actions updates. +- **Photo Handling:** Refactored to use `{photo="path" photowidth="..."}` attributes for better usability. +- **Test Dependencies:** Bats, Bats-Support, Bats-Assert are installed via system package manager (`apt`) in the devcontainer and CI test environments, replacing Git submodules. Test files updated to use `bats_load_library`. + +## What's Left to Build (Potential Future Work) + +- **Phase 2: User Experience & Flexibility:** + - Allow custom Pandoc Typst templates (beyond built-in `cv`/`letter`). + - Enhance style customization (more parameters in `style.typ` or allow custom style files). +- **Phase 3: Technical Challenges & Refinements:** + - Improve/Fix stdin/stdout support for Typst format output (overrides currently ignored). + - Refactor `typst-cv.lua` and `style.typ` (as previously noted). + - Explore dynamic font installation (e.g., `fontist`). +- **Optimize Dockerfiles:** Look for ways to share layers/stages between `Dockerfile` and `.devcontainer/Dockerfile.ubuntu`. +- **(Potentially)** Remove `Dockerfile.fedora`. +- **Phase 4: Rebranding & Marketing:** (As previously noted) + +## Current Status + +- **Phase 1 Complete:** Testing framework established, examples depersonalized. +- **Docker/CI Refactoring Complete:** Production Docker image switched to Alpine, CI updated to use it. +- **Devcontainer Switched:** Development environment moved to Ubuntu with Docker-in-Docker. +- **Test Dependencies Switched:** Successfully migrated from Git submodules to system packages (`apt`) for Bats testing framework. +- **CI Workflow Fixed & Improved:** Resolved previous CI failures (SSH keys, permissions, paths, test dependencies, entrypoint args, etc.). Implemented **devcontainer caching via GHCR** (using dynamically set lowercase image name, correct job permissions, **and dedicated `:buildcache` tag**) and **conditional push logic** for production images (only push on `push` events). Added `pr-X` tagging for both production and devcontainer image builds during PRs. Resolved `shellcheck` warning. Removed redundant `source` job and `docker pull` step. **Fixed persistent "manifest unknown" error for devcontainer tests by separating the cache tag (`:buildcache`) from the image tag (`:latest`) in the `docker/build-push-action` step.** +- **Added Security Checks:** Integrated Trivy Docker image vulnerability scanning and Dependabot dependency updates into the CI/CD process. (CodeQL was attempted but removed due to language support issues). +- **Submodule Cleanup:** Removed submodule configuration (`.gitmodules`) and directories (`tests/bats`, `tests/test_helper/*`). Old `tests/test_e2e.sh` deleted by user. +- **Ready for Verification:** Project is stable. All known CI issues are resolved. Ready for user to commit changes and trigger the CI workflow (`ci.yml`) to verify the final fixes, caching, conditional push logic, and new security scans. User should also check Dependabot configuration in repository settings. + +## Known Issues + +- **Minor Layout Quirk:** README mentions side content (`{date}`, `{location}`) might cause minor spacing variations depending on Typst layout. (Low priority Typst/styling detail). +- **Overrides Ignored for Typst Stdout:** `--set`/`TYPSTCV_*` overrides are currently ignored if outputting Typst format *to stdout* due to limitations in the pipeline. (This was not tested/fixed in this session). diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md new file mode 100644 index 0000000..07d135f --- /dev/null +++ b/memory-bank/projectbrief.md @@ -0,0 +1,35 @@ +# Project Brief: Typst CV/Letter Generator + +## Core Goal + +To provide a flexible and user-friendly system for rendering Curriculum Vitae (CV) and Cover Letters written in Markdown format into professional-looking PDFs using Typst. + +## Key Features (Original) + +- **Input Format:** Markdown with YAML frontmatter for metadata. +- **Custom Attributes:** Uses Markdown attributes for specific layout features (e.g., `photo`, `date`, `location`). +- **Rendering Pipeline:** + 1. Markdown + YAML -> Pandoc -> Typst format. + 2. Pandoc uses custom Lua filter (`linkify.lua`) for hyperlink enrichment. + 3. Pandoc uses custom Lua filter (`typst-cv.lua`) to translate attributes/classes to Typst functions. + 4. Pandoc uses specific templates (`typst-cv.typ`, `typst-letter.typ`). + 5. Typst renders the generated `.typ` file to PDF, using common styles from `style.typ` (defined as local package `@local/pandoc-cv`). +- **Workflow Management:** `justfile` orchestrates the multi-step rendering process (identified as problematic). +- **Distribution:** `Dockerfile` packages all dependencies (Pandoc, Typst, Just, fonts) for setup and execution. Includes a GitHub Action entrypoint (`entrypoint.sh`). + +## Problem Statement (User Identified & Analyzed) + +- The primary interface via `justfile` commands is complex, rigid, and not user-friendly. +- Hardcoded filenames (`kadykov`) and reliance on strict naming conventions (`filename-type-lang.md`) in `justfile` prevent general use. +- Repetitive recipes in `justfile` for public/private and languages. +- The distinction between public/private builds via `.env` and `--private` flag is inflexible. +- Docker entrypoint (`Dockerfile`) is tied to the inflexible `justfile`. + +## Objective of Current Task (Refined) + +1. **Refactor Interface:** Replace the `justfile` with a flexible shell script (`build.sh`). +2. **Improve Usability:** Design `build.sh` with a clear command-line interface accepting input files, output paths, and options. +3. **Flexible Overrides:** Implement a system for overriding YAML metadata using environment variables (prefixed `TYPSTCV_*`) and command-line arguments (`--set KEY=VALUE`), replacing the rigid `--private` flag. +4. **Add Features:** Support reading from stdin and writing PDF to stdout. +5. **Docker Integration:** Update `Dockerfile` entrypoint to use `build.sh`, making the container directly usable with the new interface. Update the GitHub Action script (`entrypoint.sh`) to use `build.sh`. +6. **Documentation:** Update `README.md` with new usage instructions. Establish and maintain the Memory Bank. diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md new file mode 100644 index 0000000..c466590 --- /dev/null +++ b/memory-bank/systemPatterns.md @@ -0,0 +1,66 @@ +# System Patterns: Typst CV/Letter Generator + +## Core Architecture: Pipeline Conversion + +The system employs a multi-stage pipeline pattern to convert Markdown input into a final PDF output. + +```mermaid +graph LR + A[Markdown + YAML Frontmatter] --> B(Pandoc); + C[linkify.lua] --> B; + D[typst-cv.lua] --> B; + E[typst-cv.typ / typst-letter.typ Template] --> B; + B -- Typst Code --> F(Typst Compile); + G[style.typ / @local/pandoc-cv] --> F; + H[Environment Variables / --set Args] -.-> F; + F --> I[PDF Output]; + + subgraph Pandoc Conversion + B + C + D + E + end + + subgraph Typst Rendering + F + G + H + end +``` + +## Key Components & Roles + +1. **Markdown Input:** User provides content and basic metadata (YAML). Uses specific Markdown attributes (`{key=value}`) and classes (`{.class}`) for layout hints. +2. **Pandoc:** Acts as the central converter. + * Parses Markdown and YAML. + * Applies Lua filters for transformations. + * Uses a Typst template (`.typ`) to structure the output. + * Can output either final PDF (simpler cases) or intermediate Typst code. +3. **Lua Filters (`linkify.lua`, `typst-cv.lua`):** Extend Pandoc's functionality. + * `linkify.lua`: Automatically creates hyperlinks based on `links` metadata. + * `typst-cv.lua`: Translates Markdown attributes/classes into specific Typst function calls (e.g., `#body-side`, `#hidden-heading`), decoupling Markdown syntax from Typst implementation details. +4. **Typst Templates (`typst-cv.typ`, `typst-letter.typ`):** Basic structure files used by Pandoc. They primarily import the main style package and pass metadata. +5. **Typst Style Package (`style.typ` / `@local/pandoc-cv`):** Contains the core Typst logic. + * Defines document structure, page layout, fonts, colors. + * Implements the functions called by `typst-cv.lua` (e.g., `body-side`, `hidden-heading`). + * Handles metadata passed from templates (`setup-style` function). + * Accesses override variables (`sys.inputs`) provided during Typst compilation. +6. **Build Script (`build.sh` - Proposed):** Orchestrates the pipeline. + * Parses user arguments (input file, overrides, output options). + * Selects the appropriate Pandoc template. + * Detects override variables (`TYPSTCV_*` env vars, `--set` args). + * Chooses the correct pipeline (direct Pandoc-to-PDF or Pandoc-to-Typst | Typst Compile) based on whether overrides are needed or stdout is used. + * Constructs and executes the final `pandoc` and `typst` commands. +7. **Docker (`Dockerfile`):** Packages the entire environment (dependencies, filters, templates, styles, build script) for reproducible execution. The entrypoint will be set to `build.sh`. + +## Key Design Decisions (Current & Proposed) + +- **Markdown as Source:** Prioritizes ease of writing for the user. +- **Pandoc as Bridge:** Leverages Pandoc's robust Markdown parsing and filter capabilities. +- **Lua Filters for Customization:** Encapsulates custom Markdown-to-Typst translation logic. +- **Typst for Layout:** Utilizes Typst's modern typesetting features for high-quality output. +- **Decoupled Styling:** Centralizes Typst styling logic in `style.typ` (via local package). +- **Script-based Interface (Proposed):** Replaces rigid `justfile` with a flexible `build.sh` for better usability and integration. +- **Environment/Arg Overrides (Proposed):** Provides a standard mechanism (`TYPSTCV_*`, `--set`) for customizing output without modifying source files. +- **Docker for Distribution:** Ensures consistent environment and simplifies setup. diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md new file mode 100644 index 0000000..9ce96cd --- /dev/null +++ b/memory-bank/techContext.md @@ -0,0 +1,63 @@ +# Technical Context: Typst CV/Letter Generator + +## Core Technologies + +- **Pandoc:** Universal document converter. Used for Markdown parsing, YAML metadata extraction, Lua filter execution, and applying Typst templates. Version specified in `Dockerfile`. +- **Typst:** Modern typesetting system. Used for final PDF rendering. Version specified in `Dockerfile`. +- **Lua:** Scripting language used for Pandoc filters (`linkify.lua`, `typst-cv.lua`). Filters are executed by Pandoc. +- **Markdown:** Input format (CommonMark with Pandoc extensions like YAML frontmatter, attributes, classes). +- **YAML:** Used within Markdown frontmatter for metadata (`author`, `title`, `links`, etc.). +- **Shell Script (Bash):** `build.sh` orchestrates the build process. +- **Docker:** Containerization platform. + - `Dockerfile`: Defines the primary **Alpine-based production image**. Uses multi-stage builds for fonts and Typst installation. + - `Dockerfile.fedora`: Previous Fedora-based image (potentially deprecated). + - `.devcontainer/Dockerfile`: Defines the **Alpine-based development environment** with dev tools and Docker-in-Docker. +- **Just:** Task runner for local development (linting, testing, building examples, building Docker images). Invoked via `just `. +- **Bats (Bash Automated Testing System):** Used for unit tests (`tests/unit/build_sh.bats`), filter tests (`tests/filter/filters.bats`), and Docker usage tests (`tests/docker.bats`). Installed via Git submodule in `tests/bats`. Includes `bats-support` and `bats-assert` helpers. + +## Key Files & Locations (within Production Docker image - `Dockerfile`) + +- **Pandoc Filters (`*.lua`):** Copied to `$PANDOC_DATA_DIR/filters/` (e.g., `/usr/share/pandoc/filters/`). +- **Pandoc Templates (`typst-*.typ`):** Copied to `$PANDOC_DATA_DIR/templates/` (e.g., `/usr/share/pandoc/templates/`). +- **Typst Style File (`style.typ`):** Copied to `/app/lib/style.typ`. Not treated as a package anymore. +- **Typst Executable:** `/usr/local/bin/typst` (Manually installed v0.12.0). +- **Typst Packages (`fontawesome`):** Copied from builder stage to `$TYPST_PACKAGE_PATH` (e.g., `/usr/share/typst/packages`). +- **Fonts:** Installed via `fontist` (builder stage) and `apk` into system font directories (e.g., `/usr/share/fonts/`). `TYPST_FONT_PATHS` environment variable points Typst to font directories *and* `/app/lib` (for `style.typ` import). +- **Build Script (`build.sh`):** Copied to `/usr/local/bin/build.sh`. +- **Working Directory:** `/data` (users mount their project here). + +## Dependencies & Versions (Production Image - `Dockerfile`) + +- Base Image: Alpine 3.21. +- Builder Stages: Ruby 3.2-slim (for fontist), Alpine 3.21 (for Typst). +- Runtime Dependencies (installed via `apk`): `bash`, `pandoc`, `fontconfig`, `font-awesome`. +- Manually Installed: Typst v0.12.0 (downloaded binary). +- Typst Packages: `@preview/fontawesome` (installed via `typst update` in builder stage). +- Fonts: Installed via `fontist` (using `fontist-manifest.yml`) and `apk`. +- `git`, `wget`, `tar` are only present in builder stages. + +## Build/Execution/Testing Environment + +- **Production Execution:** Designed to run within the built Docker container (based on `Dockerfile`), which includes runtime dependencies. User mounts their source files to `/data`. +- **Local Development:** Utilizes the **Alpine-based devcontainer** environment (`.devcontainer/Dockerfile`), which includes dev tools (`just`, `pre-commit`, `shellcheck`, etc.) and **Docker-in-Docker**. +- **Build Script (`build.sh`):** Relies on standard shell commands and tools available in the production container (Pandoc, Typst). +- **Testing:** + - Unit, Filter, E2E tests (`bats`, `sh`) are run *inside* the built production container in CI, mounting only `tests/fixtures`. + - Docker usage tests (`tests/docker.bats`) are run *on the CI host* against the built production container. + - Local testing uses `just test`. +- **CI (GitHub Actions):** + - Runs linters (`pre-commit`). + - Builds the production Docker image (`Dockerfile`). + - Runs unit, filter, E2E tests inside the container. + - Runs Docker usage tests on the host. + - Builds example PDFs using the container. + - Pushes tagged images to Docker Hub. + - Creates GitHub Releases. +- **Environment Variables:** `TYPSTCV_*` (for overrides), `PANDOC_DATA_DIR`, `TYPST_PACKAGE_PATH`, `TYPST_FONT_PATHS`, `APP_LIB_DIR` play roles in configuration within the Docker image. + +## Constraints + +- Relies on specific versions of tools (Typst, fontist) pinned in `Dockerfile`. +- Correct placement of filters, templates, and `style.typ` within the Docker image is crucial. +- Font availability (via fontist and apk) is essential. +- Network access is required during Docker build to download Typst and update packages. diff --git a/style.typ b/style.typ index 5809333..91847db 100644 --- a/style.typ +++ b/style.typ @@ -1,7 +1,5 @@ #import "@preview/fontawesome:0.5.0": * -#let author = "Aleksandr KADYKOV" - #let text-color = black #let background-color = luma(100%, 0%) // Transparent white #let primary-color = rgb("#3A468C") @@ -11,27 +9,48 @@ #let full-width = 453.45pt #let bodywidth = 317.41pt #let line-spacing = 0.8em -// Define a function to set up style that accepts external variables +// Helper function to get value from sys.inputs or fallback +#let get-input(key, fallback) = { + if key in sys.inputs.keys() { + sys.inputs.at(key) + } else { + fallback + } +} + +// Define a function to set up style that accepts external variables (fallbacks) #let setup-style( - author: author, - public-email: "cv@kadykov.com", - title: "Research Engineer", - website: "www.kadykov.com", - github: "kadykov", - gitlab: "kadykov", - linkedin: "aleksandr-kadykov", - keywords: ("CV",), + // These values come from the Pandoc template ($variable$) and act as fallbacks + author: "Default Author", + email: "default@example.com", // Keep default, likely always needed + title: "Default Title", // Keep default + website: none, // Change default to none + github: none, // Change default to none + gitlab: none, // Change default to none + linkedin: none, // Change default to none + keywords: ("Default",), language: "en", date: auto, hyphenate: auto, doc, ) = { + // Prioritize sys.inputs (from --input args) over template variables (from YAML) + let effective-author = get-input("author", author) + let effective-email = get-input("email", email) + let effective-title = get-input("title", title) + let effective-website = get-input("website", website) + let effective-github = get-input("github", github) + let effective-gitlab = get-input("gitlab", gitlab) + let effective-linkedin = get-input("linkedin", linkedin) + // Keywords and date are less likely to be overridden via CLI, but could be added if needed + let effective-keywords = keywords + let effective-date = date // Document settings set text( font: "IBM Plex Serif", size: 10.5pt, - lang: language, + lang: language, // Language might need override too? get-input("language", language) fill: text-color, hyphenate: hyphenate, ) @@ -44,38 +63,36 @@ hyphenate: auto, ) - show strong: set text(font: "IBM Plex Serif SmBld") + show strong: set text( + font: "IBM Plex Serif", + weight: "semibold", + ) set document( - title: title, - author: author, - date: date, - keywords: keywords, + title: effective-title, + author: effective-author, + date: effective-date, + keywords: effective-keywords, ) set page( paper: "a4", margin: (x: 2.5cm, y: 3.0cm), header: [ - #author + #effective-author // Use effective value #h(1fr) - #if "PHONE" in sys.inputs.keys() [ - #link("tel:" + sys.inputs.PHONE.replace(regex("[^0-9+]"), ""))[ + // Phone only comes from sys.inputs (no YAML equivalent planned) + #if "phone" in sys.inputs.keys() [ + #link("tel:" + sys.inputs.phone.replace(regex("[^0-9+]"), ""))[ #text(fill: primary-color)[#fa-phone()] - #sys.inputs.PHONE.replace("_", " ") + #sys.inputs.phone.replace("_", " ") // Allow underscores for spacing ] | ] - #let email = { - if "EMAIL" in sys.inputs.keys() { - sys.inputs.EMAIL - } else { - public-email - } - } - #link("mailto:" + email)[ + // Use effective email (checks sys.inputs first, then YAML via template var) + #link("mailto:" + effective-email)[ #text(fill: primary-color)[#fa-envelope()] - #email + #effective-email ] #v(-0.5em) #line(length: 100%, stroke: 0.5pt) @@ -85,15 +102,31 @@ #v(-1em) #line(length: 100%, stroke: 0.5pt) #v(-0.5em) - #text(fill: primary-color)[#fa-arrow-up-right-from-square()] - #link("https://" + website)[#website] - #h(1fr) - #text(fill: black)[#fa-github()] - #link("https://github.com/" + github)[#github] | - #text(fill: orange)[#fa-gitlab()] - #link("https://gitlab.com/" + gitlab)[#gitlab] | - #text(fill: blue)[#fa-linkedin()] - #link("https://www.linkedin.com/in/" + linkedin)[#linkedin] + // Use effective values for footer links, checking for none + #if effective-website != none { + text(fill: primary-color)[#fa-arrow-up-right-from-square()] + link("https://" + effective-website)[#effective-website] + } + #h(1fr) // Keep horizontal space regardless + // Group social links and add separators conditionally + #let social-links = () + #if effective-github != none { + social-links.push([#text(fill: black)[#fa-github()] #link( + "https://github.com/" + effective-github, + )[#effective-github]]) + } + #if effective-gitlab != none { + social-links.push([#text(fill: orange)[#fa-gitlab()] #link( + "https://gitlab.com/" + effective-gitlab, + )[#effective-gitlab]]) + } + #if effective-linkedin != none { + social-links.push([#text(fill: blue)[#fa-linkedin()] #link( + "https://www.linkedin.com/in/" + effective-linkedin, + )[#effective-linkedin]]) + } + // Display social links with separators + #social-links.join([ | ]) ], ) diff --git a/tests/docker.bats b/tests/docker.bats new file mode 100755 index 0000000..ce1f142 --- /dev/null +++ b/tests/docker.bats @@ -0,0 +1,114 @@ +#!/usr/bin/env bats + +# Load Bats helpers using bats_load_library for system packages +bats_load_library 'bats-support' +bats_load_library 'bats-assert' + +# --- Variables --- +# Use DOCKER_IMAGE_TAG env var if set (e.g., in CI), otherwise default to 'typst-cv:latest' for local runs +DOCKER_IMAGE="${DOCKER_IMAGE_TAG:-typst-cv:latest}" +FIXTURES_DIR="tests/fixtures" # Relative path inside the devcontainer +OUTPUT_DIR_TMP="" + +# --- Setup & Teardown --- +setup() { + # Ensure the image exists locally + docker image inspect "$DOCKER_IMAGE" > /dev/null 2>&1 || { + echo "!!! Docker image '$DOCKER_IMAGE' not found. Build it first (e.g., 'just build-docker')." >&3 + return 1 + } + # Create a temporary output directory for this test run + OUTPUT_DIR_TMP=$(mktemp -d) +} + +teardown() { + # Clean up temporary output directory + if [ -n "$OUTPUT_DIR_TMP" ]; then + rm -rf "$OUTPUT_DIR_TMP" + fi +} + +# --- Test Cases --- + +@test "docker run (no args): shows usage message" { + # Run without arguments, expect usage message and non-zero exit code + run docker run --rm "$DOCKER_IMAGE" + assert_failure # Script should exit with error if no input given + assert_output --partial "Usage: /usr/local/bin/build.sh" +} + +@test "docker run : generates PDF output" { + local input_file="$FIXTURES_DIR/example-cv.md" + local output_file="$OUTPUT_DIR_TMP/example-cv.pdf" + + # Mount fixtures read-only using host path (should work with DinD), mount output dir writable + run docker run --rm \ + -v "$(pwd)/${FIXTURES_DIR}:/test-fixtures:ro" \ + -v "$OUTPUT_DIR_TMP:/output" \ + "$DOCKER_IMAGE" --output-dir /output /test-fixtures/example-cv.md + + assert_success + assert [ -f "$output_file" ] + # Basic check: is it a PDF file? + run file "$output_file" + assert_output --partial "PDF document" +} + +@test "docker run --type letter: generates letter PDF" { + local input_file="$FIXTURES_DIR/example-letter.md" + local output_file="$OUTPUT_DIR_TMP/example-letter.pdf" + + run docker run --rm \ + -v "$(pwd)/${FIXTURES_DIR}:/test-fixtures:ro" \ + -v "$OUTPUT_DIR_TMP:/output" \ + "$DOCKER_IMAGE" --output-dir /output --type letter /test-fixtures/example-letter.md + + assert_success + assert [ -f "$output_file" ] + run file "$output_file" + assert_output --partial "PDF document" +} + +@test "docker run stdin > stdout: pipes PDF" { + local input_file="$FIXTURES_DIR/example-cv-stdin.md" # Use the new minimal fixture + local output_file="$OUTPUT_DIR_TMP/stdout.pdf" + + # Pipe input file to docker run, redirect output to file + # Mount fixtures read-only using host path (though not strictly needed for this fixture) + # Pass '-' to build.sh to indicate reading from stdin + run bash -c "cat \"./$input_file\" | docker run --rm -i \ + -v \"$(pwd)/${FIXTURES_DIR}:/test-fixtures:ro\" \ + \"$DOCKER_IMAGE\" - --output - > \"$output_file\"" # Note: -i is needed for stdin, '-' tells build.sh to use stdin + + assert_success + assert [ -f "$output_file" ] + assert [ -s "$output_file" ] # Check if file is not empty + run file "$output_file" + assert_output --partial "PDF document" +} + +@test "docker run --set: overrides metadata" { + local input_file="$FIXTURES_DIR/example-cv.md" + local output_file="$OUTPUT_DIR_TMP/override.pdf" + local override_author="Test Override Author" # Changed variable name for clarity + + # Explicitly set the output filename within the container + # Use --set author=... to match the variable used in YAML/template + run docker run --rm \ + -v "$(pwd)/${FIXTURES_DIR}:/test-fixtures:ro" \ + -v "$OUTPUT_DIR_TMP:/output" \ + "$DOCKER_IMAGE" --output-dir /output --output override.pdf --set author="$override_author" /test-fixtures/example-cv.md + + assert_success + # Check the expected file exists on the host via the volume mount + assert [ -f "$output_file" ] + + # Requires pdftotext (should be available on host if devcontainer is used, or install locally) + # This check is basic, assumes pdftotext is available + if command -v pdftotext &> /dev/null; then + run pdftotext "$output_file" - + assert_output --partial "$override_author" # Check for the correct override value + else + skip "pdftotext not found, skipping content check" + fi +} diff --git a/tests/filter/filters.bats b/tests/filter/filters.bats new file mode 100755 index 0000000..a5c6221 --- /dev/null +++ b/tests/filter/filters.bats @@ -0,0 +1,108 @@ +#!/usr/bin/env bats + +# Load Bats helpers using bats_load_library for system packages +bats_load_library 'bats-support' +bats_load_library 'bats-assert' + +# --- Configuration --- +FIXTURES_DIR="./tests/fixtures" +# Assuming PANDOC_DATA_DIR is set correctly in the environment or using default +PANDOC_DATA_DIR="${PANDOC_DATA_DIR:-/usr/share/pandoc}" +# Base pandoc command parts - use eval later if needed for complex args +PANDOC_BASE_CMD="pandoc --data-dir=\"$PANDOC_DATA_DIR\" --to=typst" + +setup() { + # Determine project root relative to this test file's directory + PROJECT_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)" + # Ensure fixtures are referenced from project root if needed, or adjust paths + FIXTURES_DIR="$PROJECT_ROOT/tests/fixtures" +} + +# --- linkify.lua Tests --- + +@test "filter/linkify: links Typst" { + local fixture="$FIXTURES_DIR/linkify_test.md" + local cmd="$PANDOC_BASE_CMD --lua-filter=linkify.lua \"$fixture\"" + run eval "$cmd" # Use eval as cmd string contains quotes + assert_success + assert_output --partial '#link("https://typst.app/")[Typst]' +} + +@test "filter/linkify: links Pandoc" { + local fixture="$FIXTURES_DIR/linkify_test.md" + local cmd="$PANDOC_BASE_CMD --lua-filter=linkify.lua \"$fixture\"" + run eval "$cmd" + assert_success + assert_output --partial '#link("https://pandoc.org/")[Pandoc]' +} + +@test "filter/linkify: links GitHub" { + local fixture="$FIXTURES_DIR/linkify_test.md" + local cmd="$PANDOC_BASE_CMD --lua-filter=linkify.lua \"$fixture\"" + run eval "$cmd" + assert_success + assert_output --partial '#link("https://github.com/")[GitHub]' +} + +@test "filter/linkify: does not link words not in YAML" { + local fixture="$FIXTURES_DIR/linkify_test.md" + local cmd="$PANDOC_BASE_CMD --lua-filter=linkify.lua \"$fixture\"" + run eval "$cmd" + assert_success + refute_output --partial '#link.*filter' # Use regex check here +} + + +# --- typst-cv.lua Tests --- + +@test "filter/typst-cv: handles photo attribute" { + local fixture="$FIXTURES_DIR/typst_cv_filter_test.md" + local cmd="$PANDOC_BASE_CMD --template=typst-cv.typ --lua-filter=typst-cv.lua \"$fixture\"" + run eval "$cmd" + assert_success + assert_output --partial 'side: profile-photo(image("photo.png", width: 100pt))' +} + +@test "filter/typst-cv: handles hidden class" { + local fixture="$FIXTURES_DIR/typst_cv_filter_test.md" + local cmd="$PANDOC_BASE_CMD --template=typst-cv.typ --lua-filter=typst-cv.lua \"$fixture\"" + run eval "$cmd" + assert_success + assert_output --partial '#hidden-heading()' +} + +@test "filter/typst-cv: handles location attribute" { + local fixture="$FIXTURES_DIR/typst_cv_filter_test.md" + local cmd="$PANDOC_BASE_CMD --template=typst-cv.typ --lua-filter=typst-cv.lua \"$fixture\"" + run eval "$cmd" + assert_success + # Check for the specific function call and the content (actual output has single backslash) + assert_output --partial 'side: event-date()[City \ Country]' +} + +@test "filter/typst-cv: handles date attribute" { + local fixture="$FIXTURES_DIR/typst_cv_filter_test.md" + local cmd="$PANDOC_BASE_CMD --template=typst-cv.typ --lua-filter=typst-cv.lua \"$fixture\"" + run eval "$cmd" + assert_success + assert_output --partial 'side: company-location()[2023 - Present]' +} + +@test "filter/typst-cv: handles ordered list" { + local fixture="$FIXTURES_DIR/typst_cv_filter_test.md" + local cmd="$PANDOC_BASE_CMD --template=typst-cv.typ --lua-filter=typst-cv.lua \"$fixture\"" + run eval "$cmd" + assert_success + assert_output --partial '#block(width: full-width)' +} + +@test "filter/typst-cv: handles header with both date and location (processes date)" { + local fixture="$FIXTURES_DIR/typst_cv_filter_test.md" + local cmd="$PANDOC_BASE_CMD --template=typst-cv.typ --lua-filter=typst-cv.lua \"$fixture\"" + run eval "$cmd" + assert_success + # Check date was processed (include closing parenthesis) + assert_output --partial 'side: company-location()[Date First])' + # Check location was NOT processed on the same header + refute_output --partial 'Header With Both.*side: event-date()' # Keep .* here for refute flexibility +} diff --git a/tests/fixtures/example-cv-no-optional.md b/tests/fixtures/example-cv-no-optional.md new file mode 100644 index 0000000..a418320 --- /dev/null +++ b/tests/fixtures/example-cv-no-optional.md @@ -0,0 +1,67 @@ +--- +title: Curriculum Vitae - No Optional Footer Links +author: Jane Doe Minimal +date: # Use structured date + year: 2025 + month: 4 + day: 26 +email: jane.doe.minimal@example.com +# No website, github, gitlab, linkedin here +# Optional: Define links used in the document +links: + Typst: https://typst.app/ + Pandoc: https://pandoc.org/ +--- + +# Jane Doe Minimal {photo="placeholder-photo.png"} + +## Summary + +This CV omits optional footer links (website, github, gitlab, linkedin) to test conditional display logic. + +## Experience {.hidden} + +### Example Corp {location="Remote"} +*Leading provider of innovative examples* + +#### Senior Example Engineer {date="2022 - Present"} +- Developed and maintained key features. + +### Another Company Inc. {location="Example City"} +*Pioneers in fictional services* + +#### Example Intern {date="Summer 2021"} +- Assisted the development team. + +## Education {.hidden} + +### University of Example {location="Example City, EX"} + +#### M.S. in Computer Science {date="2020 - 2022"} +*Thesis: Advanced Topics in Placeholders* + +### Example College {location="Another Town, AT"} + +#### B.S. in Software Engineering {date="2016 - 2020"} +*Minor: Fictional Studies* + +## Skills + +- **Programming:** Python, JavaScript, Typst, Shell Scripting +- **Tools:** Git, Docker, Pandoc +- **Concepts:** Agile Development, Test-Driven Development + +## Projects + +### Typst CV Generator + +A project to generate CVs and letters using Typst. + +### Placeholder Project + +Another example project demonstrating skills. + +## Languages + +- English (Native) +- French (Conversational) diff --git a/tests/fixtures/example-cv-stdin.md b/tests/fixtures/example-cv-stdin.md new file mode 100644 index 0000000..61b86d7 --- /dev/null +++ b/tests/fixtures/example-cv-stdin.md @@ -0,0 +1,16 @@ +--- +title: Minimal CV for Stdin Test +author: Stdin User +email: stdin@example.com +--- + +# Stdin User + +## Summary + +This is a minimal CV used for testing stdin piping in Docker tests. It does not contain images or complex formatting. + +## Skills + +- Testing +- Stdin diff --git a/tests/fixtures/example-cv.md b/tests/fixtures/example-cv.md new file mode 100644 index 0000000..2546472 --- /dev/null +++ b/tests/fixtures/example-cv.md @@ -0,0 +1,84 @@ +--- +title: Curriculum Vitae +author: Jane Doe +date: # Use structured date + year: 2025 + month: 4 + day: 25 +email: jane.doe@example.com +phone: +1-555-123-4567 +website: example.com +github: janedoe +linkedin: janedoe +# Optional: Define links used in the document +links: + website: https://example.com + Typst: https://typst.app/ + Pandoc: https://pandoc.org/ +# Optional: Add private data here, can be overridden by env vars or --set +private_info: This is private info from YAML +--- + +# Jane Doe {photo="placeholder-photo.png"} + +## Summary + +A highly motivated and skilled professional seeking a challenging position. Experienced in various technologies and methodologies. Eager to contribute to innovative projects. Check my website. + +--- + +## Experience {.hidden} + +### Example Corp {location="Remote"} +*Leading provider of innovative examples* + +#### Senior Example Engineer {date="2022 - Present"} +- Developed and maintained key features for the company's flagship product using advanced placeholder techniques. +- Collaborated with cross-functional teams (Product, Design, QA) to deliver high-quality software releases on schedule. +- Mentored junior engineers, conducted code reviews, and improved team coding standards. + +### Another Company Inc. {location="Example City"} +*Pioneers in fictional services* + +#### Example Intern {date="Summer 2021"} +- Assisted the development team with various tasks including testing and documentation. +- Gained practical experience in the software development lifecycle and agile methodologies. + +--- + +## Education {.hidden} + +### University of Example {location="Example City, EX"} + +#### M.S. in Computer Science {date="2020 - 2022"} +*Thesis: Advanced Topics in Placeholders* +Relevant coursework: Advanced Algorithms, Distributed Systems, Machine Learning. + +### Example College {location="Another Town, AT"} + +#### B.S. in Software Engineering {date="2016 - 2020"} +*Minor: Fictional Studies* +Graduated with honors. Capstone project involved developing a sample application. + +--- + +## Skills + +- **Programming:** Python, JavaScript, Typst, Shell Scripting +- **Tools:** Git, Docker, Pandoc +- **Concepts:** Agile Development, Test-Driven Development + +## Projects + +### Typst CV Generator + +A project to generate CVs and letters using Typst. + +### Placeholder Project + +Another example project demonstrating skills. + +## Languages + +- English (Native) +- French (Conversational) diff --git a/tests/fixtures/example-letter.md b/tests/fixtures/example-letter.md new file mode 100644 index 0000000..b1a42f8 --- /dev/null +++ b/tests/fixtures/example-letter.md @@ -0,0 +1,35 @@ +--- +title: Cover Letter +author: Jane Doe +date: April 24, 2025 +recipient: | + Hiring Manager + Example Corp + 123 Example St + Example City, EX 12345 +email: jane.doe@example.com +phone: +1-555-123-4567 +website: example.com +github: janedoe +linkedin: janedoe +# Optional: Define links used in the document +links: + typst: https://typst.app/ + Pandoc: https://pandoc.org/ +# Optional: Add private data here, can be overridden by env vars or --set +private_info: This is private info from YAML +--- + +Dear Hiring Manager, + +I am writing to express my strong interest in the Example Role position advertised on [Example.com](https://example.com/jobs/123). With my background in developing innovative solutions and my experience with technologies relevant to your stack, I am confident I possess the skills and qualifications necessary to make a significant contribution to Example Corp. + +In my previous role at Another Company Inc., I was responsible for [mention a key achievement or responsibility]. This experience allowed me to hone my skills in [mention relevant skill 1] and [mention relevant skill 2], both of which are mentioned as requirements in the job description. I am particularly drawn to Example Corp's work in [mention company area of interest] and believe my proactive approach and collaborative spirit would be a great asset to your team. + +I am proficient in various tools and technologies, including those used in this project like Pandoc and Typst. I am eager to learn more about this opportunity and discuss how my qualifications align with your needs. + +Thank you for your time and consideration. I have attached my CV for your review and look forward to hearing from you soon. + +Sincerely, + +Jane Doe diff --git a/tests/fixtures/linkify_test.md b/tests/fixtures/linkify_test.md new file mode 100644 index 0000000..11d97c7 --- /dev/null +++ b/tests/fixtures/linkify_test.md @@ -0,0 +1,13 @@ +--- +title: Linkify Test +links: + Typst: https://typst.app/ + Pandoc: https://pandoc.org/ + GitHub: https://github.com/ +--- + +This document tests the linkify filter. + +It should link Typst and Pandoc automatically. +Words like GitHub should also be linked. +Words not in the list, like filter, should remain plain text. diff --git a/tests/fixtures/placeholder-photo.png b/tests/fixtures/placeholder-photo.png new file mode 100644 index 0000000..1914264 Binary files /dev/null and b/tests/fixtures/placeholder-photo.png differ diff --git a/tests/fixtures/typst_cv_filter_test.md b/tests/fixtures/typst_cv_filter_test.md new file mode 100644 index 0000000..2cec496 --- /dev/null +++ b/tests/fixtures/typst_cv_filter_test.md @@ -0,0 +1,30 @@ +--- +title: Typst CV Filter Test - Current Logic +--- + +# Name {photo="photo.png" photowidth="100pt"} + +This header has a photo attribute. + +## Section 1 {.hidden} + +This header has the hidden class. + +### Company {location="City \\ Country"} + +This header has a location attribute. + +#### Position {date="2023 - Present"} + +This header has a date attribute. + +##### Unchanged Header + +This header has no special attributes or classes. + +###### Header With Both {date="Date First" location="Location Second"} + +This header has both date and location. The filter should only process the first one it finds (likely date). + +1. First item in ordered list. +2. Second item in ordered list. diff --git a/tests/test_e2e.bats b/tests/test_e2e.bats new file mode 100755 index 0000000..7e95b41 --- /dev/null +++ b/tests/test_e2e.bats @@ -0,0 +1,133 @@ +#!/usr/bin/env bats + +# Load Bats helpers +bats_load_library 'bats-support' +bats_load_library 'bats-assert' +# bats_load_library 'bats-file' # Can add later if needed + +# --- Variables --- +PROJECT_ROOT="" +SCRIPT="" +FIXTURES_DIR="" +TEST_OUTPUT_DIR="" + +# --- Setup & Teardown --- +setup() { + # Determine project root relative to this test file's directory + PROJECT_ROOT="$(cd "$BATS_TEST_DIRNAME/.." && pwd)" + SCRIPT="$PROJECT_ROOT/build.sh" + FIXTURES_DIR="$PROJECT_ROOT/tests/fixtures" + TEST_OUTPUT_DIR="$BATS_TMPDIR/test_output" + + # Create temporary output directory + mkdir -p "$TEST_OUTPUT_DIR" + + # Ensure build script is executable (should be via git, but double-check) + # No - this caused issues before. Assume it's executable. + # chmod +x "$SCRIPT" +} + +teardown() { + # Clean up temporary output directory + rm -rf "$TEST_OUTPUT_DIR" +} + +# --- Test Cases --- + +@test "e2e: generates PDF for example-cv.md" { + local input_fixture="$FIXTURES_DIR/example-cv.md" + local output_filename="example-cv.pdf" + local output_file_abs="$TEST_OUTPUT_DIR/$output_filename" + + # Run build.sh from /tmp to avoid pandoc permission issues on mounted volume + # Pass absolute paths for input/output-dir + run bash -c "cd \"$BATS_TMPDIR\" && \"$SCRIPT\" --output-dir \"$TEST_OUTPUT_DIR\" --output \"$output_filename\" \"$input_fixture\"" + assert_success "[build.sh] PDF generation failed" + + run test -f "$output_file_abs" + assert_success "Output PDF file not found: $output_file_abs" + run test -s "$output_file_abs" + assert_success "Output PDF file is empty: $output_file_abs" +} + +@test "e2e: example-cv.md PDF contains hidden headings" { + local input_fixture="$FIXTURES_DIR/example-cv.md" + local output_filename="example-cv-headings.pdf" # Use different name to avoid conflict if run in parallel + local output_file_abs="$TEST_OUTPUT_DIR/$output_filename" + + # Generate the PDF first (run from /tmp) + run bash -c "cd \"$BATS_TMPDIR\" && \"$SCRIPT\" --output-dir \"$TEST_OUTPUT_DIR\" --output \"$output_filename\" \"$input_fixture\"" + assert_success "[build.sh] PDF generation for heading check failed" + + run test -f "$output_file_abs" + assert_success "Output PDF file for heading check not found: $output_file_abs" + + # Check for pdftotext + run command -v pdftotext + if [ "$status" -ne 0 ]; then + skip "pdftotext not found, skipping content check" + fi + + # Extract text and check content + run pdftotext "$output_file_abs" - + assert_success "pdftotext failed" + assert_output --partial "Experience" "Hidden heading 'Experience' not found" + assert_output --partial "Education" "Hidden heading 'Education' not found" +} + +@test "e2e: generates PDF for example-letter.md" { + local input_fixture="$FIXTURES_DIR/example-letter.md" + local output_filename="example-letter.pdf" + local output_file_abs="$TEST_OUTPUT_DIR/$output_filename" + + # Run build.sh from /tmp, pass --type letter + run bash -c "cd \"$BATS_TMPDIR\" && \"$SCRIPT\" --output-dir \"$TEST_OUTPUT_DIR\" --output \"$output_filename\" \"$input_fixture\" --type letter" + assert_success "[build.sh] PDF generation failed for letter" + + run test -f "$output_file_abs" + assert_success "Output letter PDF file not found: $output_file_abs" + run test -s "$output_file_abs" + assert_success "Output letter PDF file is empty: $output_file_abs" +} + +@test "e2e: generates PDF for example-cv-no-optional.md" { + local input_fixture="$FIXTURES_DIR/example-cv-no-optional.md" + local output_filename="example-cv-no-optional.pdf" + local output_file_abs="$TEST_OUTPUT_DIR/$output_filename" + + # Run build.sh from /tmp + run bash -c "cd \"$BATS_TMPDIR\" && \"$SCRIPT\" --output-dir \"$TEST_OUTPUT_DIR\" --output \"$output_filename\" \"$input_fixture\"" + assert_success "[build.sh] PDF generation failed for no-optional CV" + + run test -f "$output_file_abs" + assert_success "Output no-optional CV PDF file not found: $output_file_abs" + run test -s "$output_file_abs" + assert_success "Output no-optional CV PDF file is empty: $output_file_abs" +} + +@test "e2e: example-cv-no-optional.md PDF lacks footer links" { + local input_fixture="$FIXTURES_DIR/example-cv-no-optional.md" + local output_filename="example-cv-no-optional-links.pdf" # Use different name + local output_file_abs="$TEST_OUTPUT_DIR/$output_filename" + + # Generate the PDF first (run from /tmp) + run bash -c "cd \"$BATS_TMPDIR\" && \"$SCRIPT\" --output-dir \"$TEST_OUTPUT_DIR\" --output \"$output_filename\" \"$input_fixture\"" + assert_success "[build.sh] PDF generation for no-optional link check failed" + + run test -f "$output_file_abs" + assert_success "Output PDF file for no-optional link check not found: $output_file_abs" + + # Check for pdftotext + run command -v pdftotext + if [ "$status" -ne 0 ]; then + skip "pdftotext not found, skipping content check" + fi + + # Extract text and check content + run pdftotext "$output_file_abs" - + assert_success "pdftotext failed" + # Check that specific URL parts or usernames are NOT present + refute_output --partial "github.com/" "Found 'github.com/' in no-optional PDF" + refute_output --partial "gitlab.com/" "Found 'gitlab.com/' in no-optional PDF" + refute_output --partial "linkedin.com/in/" "Found 'linkedin.com/in/' in no-optional PDF" +} diff --git a/tests/unit/build_sh.bats b/tests/unit/build_sh.bats new file mode 100755 index 0000000..d07f223 --- /dev/null +++ b/tests/unit/build_sh.bats @@ -0,0 +1,88 @@ +#!/usr/bin/env bats + +# Load Bats helpers using bats_load_library for system packages +bats_load_library 'bats-support' +bats_load_library 'bats-assert' + +setup() { + # Create dummy input and output directories for tests + mkdir -p "$BATS_TMPDIR/fixtures" + mkdir -p "$BATS_TMPDIR/test_output" + cat > "$BATS_TMPDIR/fixtures/dummy.md" <