diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..62f6858a8 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,17 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "cd \"$CLAUDE_PROJECT_DIR\" && just check", + "timeout": 120, + "statusMessage": "Running just check..." + } + ] + } + ] + } +} diff --git a/.claude/skills/publish-rendercv-typst-package/SKILL.md b/.claude/skills/publish-rendercv-typst-package/SKILL.md new file mode 100644 index 000000000..bea33491c --- /dev/null +++ b/.claude/skills/publish-rendercv-typst-package/SKILL.md @@ -0,0 +1,195 @@ +--- +name: publish-rendercv-typst-package +description: Create a PR to publish a new version of the rendercv-typst package to the Typst Universe (typst/packages repository). Validates package integrity, forks/clones the repo, copies files, and opens a PR. +disable-model-invocation: true +--- + +# Publish rendercv-typst to Typst Universe + +Create a pull request to `typst/packages` to publish the current version of `src/rendercv/renderer/rendercv_typst/`. + +The clone location for the typst/packages fork is `$HOME/.cache/rendercv/typst-packages`. + +## Step 1: Read package metadata + +Read `src/rendercv/renderer/rendercv_typst/typst.toml` to get the version and all metadata fields. + +## Step 2: Validate package integrity + +Run ALL checks below. Collect ALL failures and report them together. Do NOT proceed to Step 3 if any check fails. + +### 2a: Required files + +Verify these exist in `src/rendercv/renderer/rendercv_typst/`: +- `lib.typ` +- `typst.toml` +- `README.md` +- `LICENSE` +- `thumbnail.png` +- `template/main.typ` + +### 2b: Manifest completeness + +Parse `typst.toml` and verify it has: +- Required: `name`, `version`, `entrypoint`, `authors`, `license`, `description` +- Template section: `[template]` with `path`, `entrypoint`, `thumbnail` + +### 2c: Version consistency + +Check that the version string in `typst.toml` appears correctly in: +- `README.md` import statements (`@preview/rendercv:X.Y.Z`) +- `template/main.typ` import statement (`@preview/rendercv:X.Y.Z`) +- All example files in `src/rendercv/renderer/rendercv_typst/examples/*.typ` (if they have import statements) + +If ANY file references an old version, stop and report which files need updating. + +### 2d: CHANGELOG entry + +Read `src/rendercv/renderer/rendercv_typst/CHANGELOG.md` and verify there is an entry for the version being published. + +### 2e: All themes have example files + +This is critical. Extract all theme names shown in the README by finding image references that match the pattern `examples/.png` in the image URLs. Then verify that EVERY theme has a corresponding `.typ` file in `src/rendercv/renderer/rendercv_typst/examples/`. + +For example, if the README shows images for classic, engineeringresumes, sb2nov, moderncv, engineeringclassic, and harvard, then ALL of these must exist as `.typ` files in `src/rendercv/renderer/rendercv_typst/examples/`. + +If any example file is missing, STOP and tell the user exactly which files are missing. + +### 2f: No stale or broken links + +Check that the `README.md` does not reference nonexistent files within the package (e.g., broken relative links). + +### 2g: Import style in template + +Verify `template/main.typ` uses the absolute package import (`@preview/rendercv:{version}`) and NOT a relative import like `../lib.typ`. The Typst packages repository requires absolute imports. + +## Step 3: Handle previous work + +1. Check for existing open PRs for rendercv in `typst/packages`: + ``` + gh pr list --repo typst/packages --author @me --search "rendercv" --state all + ``` + +2. If an existing PR is **open**, ask the user what to do: + - Update the existing PR? + - Close it and create a new one? + - Abort? + +3. If the clone directory `$HOME/.cache/rendercv/typst-packages` already exists: + - If there are old branches for previous versions that have been merged/closed, delete them. + - Reset to upstream/main before proceeding. + +## Step 4: Set up fork and clone + +### If clone does NOT exist: + +```bash +mkdir -p $HOME/.cache/rendercv +# Fork if not already forked (idempotent) +gh repo fork typst/packages --clone=false +# Clone with sparse checkout +gh repo clone $(gh api user --jq .login)/packages $HOME/.cache/rendercv/typst-packages -- --filter=blob:none --sparse +cd $HOME/.cache/rendercv/typst-packages +git sparse-checkout set packages/preview/rendercv +git remote add upstream https://github.com/typst/packages.git 2>/dev/null || true +git fetch upstream main +``` + +### If clone ALREADY exists: + +```bash +cd $HOME/.cache/rendercv/typst-packages +git fetch upstream main +git checkout main +git reset --hard upstream/main +``` + +## Step 5: Create the package version directory + +1. Read the version from `typst.toml` (e.g., `0.3.0`). +2. Create a new branch: `git checkout -b rendercv-{version}` +3. Create the target directory: `packages/preview/rendercv/{version}/` +4. Copy files from the rendercv-typst source directory into the target: + +**Files to copy:** +- `lib.typ` +- `typst.toml` +- `README.md` +- `LICENSE` +- `thumbnail.png` +- `template/` (entire directory) +- `examples/` (entire directory, but exclude any `.pdf` files) + +**Do NOT copy:** +- `CHANGELOG.md` +- `.git/` or `.gitignore` +- Any `.pdf` files + +5. Verify no PDF files ended up in the target directory. + +## Step 6: Determine previous version + +Look at existing directories in `packages/preview/rendercv/` to find the most recent previous version. This is needed for the PR description. If no previous version exists (first submission), note that this is a new package. + +## Step 7: Build PR description + +Read `src/rendercv/renderer/rendercv_typst/CHANGELOG.md` and extract the changes for the current version. + +**PR title:** `rendercv:{version}` + +**PR body for updates:** +``` +I am submitting + - [ ] a new package + - [x] an update for a package + +Description: {Brief description of the package}. {Summary of what changed in this version}. + +### Changes from {previous_version} + +{Bullet list of changes extracted from CHANGELOG.md} +``` + +**PR body for new packages** (if no previous version exists, include the full checklist): +``` +I am submitting + - [x] a new package + - [ ] an update for a package + +Description: {Description from typst.toml} + +I have read and followed the submission guidelines and, in particular, I +- [x] selected a name that isn't the most obvious or canonical name for what the package does +- [x] added a `typst.toml` file with all required keys +- [x] added a `README.md` with documentation for my package +- [x] have chosen a license and added a `LICENSE` file or linked one in my `README.md` +- [x] tested my package locally on my system and it worked +- [x] `exclude`d PDFs or README images, if any, but not the LICENSE +- [x] ensured that my package is licensed such that users can use and distribute the contents of its template directory without restriction, after modifying them through normal use. +``` + +## Step 8: Commit, push, and create PR + +```bash +cd $HOME/.cache/rendercv/typst-packages +git add packages/preview/rendercv/{version}/ +git commit -m "rendercv:{version}" +git push -u origin rendercv-{version} +``` + +Create the PR: +```bash +gh pr create \ + --repo typst/packages \ + --base main \ + --title "rendercv:{version}" \ + --body "..." # Use the body from Step 7 +``` + +## Step 9: Report results + +Tell the user: +1. The PR URL (clickable) +2. The clone location (`$HOME/.cache/rendercv/typst-packages`) +3. The branch name (`rendercv-{version}`) +4. Any warnings noticed during validation (even if they didn't block the PR) diff --git a/.claude/skills/rendercv-development-context/SKILL.md b/.claude/skills/rendercv-development-context/SKILL.md new file mode 100644 index 000000000..27ec6301a --- /dev/null +++ b/.claude/skills/rendercv-development-context/SKILL.md @@ -0,0 +1,82 @@ +--- +name: rendercv-development-context +description: RenderCV codebase architecture, source code standards, and project references. Use when writing or reviewing RenderCV code. +--- + +# RenderCV Development Context + +## Codebase references + +- @docs/developer_guide/understanding_rendercv.md +- @docs/developer_guide/testing.md +- @docs/developer_guide/json_schema.md +- @src/rendercv/schema/ +- @src/rendercv/renderer/ +- @src/rendercv/cli/ +- @tests/ +- @pyproject.toml +- @justfile + +## How-to guides + +- @docs/developer_guide/how_to/add_theme.md +- @docs/developer_guide/how_to/add_locale.md +- @docs/developer_guide/how_to/add_social_network.md + +## Source code standards + +### Type annotations + +Every function, variable, and class attribute must be strictly typed. No exceptions. + +Use modern Python 3.12+ syntax: + +- Type aliases with `type` statement +- PEP 695 type parameters (`[T]`, `[**P]`) +- Pipe unions (`str | int`, not `Union[str, int]`) +- Proper optional types (`str | None`, not `Optional[str]`) + +### Linting and type checking + +Always run `just check` and `just format` before committing. `just check` must show **zero errors**: + +```bash +just format +just check +``` + +If there's absolutely no alternative, use `# ty: ignore[error-code]` or `#NOQA: error-code` to ignore typing or linting errors. + +### Docstrings + +Use [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings). Include a **"Why" section** and **"Example" section** when it adds value: + +```python +def resolve_relative_path( + path: pathlib.Path, info: pydantic.ValidationInfo, must_exist: bool = True +) -> pathlib.Path: + """Convert relative path to absolute path based on input file location. + + Why: + Users reference files like `photo: profile.jpg` relative to their CV + YAML. This validator resolves such paths to absolute form and validates + existence, enabling file access during rendering. + + Args: + path: Path to resolve (may be relative or absolute). + info: Validation context containing input file path. + must_exist: Whether to raise error if path doesn't exist. + + Returns: + Absolute path. + """ +``` + +Docstring order: + +1. Brief description (one line) +2. Why section (when it adds value) +3. Example section (when it adds value) +4. Args section (mandatory) +5. Returns section (mandatory) +6. Raises section (mandatory if function raises exceptions) diff --git a/.claude/skills/rendercv-skill b/.claude/skills/rendercv-skill new file mode 160000 index 000000000..fc14e727b --- /dev/null +++ b/.claude/skills/rendercv-skill @@ -0,0 +1 @@ +Subproject commit fc14e727b2d4a36c612ef0989726bd18411218d7 diff --git a/.claude/skills/rendercv-testing-context/SKILL.md b/.claude/skills/rendercv-testing-context/SKILL.md new file mode 100644 index 000000000..3589fc2da --- /dev/null +++ b/.claude/skills/rendercv-testing-context/SKILL.md @@ -0,0 +1,155 @@ +--- +name: rendercv-testing-context +description: RenderCV test authoring standards, structure, and conventions. Use when writing or reviewing tests. +--- + +# RenderCV Testing Context + +## References + +- @tests/ +- @docs/developer_guide/testing.md + +## File structure + +Each test file tests all classes and functions in its corresponding source file. The structure mirrors `src/rendercv/`: + +``` +src/rendercv/renderer/templater/date.py + → tests/renderer/templater/test_date.py + (tests all functions and classes in date.py) + +src/rendercv/schema/models/cv/section.py + → tests/schema/models/cv/test_section.py + (tests all functions and classes in section.py) +``` + +## Naming conventions + +Test names must include the name of the function or class being tested. + +**When you need only one test**, use `test_` + the name: + +- Testing `clean_url()` → `test_clean_url` +- Testing `Cv` → `test_cv` + +**When you need multiple tests**, wrap them in a class using `Test` + PascalCase name: + +- Testing `clean_url()` → `TestCleanUrl` +- Testing `Cv` → `TestCv` + +Example with one test: + +```python +@pytest.mark.parametrize( + ("url", "expected_clean_url"), + [ + ("https://example.com", "example.com"), + ("https://example.com/", "example.com"), + ("https://example.com/test", "example.com/test"), + ], +) +def test_clean_url(url, expected_clean_url): + assert clean_url(url) == expected_clean_url +``` + +Example with multiple tests: + +```python +class TestComputeDateString: + @pytest.mark.parametrize(...) + def test_date_parameter_takes_precedence(self, ...): + ... + + @pytest.mark.parametrize(...) + def test_date_ranges(self, ...): + ... + + @pytest.mark.parametrize(...) + def test_returns_none_for_incomplete_data(self, ...): + ... +``` + +## Use parametrize for variations + +Instead of writing multiple similar tests, use `@pytest.mark.parametrize`: + +```python +@pytest.mark.parametrize( + ("input_a", "input_b", "expected"), + [ + ("2020-01-01", "2021-01-01", "Jan 2020 – Jan 2021"), + ("2020-01", "2021-02-01", "Jan 2020 – Feb 2021"), + (2020, 2021, "2020 – 2021"), + ], +) +def test_date_ranges(self, input_a, input_b, expected): + result = compute_date_string(None, input_a, input_b, EnglishLocale()) + assert result == expected +``` + +## Shared fixtures with conftest.py + +Place shared fixtures in `conftest.py`. Use the closest one possible: + +- Fixtures for one folder → that folder's `conftest.py` +- Fixtures for multiple folders → their closest common parent's `conftest.py` + +``` +tests/ +├── conftest.py # Used across all tests +├── schema/ +│ ├── conftest.py # Used by schema tests only +│ └── models/ +│ └── cv/ +│ ├── conftest.py # Used by CV model tests only +│ ├── test_section.py +│ └── test_cv.py +└── renderer/ + └── ... +``` + +## Testing principles + +**Keep tests focused.** Test functions in isolation: input → output. + +**Don't create unnecessary fixtures.** If setup is one clear line, inline it: + +```python +# Don't: +@pytest.fixture +def locale(self): + return EnglishLocale() + +def test_something(self, locale): + result = format_date(Date(2020, 1, 1), locale) + +# Do: +def test_something(self): + result = format_date(Date(2020, 1, 1), EnglishLocale()) +``` + +**Prefer real behavior over mocking.** Only mock when there's no practical alternative (external APIs, file system, etc.). + +**Name tests by expected behavior, not by input:** + +- Good: `test_returns_none_for_incomplete_data` - describes what should happen +- Bad: `test_function_with_none_input` - describes input but not behavior + +**Keep tests simple:** + +```python +def test_something(self, input, expected): + result = function_under_test(input) + assert result == expected +``` + +**What to test:** + +- Input → expected output +- Input → expected error + +**What to avoid:** + +- Testing implementation details instead of behavior +- Complex test setup when simple values work diff --git a/.claude/skills/review-rendercv-pr/SKILL.md b/.claude/skills/review-rendercv-pr/SKILL.md new file mode 100644 index 000000000..4889ef6bd --- /dev/null +++ b/.claude/skills/review-rendercv-pr/SKILL.md @@ -0,0 +1,131 @@ +--- +name: review-rendercv-pr +description: Review a GitHub pull request against RenderCV's codebase standards, architecture, and test requirements, then post a detailed review. +--- + +# Review a RenderCV Pull Request + +Review a GitHub PR for the `rendercv` repository. Analyze code quality, correctness, test coverage, and adherence to project conventions, then post a review. + +## Step 1: Identify the PR + +If a PR number or URL is provided, use it. Otherwise, list open PRs: + +```bash +gh pr list --repo rendercv/rendercv --state open --json number,title,author,headRefName --limit 20 +``` + +Read the PR details: + +```bash +gh pr view --repo rendercv/rendercv +``` + +## Step 2: Understand RenderCV + +Before reviewing, build a deep understanding of the project: + +- @.claude/skills/rendercv-development-context/SKILL.md +- @.claude/skills/rendercv-testing-context/SKILL.md + +Read the referenced files, focusing on modules relevant to the PR. + +## Step 3: Analyze the PR diff + +Get the full diff and list of changed files: + +```bash +gh pr diff --repo rendercv/rendercv +``` + +```bash +gh pr view --repo rendercv/rendercv --json files --jq '.files[].path' +``` + +Read the linked issue (if any) to understand the motivation: + +```bash +gh pr view --repo rendercv/rendercv --json body --jq '.body' +``` + +## Step 4: Read the full context of changed files + +For every file touched by the PR, read the **full file on the PR branch** (not just the diff) to understand context: + +```bash +gh pr diff --repo rendercv/rendercv --patch +``` + +Also read the corresponding source and test files in the main branch to understand what existed before. + +## Step 5: Evaluate against RenderCV standards + +Check each of these categories systematically: + +Evaluate the PR against @.claude/skills/rendercv-development-context/SKILL.md and @.claude/skills/rendercv-testing-context/SKILL.md. Also check for correctness (edge cases, regressions) and security (no injection, path traversal, or hardcoded secrets). + +## Step 6: Check CI status + +```bash +gh pr checks --repo rendercv/rendercv +``` + +Note any failing checks — these must be resolved before merging. + +## Step 7: Post the review + +Compose a structured review and post it using `gh`: + +```bash +gh pr review --repo rendercv/rendercv \ + --body "$(cat <<'EOF' +## Review Summary + +<1-2 sentence overview of the PR and its quality> + +## Correctness + + +## Code Conventions + + +## Architecture + + +## Testing + + +## Other Notes + +EOF +)" \ + -- +``` + +Where `` is one of: + +- `--approve` — Everything looks good, meets all standards. +- `--request-changes` — Issues that must be fixed before merging. +- `--comment` — Observations that don't block merging but are worth noting. + +### Inline comments + +For specific line-level feedback, add inline review comments: + +```bash +gh api repos/rendercv/rendercv/pulls//comments \ + --method POST \ + -f body="" \ + -f commit_id="$(gh pr view --repo rendercv/rendercv --json headRefOid --jq '.headRefOid')" \ + -f path="" \ + -f side="RIGHT" \ + -F line= +``` + +## Step 8: Report results + +Tell the user: +1. Overall assessment (approve / request changes / comment) +2. Key findings in each category +3. Any blocking issues that must be fixed +4. Link to the posted review diff --git a/.claude/skills/solve-rendercv-issue/SKILL.md b/.claude/skills/solve-rendercv-issue/SKILL.md new file mode 100644 index 000000000..3df120ef8 --- /dev/null +++ b/.claude/skills/solve-rendercv-issue/SKILL.md @@ -0,0 +1,106 @@ +--- +name: solve-rendercv-issue +description: Pick up a GitHub issue (or accept one), fully understand the RenderCV codebase, implement the fix/feature with tests, and open a PR to origin/main. +--- + +# Solve a RenderCV Issue + +Pick a GitHub issue from the `rendercv` repository, implement a complete solution with tests, and open a pull request to `origin/main`. + +## Step 1: Select an issue + +If an issue number or URL is provided, use it. Otherwise, pick the highest-priority open issue automatically: + +```bash +gh issue list --repo rendercv/rendercv --state open --sort created --json number,title,labels,body --limit 10 +``` + +Choose the most impactful issue that is not labeled `wontfix` or `question`. Prefer bugs over features, and smaller-scoped issues over vague ones. + +Read the full issue body: + +```bash +gh issue view --repo rendercv/rendercv +``` + +## Step 2: Understand RenderCV + +Before writing any code, build a deep understanding of the project: + +- @.claude/skills/rendercv-development-context/SKILL.md +- @.claude/skills/rendercv-testing-context/SKILL.md + +Read the referenced files, focusing on modules relevant to the issue. + +## Step 3: Set up the branch + +Create a branch from `origin/main` named after the issue: + +```bash +git fetch origin main +git checkout -b claude/issue- origin/main +``` + +## Step 4: Reproduce the problem (bugs only) + +For bug reports: + +1. Write a **failing test** that demonstrates the bug. Place it in the correct test file per the testing standards in the development context. +2. Run only that test to confirm it fails: + ```bash + uv run --frozen --all-extras pytest tests/path/to/test_file.py::test_name -x + ``` +3. Do NOT proceed to the fix until you have a red test. + +## Step 5: Implement the solution + +Write the fix or feature following @.claude/skills/rendercv-development-context/SKILL.md and @.claude/skills/rendercv-testing-context/SKILL.md. + +## Step 6: Verify the solution + +Run all verification commands from the development context (`just format`, `just check`, `just test`, `just test-coverage`). Every single one must pass before proceeding. + +## Step 7: Commit and push + +Stage only the files you changed. Write a clear commit message and push: + +```bash +git add +git commit -m "Fix #: " +git push origin claude/issue- +``` + +## Step 8: Open a pull request + +Create a PR targeting `main`: + +```bash +gh pr create \ + --repo rendercv/rendercv \ + --base main \ + --head claude/issue- \ + --title "Fix #: " \ + --body "$(cat <<'EOF' +## Summary + +<1-3 bullet points explaining what was done and why> + +Closes # + +## Changes + + + +## Test plan + + +EOF +)" +``` + +Tell the user: +1. What the issue was (root cause) +2. How it was fixed (approach) +3. What tests were added or modified +4. Coverage status (must remain 100%) +5. Link to the PR diff --git a/.claude/skills/triage-rendercv-issue/SKILL.md b/.claude/skills/triage-rendercv-issue/SKILL.md new file mode 100644 index 000000000..a225b6d0b --- /dev/null +++ b/.claude/skills/triage-rendercv-issue/SKILL.md @@ -0,0 +1,65 @@ +--- +name: triage-rendercv-issue +description: Analyze a newly opened GitHub issue, comment with findings and an action plan, and offer to open a PR. +--- + +# Triage a RenderCV Issue + +Analyze a newly opened issue on the `rendercv/rendercv` repository. Post a helpful comment that demonstrates understanding of the problem and offers next steps. + +## Step 1: Read the issue + +Get the full issue details including all comments: + +```bash +gh issue view --repo rendercv/rendercv --comments +``` + +Determine: + +- Is this a bug report, feature request, question, or something else? +- Is there enough information to understand and reproduce the problem? +- Is this a duplicate of an existing issue? + +Check for duplicates: + +```bash +gh issue list --repo rendercv/rendercv --state all --search "" --json number,title,state --limit 10 +``` + +## Step 2: Understand the relevant code + +Familiarize yourself with the project architecture and the specific area the issue relates to: + +- @.claude/skills/rendercv-development-context/SKILL.md + +Read the architecture and source structure sections, then explore the specific source files and tests related to the issue. + +## Step 3: Post a comment + +Comment on the issue using `gh`: + +```bash +gh issue comment --repo rendercv/rendercv --body "$(cat <<'EOF' + +EOF +)" +``` + +### Comment structure + +1. **Understanding**: Restate the problem in your own words to confirm you understand it. +2. **Analysis**: Which files and modules are involved, what the current behavior is, and why the issue occurs (or what would need to change for a feature request). +3. **Proposed approach**: Concrete steps to fix or implement this, referencing specific files and functions. +4. **What to avoid**: Approaches that would be wrong, overly complex, or against the project's conventions. +5. **Offer**: End with: _"Reply `@claude` followed by your instructions if you'd like me to open a PR for this."_ + +### Guidelines + +- Be concise and specific. Reference actual file paths. +- If the issue is unclear or missing information, ask clarifying questions instead of guessing. +- If it's a duplicate, out of scope, or `wontfix` material, explain why politely and suggest closing. +- If it's a question (not a bug or feature), answer it directly. +- Follow the project's conventions from the development context when discussing the approach. +- Don't promise timelines or complexity estimates. +- Don't label or assign the issue unless explicitly asked. diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..96d68eae4 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,3 @@ +FROM mcr.microsoft.com/devcontainers/base:debian +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ +RUN curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d395c7d6a..aae1a8828 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,15 +1,19 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/python { - "name": "Python 3", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", - // Features to add to the dev container. More info: https://containers.dev/features. - "features": { - "ghcr.io/devcontainers-contrib/features/hatch:2": {} - }, - // Use 'initializeCommand' to run commands before anything else: - "initializeCommand": "git submodule update --init", - // Use 'onCreateCommand' to run commands after the container is created inside the container: - "onCreateCommand": "hatch env create default && hatch env create docs", + "name": "rendercv", + "build": { "dockerfile": "Dockerfile" }, + "postCreateCommand": "uv sync --all-extras", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-python.debugpy", + "charliermarsh.ruff", + "ms-python.black-formatter", + "astral-sh.ty", + "tombi-toml.tombi", + "redhat.vscode-yaml", + "myriad-dreamin.tinymist" + ] + } + } } diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 04f085e7e..1b72b86da 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1 +1 @@ -All contributions to RenderCV are welcome! To get started, please read [the developer guide](https://docs.rendercv.com/developer_guide). +All contributions to RenderCV are welcome! To get started, please read [the developer guide](https://docs.rendercv.com/developer_guide). diff --git a/.github/FUNDING.yaml b/.github/FUNDING.yaml index 4552d4bbd..ecc12cc79 100644 --- a/.github/FUNDING.yaml +++ b/.github/FUNDING.yaml @@ -1 +1 @@ -ko_fi: atalays \ No newline at end of file +ko_fi: atalays diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index c58a98805..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve RenderCV -title: '' -labels: bug -assignees: sinaatalay ---- - -**Describe the bug** -A clear and concise description of what the bug is and what you expected to happen. - -**To Reproduce** -Please provide a minimal YAML input as a code block for us to produce the same error. - -**Screenshots** -If applicable, add screenshots to help explain your problem. diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md deleted file mode 100644 index fe0ea0098..000000000 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: Enhancement -about: Suggest an idea for RenderCV -title: '' -labels: enhancement -assignees: sinaatalay ---- - -A clear and concise description of what you want to happen. diff --git a/.github/ISSUE_TEMPLATE/improvement_to_documentation.md b/.github/ISSUE_TEMPLATE/improvement_to_documentation.md deleted file mode 100644 index a710b2471..000000000 --- a/.github/ISSUE_TEMPLATE/improvement_to_documentation.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: Improvement to the documentation -about: Create a report to help us improve the documentation of RenderCV -title: '' -labels: documentation -assignees: sinaatalay ---- - -A clear and concise description of what you want to add, improve, or fix in the documentation. diff --git a/.github/dependabot.yml b/.github/dependabot.yaml similarity index 100% rename from .github/dependabot.yml rename to .github/dependabot.yaml diff --git a/.github/workflows/claude-issue-triage.yaml b/.github/workflows/claude-issue-triage.yaml new file mode 100644 index 000000000..ed5c7dc2c --- /dev/null +++ b/.github/workflows/claude-issue-triage.yaml @@ -0,0 +1,28 @@ +name: Claude Issue Triage + +# When a new issue is opened, Claude analyzes it and posts a comment. +# Requires: Install the Claude GitHub App (https://github.com/apps/claude) +# and add ANTHROPIC_API_KEY to repository secrets. + +on: + issues: + types: [opened] + +jobs: + triage: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + id-token: write + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + claude_args: "--model claude-sonnet-4-6" + prompt: | + Follow .claude/skills/triage-rendercv-issue/SKILL.md to triage issue #${{ github.event.issue.number }}. diff --git a/.github/workflows/claude-pr-review.yaml b/.github/workflows/claude-pr-review.yaml new file mode 100644 index 000000000..ebefcebb6 --- /dev/null +++ b/.github/workflows/claude-pr-review.yaml @@ -0,0 +1,41 @@ +name: Claude PR Review + +# When a PR is opened or updated, Claude reviews it. +# Requires: Install the Claude GitHub App (https://github.com/apps/claude) +# and add ANTHROPIC_API_KEY to repository secrets. + +on: + pull_request: + types: [opened, ready_for_review] + +jobs: + review: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write + id-token: write + steps: + - uses: actions/checkout@v6 + with: + submodules: true + fetch-depth: 0 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.13" + + - name: Install just + uses: taiki-e/install-action@just + + - name: Install dependencies + run: just sync + + - uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + claude_args: "--model claude-sonnet-4-6" + prompt: | + Follow .claude/skills/review-rendercv-pr/SKILL.md to review PR #${{ github.event.pull_request.number }}. diff --git a/.github/workflows/claude.yaml b/.github/workflows/claude.yaml new file mode 100644 index 000000000..c84f621ff --- /dev/null +++ b/.github/workflows/claude.yaml @@ -0,0 +1,51 @@ +name: Claude + +# Interactive Claude: responds to @claude mentions in issue and PR comments. +# This is how you tell Claude "okay, make a PR" or give follow-up instructions. +# Requires: Install the Claude GitHub App (https://github.com/apps/claude) +# and add ANTHROPIC_API_KEY to repository secrets. + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write + id-token: write + actions: read + steps: + - uses: actions/checkout@v6 + with: + submodules: true + fetch-depth: 0 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.13" + + - name: Install just + uses: taiki-e/install-action@just + + - name: Install dependencies + run: just sync + + - uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + claude_args: "--model claude-sonnet-4-6" + prompt: | + Use the skills in .claude/skills/ as appropriate. For fixing issues, follow .claude/skills/solve-rendercv-issue/SKILL.md. For reviewing PRs, follow .claude/skills/review-rendercv-pr/SKILL.md. diff --git a/.github/workflows/create-executables.yaml b/.github/workflows/create-executables.yaml index 3afc468e5..7f48d1b9e 100644 --- a/.github/workflows/create-executables.yaml +++ b/.github/workflows/create-executables.yaml @@ -1,8 +1,8 @@ name: Create executables -# GitHub events that triggers the workflow: +# GitHub events that trigger the workflow: on: - workflow_call: # to make the workflow triggerable from other workflows (publish-a-release.yaml) + workflow_call: # to make the workflow triggerable from other workflows workflow_dispatch: # to make the workflow triggerable manually jobs: @@ -19,17 +19,19 @@ jobs: contents: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - - name: Install Hatch - uses: pypa/hatch@install + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Install just + uses: taiki-e/install-action@just - name: Create executable - run: | - hatch run exe:create + run: just create-executable - name: Upload executable as an artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: rendercv-${{ matrix.os }} path: bin/* diff --git a/.github/workflows/deploy-docs.yaml b/.github/workflows/deploy-docs.yaml index bab496ba6..6168bf06d 100644 --- a/.github/workflows/deploy-docs.yaml +++ b/.github/workflows/deploy-docs.yaml @@ -22,26 +22,26 @@ concurrency: cancel-in-progress: false jobs: - # Build job build: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - - name: Install Hatch - uses: pypa/hatch@install + - name: Install uv + uses: astral-sh/setup-uv@v7 - - name: Build the website - run: | - hatch run docs:build + - name: Install just + uses: taiki-e/install-action@just + + - name: Build docs + run: just build-docs - name: Upload the website as an artifact uses: actions/upload-pages-artifact@v4 with: path: site - # Deployment job deploy: name: Deploy environment: diff --git a/.github/workflows/publish-a-release.yaml b/.github/workflows/release.yaml similarity index 53% rename from .github/workflows/publish-a-release.yaml rename to .github/workflows/release.yaml index f63663d98..1b213c52a 100644 --- a/.github/workflows/publish-a-release.yaml +++ b/.github/workflows/release.yaml @@ -1,89 +1,103 @@ name: Publish a release -# GitHub events that triggers the workflow: +# GitHub events that trigger the workflow: on: release: types: - published - jobs: - call_test_workflow: + test: name: Run Tests uses: ./.github/workflows/test.yaml - call_create_executables_workflow: + # update_files: + # name: Update schema.json, examples, and entry figures + # uses: ./.github/workflows/update-files.yaml + # needs: + # - test + + build: + name: Build Package + needs: + - test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Check if the release tag matches the version + uses: samuelcolvin/check-python-version@v5 + with: + version_file_path: src/rendercv/__init__.py + + - name: Build + run: uv build + + - name: Upload the wheel and source distribution as artifacts + uses: actions/upload-artifact@v7 + with: + name: dist + path: dist + + create_executables: name: Create Executables needs: - - call_test_workflow + - test uses: ./.github/workflows/create-executables.yaml - upload_release_files: - name: Create release files + create_github_release: + name: Add assets to the GitHub Release needs: - - call_create_executables_workflow + - build + - create_executables runs-on: ubuntu-latest permissions: contents: write steps: - name: Download the executables - uses: actions/download-artifact@v5 - - - name: Checkout the repository - uses: actions/checkout@v5 - - - uses: actions/checkout@v5 - - - name: Install Hatch - uses: pypa/hatch@install + uses: actions/download-artifact@v8 + with: + pattern: rendercv-* + merge-multiple: false - - name: Build - run: | - hatch build + - name: Download the build artifacts + uses: actions/download-artifact@v8 + with: + name: dist + path: dist - - name: Upload the executables as release assets + - name: Add assets to the GitHub release uses: softprops/action-gh-release@v2 with: files: | - rendercv-*/rendercv-linux-ARM64 - rendercv-*/rendercv-linux-x86_64 - rendercv-*/rendercv-macos-ARM64 - rendercv-*/rendercv-windows-x86_64.exe - dist/rendercv-*.tar.gz + rendercv-*/rendercv-linux-ARM64.zip + rendercv-*/rendercv-linux-x86_64.zip + rendercv-*/rendercv-macos-ARM64.zip + rendercv-*/rendercv-windows-x86_64.zip dist/rendercv-*.whl publish_to_pypi: name: Publish to PyPI needs: - - upload_release_files + - create_github_release runs-on: ubuntu-latest environment: release permissions: id-token: write steps: - - uses: actions/checkout@v5 - - - name: Install Hatch - uses: pypa/hatch@install - - - name: Check if the release tag matches the version - uses: samuelcolvin/check-python-version@v5 + - name: Download the build artifacts + uses: actions/download-artifact@v8 with: - version_file_path: rendercv/__init__.py - - - name: Build - run: | - hatch build + name: dist + path: dist - name: Upload package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - - name: Upload the wheel and source distribution as artifacts - uses: actions/upload-artifact@v4 - with: - path: dist - - publish_to_dockerhub_and_ghcr: - name: Push Docker image to Docker Hub and GitHub Container Registry + publish_docker_to_ghcr: + name: Push Docker image to GitHub Container Registry runs-on: ubuntu-latest needs: - publish_to_pypi @@ -94,13 +108,7 @@ jobs: id-token: write steps: - name: Check out the repo - uses: actions/checkout@v5 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + uses: actions/checkout@v6 - name: Log in to the Container registry uses: docker/login-action@v3 @@ -114,28 +122,24 @@ jobs: uses: docker/metadata-action@v5 with: images: | - rendercv/rendercv ghcr.io/${{ github.repository }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + - name: Build and push Docker images id: push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 - name: Generate artifact attestation - uses: actions/attest-build-provenance@v3 + uses: actions/attest-build-provenance@v4 with: - subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} + subject-name: ghcr.io/${{ github.repository }} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true - - call_update_files_workflow: - name: Update files - uses: ./.github/workflows/update-files.yaml - needs: - - publish_to_dockerhub_and_ghcr - - publish_to_pypi diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 17a699c53..a031425b5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,18 +1,42 @@ name: Test -# GitHub events that triggers the workflow: +# GitHub events that trigger the workflow: on: push: branches: - - "*" + - main pull_request: branches: - "*" - workflow_call: # to make the workflow triggerable from other workflows (publish-to-pypi.yaml) + workflow_call: # to make the workflow triggerable from other workflows workflow_dispatch: # to make the workflow triggerable manually # The workflow: jobs: + prek: + name: Run pre-commit checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.13" + + - name: Install just + uses: taiki-e/install-action@just + + - name: Install the project + run: just sync + + - name: Run prek + uses: j178/prek-action@v1 + with: + extra-args: --all-files + test: name: Test with Py${{ matrix.python-version }} on ${{ matrix.os }} @@ -20,60 +44,67 @@ jobs: fail-fast: false matrix: os: [ubuntu, windows, macos] - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.12", "3.13", "3.14"] runs-on: ${{ matrix.os }}-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 + with: + submodules: true - - name: Install Hatch - uses: pypa/hatch@install + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + python-version: ${{ matrix.python-version }} + + - name: Install just + uses: taiki-e/install-action@just - name: Test - run: hatch run test.py${{ matrix.python-version }}:test-and-report + run: just test-coverage - name: Rename the coverage file run: mv .coverage .coverage.${{ matrix.python-version }}.${{ matrix.os }} - name: Upload coverage as an artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: include-hidden-files: true name: coverage-${{ matrix.python-version }}-${{ matrix.os }} path: .coverage.${{ matrix.python-version }}.${{ matrix.os }} report-coverage: - # Run only if the workflow was triggered by a push event - if: github.event_name == 'push' name: Generate the coverage report needs: [test] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Download coverage files - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: pattern: coverage-* # download all the uploaded coverage reports path: coverage merge-multiple: true # download them in the same folder - - name: Install Hatch - uses: pypa/hatch@install + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Install just + uses: taiki-e/install-action@just - name: Combine coverage files run: | - hatch run coverage combine coverage - hatch run coverage report - hatch run coverage html --show-contexts --title "RenderCV coverage for ${{ github.sha }}" + uv run --frozen coverage combine coverage + uv run --frozen coverage report + uv run --frozen coverage html --show-contexts --title "RenderCV coverage for ${{ github.sha }}" - name: Upload the coverage report to smokeshow - run: | - pip install smokeshow==0.4.0 - smokeshow upload ./htmlcov + run: uv tool run smokeshow==0.4.0 upload ./htmlcov env: - SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} + SMOKESHOW_GITHUB_STATUS_DESCRIPTION: ${{ github.event_name != 'pull_request' && 'Coverage {coverage-percentage}' || '' }} SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 90 + SMOKESHOW_GITHUB_CONTEXT: coverage SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/update-files.yaml b/.github/workflows/update-files.yaml deleted file mode 100644 index c30d2e9f8..000000000 --- a/.github/workflows/update-files.yaml +++ /dev/null @@ -1,50 +0,0 @@ -name: Update files - -# GitHub events that triggers the workflow: -on: - workflow_call: # to make the workflow triggerable from other workflows (publish-a-release.yaml) - workflow_dispatch: # to make the workflow triggerable manually - -jobs: - update_files: - name: Update schema.json, examples, and entry figures - runs-on: ubuntu-latest - - permissions: - contents: write - - steps: - - uses: actions/checkout@v5 - - - name: Install Hatch - uses: pypa/hatch@install - - - name: Set Git credentials - run: | - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "GitHub Actions [Bot]" - - - name: Update schema.json - continue-on-error: true - run: | - hatch run update-schema - git add schema.json - - - name: Update `examples` folder - continue-on-error: true - run: | - hatch run update-examples - git add examples/* - git add docs/assets/images/*.png - - - name: Update entry figures - continue-on-error: true - run: | - hatch run docs:update-entry-figures - git add docs/assets/images/**/*.png - - - name: Push changes - continue-on-error: true - run: | - git commit -m "Update schema.json, examples, and entry figures" - git push origin HEAD:main diff --git a/.gitignore b/.gitignore index f493f6878..eaf41ca70 100644 --- a/.gitignore +++ b/.gitignore @@ -165,10 +165,7 @@ cython_debug/ # VSCode .vscode/ -# Personal CVs -*_CV.yaml -*_cv.py -*_CV.typ +# RenderCV output rendercv_output/ # Include reference files @@ -189,4 +186,15 @@ rendercv_output/ render_command.prof # Executables: -bin/ \ No newline at end of file +bin/ + +# Coverage: +coverage.md + +# MkDocs Material: +mkdocs-material/ + +# ATS proof generated artifacts (reproducible via scripts/ats_proof/run_all.py): +scripts/ats_proof/rendered/ +scripts/ats_proof/results/ +scripts/ats_proof/analysis/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..56f86416e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule ".claude/skills/rendercv-skill"] + path = .claude/skills/rendercv-skill + url = https://github.com/rendercv/rendercv-skill.git +[submodule "src/rendercv/renderer/typst_fontawesome"] + path = src/rendercv/renderer/typst_fontawesome + url = https://github.com/duskmoon314/typst-fontawesome.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 00fe25a57..4b8d7d717 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,27 @@ repos: - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.2 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 hooks: - # Run the linter. - - id: ruff + - id: check-added-large-files + - id: check-toml - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell + args: + - --skip=src/rendercv/schema/models/locale/other_locales/* + - --exclude-file=schema.json + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.7 + hooks: + - id: ruff-check + - id: ruff-format + - repo: local + hooks: + - id: ty-check + name: ty-check + language: python + entry: ty check src tests + pass_filenames: false + args: [--python=.venv/] + additional_dependencies: ["ty>=0.0.24"] diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..3971f2b03 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,87 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What is RenderCV? + +RenderCV is a resume/CV builder for academics and engineers. Users write CVs in YAML, and RenderCV produces PDFs with professional typography. It supports multiple themes (classic, moderncv, sb2nov, engineeringresumes, engineeringclassic) and custom themes. Deployed at rendercv.com as a web app. + +## Core Pipeline + +``` +YAML → (ruamel.yaml) → Python dict → (pydantic) → RenderCVModel → (jinja2) → Typst file → (typst) → PDF +``` + +1. **Parse**: `ruamel.yaml` reads YAML into Python dicts (`schema/yaml_reader.py`) +2. **Validate**: `pydantic` validates into `RenderCVModel` (`schema/models/rendercv_model.py`) +3. **Template**: `jinja2` renders Typst templates with model data (`renderer/templater/templater.py`) +4. **Compile**: `typst` Python bindings compile `.typ` to PDF (`renderer/pdf_png.py`) + +Markdown in YAML fields is converted to Typst syntax via the `markdown` library (`renderer/templater/markdown_parser.py`). + +## Commands + +```bash +just sync # Install dependencies +just test # Run tests (parallel via pytest-xdist) +just check # Lint (ruff) + type-check (ty) + pre-commit hooks +just format # Format (black + ruff) +just update-testdata # Regenerate reference files for tests +just test-coverage # Run tests with coverage report +just update-schema # Regenerate JSON schema +just update-examples # Update example output files +``` + +Run a single test: +```bash +uv run --frozen --all-extras pytest tests/renderer/templater/test_date.py -x +``` + +Run tests matching a keyword: +```bash +uv run --frozen --all-extras pytest -k "test_markdown_to_typst" -x +``` + +## Source Layout + +``` +src/rendercv/ + cli/ # Typer CLI (render, new, create-theme commands) + render_command/run_rendercv.py # Main render orchestration + schema/ # Data models and validation + models/ + cv/ # CV content models (entries, sections) + design/ # Theme models (classic, moderncv, etc.) + locale/ # Localization model + settings/ # Rendering settings + rendercv_model.py # Top-level RenderCVModel + rendercv_model_builder.py # Builds RenderCVModel from YAML with overrides + renderer/ # Output generation + templater/ + templates/typst/ # Jinja2 templates for Typst output (per theme) + templater.py # Jinja2 rendering logic + markdown_parser.py # Markdown → Typst conversion + typst.py # Typst source file generation + pdf_png.py # PDF/PNG compilation + html.py # HTML output + markdown.py # Markdown output +``` + +## Testing + +Tests mirror the source structure: `src/rendercv/renderer/templater/date.py` → `tests/renderer/templater/test_date.py`. + +**Reference file testing**: Renderer tests compare generated output against reference files in test data directories. Use `just update-testdata` to regenerate references when output intentionally changes. + +**Key fixtures** (in `tests/renderer/conftest.py`): +- `minimal_rendercv_model` / `full_rendercv_model` — pre-built models for testing +- `compare_file_with_reference` — compares generated output against reference files + +## Code Conventions + +- **No private API syntax**: Never use underscore-prefixed names (`_Foo`, `_bar`). All names are public. +- **Strict typing**: Every function, variable, and class attribute must have type annotations. +- **Python 3.12+ syntax**: Use `type` statements for aliases, `X | Y` unions, `X | None` for optionals. +- **Docstrings**: Google-style with Why/Args/Returns/Raises sections. +- **`just check` must show zero errors** before committing. +- Use `uv`, never `pip` or `python` directly. diff --git a/Dockerfile b/Dockerfile index 0f9643ea2..d7de38a1d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,49 @@ -# Use the official Python image as a base -FROM python:3.13-slim - -# Install RenderCV: -RUN pip install "rendercv[full]" - -# Create a directory for the app -WORKDIR /rendercv - -# Set the entrypoint to /bin/sh instead of Python -ENTRYPOINT ["/bin/bash"] - +# Use a Python image with uv pre-installed +FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder + +# Install the project into `/app` +WORKDIR /app + +# Enable bytecode compilation +ENV UV_COMPILE_BYTECODE=1 + +# Copy from the cache instead of linking since it's a mounted volume +ENV UV_LINK_MODE=copy + +# Install the project's dependencies using the lockfile and settings +# This layer is cached separately from the project code for faster rebuilds +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-project --no-editable --extra full --no-default-groups + +# Then, add the rest of the project source code and install it +# Installing separately from its dependencies allows optimal layer caching +COPY . /app +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-editable --extra full --no-default-groups + +# Final stage +FROM python:3.12-slim-bookworm + +# Setup a non-root user +RUN groupadd --system --gid 999 rendercv \ + && useradd --system --gid 999 --uid 999 --create-home rendercv + +# Set working directory +WORKDIR /app + +# Copy the virtual environment from the builder stage +COPY --from=builder --chown=rendercv:rendercv /app/.venv /app/.venv + +# Place executables in the environment at the front of the path +ENV PATH="/app/.venv/bin:$PATH" + +# Use the non-root user to run our application +USER rendercv + +# Set the entrypoint to the rendercv CLI (installed via pyproject.toml entry point) +ENTRYPOINT ["rendercv"] + +# Default command shows help +CMD ["--help"] diff --git a/README.md b/README.md index d6a633018..ef45a7b0b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

RenderCV

-_The engine of the [RenderCV App](https://rendercv.com)_ +_Resume builder for academics and engineers, deployed at [rendercv.com](https://rendercv.com)_ [![test](https://github.com/rendercv/rendercv/actions/workflows/test.yaml/badge.svg?branch=main)](https://github.com/rendercv/rendercv/actions/workflows/test.yaml) [![coverage](https://coverage-badge.samuelcolvin.workers.dev/rendercv/rendercv.svg)](https://coverage-badge.samuelcolvin.workers.dev/redirect/rendercv/rendercv) @@ -11,68 +11,160 @@ _The engine of the [RenderCV App](https://rendercv.com)_
-RenderCV engine is a Typst-based Python package with a command-line interface (CLI) that allows you to version-control your CV/resume as source code. It reads a CV written in a YAML file with Markdown syntax, converts it into a [Typst](https://typst.app) code, and generates a PDF. +Write your CV or resume as YAML, then run RenderCV, -RenderCV engine's focus is to provide these three features: +```bash +rendercv render John_Doe_CV.yaml +``` + +and get a PDF with perfect typography. -- **Content-first approach:** Users should be able to focus on the content instead of worrying about the formatting. -- **A mechanism to version-control a CV's content and design separately:** The content and design of a CV are separate issues and they should be treated separately. -- **Robustness:** A PDF should be delivered if there aren't any errors. If errors exist, they should be clearly explained along with solutions. +With RenderCV, you can: +- Version-control your CV — it's just text. +- Focus on content — don't worry about the formatting. +- Get perfect typography — consistent alignment and spacing, handled for you. -It takes a YAML file that looks like this: +A YAML file like this: ```yaml cv: name: John Doe - location: Location - email: john.doe@example.com - phone: tel:+1-609-999-9995 + location: San Francisco, CA + email: john.doe@email.com + website: https://rendercv.com/ social_networks: - network: LinkedIn - username: john.doe + username: rendercv - network: GitHub - username: john.doe + username: rendercv sections: - welcome_to_RenderCV!: - - '[RenderCV](https://rendercv.com) is a Typst-based CV - framework designed for academics and engineers, with Markdown - syntax support.' - - Each section title is arbitrary. Each section contains - a list of entries, and there are 7 different entry types - to choose from. + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + - See the [documentation](https://docs.rendercv.com) for more details. education: - - institution: Stanford University + - institution: Princeton University area: Computer Science degree: PhD - location: Stanford, CA, USA - start_date: 2023-09 - end_date: present + date: + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ + summary: highlights: - - Working on the optimization of autonomous vehicles - in urban environments + - "Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment" + - "Advisor: Prof. Sanjeev Arora" + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) ... ``` -Then, it produces one of these PDFs with its corresponding Typst file, Markdown file, HTML file, and images as PNGs. Click on the images below to preview PDF files. +becomes one of these PDFs. Click on the images to preview. + +| [![Classic Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/classic.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_ClassicTheme_CV.pdf) | [![Engineeringresumes Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/engineeringresumes.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_EngineeringresumesTheme_CV.pdf) | [![Sb2nov Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/sb2nov.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_Sb2novTheme_CV.pdf) | +| --- | --- | --- | +| [![Moderncv Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/moderncv.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_ModerncvTheme_CV.pdf) | [![Engineeringclassic Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/engineeringclassic.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_EngineeringclassicTheme_CV.pdf) | [![Harvard Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/harvard.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_HarvardTheme_CV.pdf) | +| [![Ink Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/ink.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_InkTheme_CV.pdf) | [![Opal Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/opal.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_OpalTheme_CV.pdf) | [![Ember Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/ember.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_EmberTheme_CV.pdf) | + + +## JSON Schema + +RenderCV's JSON Schema lets you fill out the YAML interactively, with autocompletion and inline documentation. + +![JSON Schema of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/json_schema.gif) + + +## Extensive Design Options + +You have full control over every detail. + +```yaml +design: + theme: classic + page: + size: us-letter + top_margin: 0.7in + bottom_margin: 0.7in + left_margin: 0.7in + right_margin: 0.7in + show_footer: true + show_top_note: true + colors: + body: rgb(0, 0, 0) + name: rgb(0, 79, 144) + headline: rgb(0, 79, 144) + connections: rgb(0, 79, 144) + section_titles: rgb(0, 79, 144) + links: rgb(0, 79, 144) + footer: rgb(128, 128, 128) + top_note: rgb(128, 128, 128) + typography: + line_spacing: 0.6em + alignment: justified + date_and_location_column_alignment: right + font_family: Source Sans 3 + # ...and much more +``` + +![Design Options of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/design_options.gif) + +> [!TIP] +> Want to set up a live preview environment like the one shown above? See [how to set up VS Code for RenderCV](https://docs.rendercv.com/user_guide/how_to/set_up_vs_code_for_rendercv). + +## Strict Validation + +No surprises. If something's wrong, you'll know exactly what and where. If it's valid, you get a perfect PDF. + +![Strict Validation Feature of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/validation.gif) + + +## Any Language + +Fill out the locale field for your language. + +```yaml +locale: + language: english + last_updated: Last updated in + month: month + months: months + year: year + years: years + present: present + month_abbreviations: + - Jan + - Feb + - Mar + ... +``` + +## AI Agent Skill -| [![Classic Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/classic.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_ClassicTheme_CV.pdf) | [![Sb2nov Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/sb2nov.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_Sb2novTheme_CV.pdf) | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| [![Moderncv Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/moderncv.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_ModerncvTheme_CV.pdf) | [![Engineeringresumes Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/engineeringresumes.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_EngineeringresumesTheme_CV.pdf) | -| [![Engineeringclassic Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/engineeringclassic.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_EngineeringclassicTheme_CV.pdf) | ![Custom themes can be added.](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/customtheme.png) | +Let AI coding agents create and edit your CV. Install the RenderCV skill: -RenderCV comes with a JSON Schema so that the YAML input file can be filled out interactively. +```bash +npx skills add rendercv/rendercv-skill +``` -![JSON Schema of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/schema.gif) +Works with any AI agent that supports the [skills standard](https://skills.sh). The skill is [auto-generated](https://github.com/rendercv/rendercv/blob/main/scripts/rendercv_skill/generate.py) from RenderCV's source code and [evaluated](https://github.com/rendercv/rendercv/tree/main/scripts/rendercv_skill/evals) with promptfoo against RenderCV's own Pydantic validation pipeline. See the [documentation](https://docs.rendercv.com/user_guide/how_to/use_the_ai_agent_skill) for details. -## Getting Started +## Get Started -RenderCV engine is very easy to install (`pip install "rendercv[full]"`) and easy to use (`rendercv new "John Doe"`). Follow the [user guide](https://docs.rendercv.com/user_guide) to get started. +Install RenderCV (Requires Python 3.12+): -## Motivation +``` +pip install "rendercv[full]" +``` + +Create a new CV yaml file: + +``` +rendercv new "John Doe" +``` -We are developing a [purpose-built app](https://rendercv.com) for writing CVs and resumes that will be available on mobile and web. This Python project is the foundation of that app. Check out [our blog post](https://rendercv.com/introducing-rendercv/) to learn more about why one would use such an app. +Edit the YAML, then render: -## Contributing +``` +rendercv render "John_Doe_CV.yaml" +``` -All contributions to RenderCV are welcome! To get started, please read [the developer guide](https://docs.rendercv.com/developer_guide). +For more details, see the [user guide](https://docs.rendercv.com/user_guide/). diff --git a/docs/CNAME b/docs/CNAME index f02c33086..ac92fae4d 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -docs.rendercv.com \ No newline at end of file +docs.rendercv.com diff --git a/docs/api_reference/api_reference.py b/docs/api_reference/api_reference.py new file mode 100644 index 000000000..9a6d4af2f --- /dev/null +++ b/docs/api_reference/api_reference.py @@ -0,0 +1,37 @@ +from pathlib import Path + +import mkdocs_gen_files + +nav = mkdocs_gen_files.Nav() + +repository_root = Path(__file__).parent.parent.parent +api_reference = repository_root / "docs" / "api_reference" +src_rendercv = repository_root / "src" / "rendercv" +nav[("src", "rendercv")] = "index.md" + +# Process each Python file in the objects directory +for path in sorted(src_rendercv.rglob("*.py")): + # Skip __init__.py files and __main__.py files + if path.name in ("__init__.py", "__main__.py"): + continue + + # Get the relative path from the objects directory + module_path = path.relative_to(src_rendercv).with_suffix("") + doc_path = module_path.with_suffix(".md") + parts = (f"rendercv.{module_path.parts[0]}", *module_path.parts[1:]) + + # Add to navigation + nav[("src", *parts)] = doc_path.as_posix() + + # Generate the documentation page + with mkdocs_gen_files.open(f"api_reference/{doc_path}", "w") as fd: + module_ident = "rendercv." + ".".join(module_path.parts) + fd.write(f"::: {module_ident}\n") + + # Set the edit path to the actual source file + # mkdocs_gen_files.set_edit_path(full_doc_path, full_doc_path.relative_to(docs_path)) + + +# Write the navigation file +with mkdocs_gen_files.open("api_reference/SUMMARY.md", "w") as nav_file: + nav_file.writelines(nav.build_literate_nav()) diff --git a/docs/api_reference/index.md b/docs/api_reference/index.md new file mode 100644 index 000000000..665880664 --- /dev/null +++ b/docs/api_reference/index.md @@ -0,0 +1,7 @@ +# API Reference + +RenderCV is a CLI application, not a library. Its internal API is not guaranteed to be stable and may change without notice. However, for those who wish to use RenderCV programmatically in Python scripts, the complete API reference is provided here. + +::: rendercv + options: + heading_level: 2 diff --git a/docs/assets/images/classic.png b/docs/assets/images/classic.png deleted file mode 100644 index 66b4f9c68..000000000 Binary files a/docs/assets/images/classic.png and /dev/null differ diff --git a/docs/assets/images/classic/bullet_entry.png b/docs/assets/images/classic/bullet_entry.png index 9f73f62f1..7d226ea42 100644 Binary files a/docs/assets/images/classic/bullet_entry.png and b/docs/assets/images/classic/bullet_entry.png differ diff --git a/docs/assets/images/classic/classic.png b/docs/assets/images/classic/classic.png new file mode 100644 index 000000000..5db5a3ad1 Binary files /dev/null and b/docs/assets/images/classic/classic.png differ diff --git a/docs/assets/images/classic/education_entry.png b/docs/assets/images/classic/education_entry.png index 3354c5c95..db8841200 100644 Binary files a/docs/assets/images/classic/education_entry.png and b/docs/assets/images/classic/education_entry.png differ diff --git a/docs/assets/images/classic/experience_entry.png b/docs/assets/images/classic/experience_entry.png index 5150964a1..23f03ec12 100644 Binary files a/docs/assets/images/classic/experience_entry.png and b/docs/assets/images/classic/experience_entry.png differ diff --git a/docs/assets/images/classic/normal_entry.png b/docs/assets/images/classic/normal_entry.png index 8a2901696..f3caa66d9 100644 Binary files a/docs/assets/images/classic/normal_entry.png and b/docs/assets/images/classic/normal_entry.png differ diff --git a/docs/assets/images/classic/numbered_entry.png b/docs/assets/images/classic/numbered_entry.png index a4b56400c..f34fd5cf4 100644 Binary files a/docs/assets/images/classic/numbered_entry.png and b/docs/assets/images/classic/numbered_entry.png differ diff --git a/docs/assets/images/classic/one_line_entry.png b/docs/assets/images/classic/one_line_entry.png index 0d98e455f..de1edbccd 100644 Binary files a/docs/assets/images/classic/one_line_entry.png and b/docs/assets/images/classic/one_line_entry.png differ diff --git a/docs/assets/images/classic/publication_entry.png b/docs/assets/images/classic/publication_entry.png index e451b79a7..872974eb4 100644 Binary files a/docs/assets/images/classic/publication_entry.png and b/docs/assets/images/classic/publication_entry.png differ diff --git a/docs/assets/images/classic/reversed_numbered_entry.png b/docs/assets/images/classic/reversed_numbered_entry.png index ee69e518e..0071683dc 100644 Binary files a/docs/assets/images/classic/reversed_numbered_entry.png and b/docs/assets/images/classic/reversed_numbered_entry.png differ diff --git a/docs/assets/images/classic/text_entry.png b/docs/assets/images/classic/text_entry.png index 9a0e80a97..479be983e 100644 Binary files a/docs/assets/images/classic/text_entry.png and b/docs/assets/images/classic/text_entry.png differ diff --git a/docs/assets/images/customtheme.png b/docs/assets/images/customtheme.png index 95744d354..d6435dbe8 100644 Binary files a/docs/assets/images/customtheme.png and b/docs/assets/images/customtheme.png differ diff --git a/docs/assets/images/design_options.gif b/docs/assets/images/design_options.gif new file mode 100644 index 000000000..3feda7546 Binary files /dev/null and b/docs/assets/images/design_options.gif differ diff --git a/docs/assets/images/ember/bullet_entry.png b/docs/assets/images/ember/bullet_entry.png new file mode 100644 index 000000000..41a5e0a85 Binary files /dev/null and b/docs/assets/images/ember/bullet_entry.png differ diff --git a/docs/assets/images/ember/education_entry.png b/docs/assets/images/ember/education_entry.png new file mode 100644 index 000000000..bcdaa3fb9 Binary files /dev/null and b/docs/assets/images/ember/education_entry.png differ diff --git a/docs/assets/images/ember/experience_entry.png b/docs/assets/images/ember/experience_entry.png new file mode 100644 index 000000000..cce51a5f4 Binary files /dev/null and b/docs/assets/images/ember/experience_entry.png differ diff --git a/docs/assets/images/ember/normal_entry.png b/docs/assets/images/ember/normal_entry.png new file mode 100644 index 000000000..9045b9bfa Binary files /dev/null and b/docs/assets/images/ember/normal_entry.png differ diff --git a/docs/assets/images/ember/numbered_entry.png b/docs/assets/images/ember/numbered_entry.png new file mode 100644 index 000000000..228ecbe98 Binary files /dev/null and b/docs/assets/images/ember/numbered_entry.png differ diff --git a/docs/assets/images/ember/one_line_entry.png b/docs/assets/images/ember/one_line_entry.png new file mode 100644 index 000000000..b19c89458 Binary files /dev/null and b/docs/assets/images/ember/one_line_entry.png differ diff --git a/docs/assets/images/ember/publication_entry.png b/docs/assets/images/ember/publication_entry.png new file mode 100644 index 000000000..b44519150 Binary files /dev/null and b/docs/assets/images/ember/publication_entry.png differ diff --git a/docs/assets/images/ember/reversed_numbered_entry.png b/docs/assets/images/ember/reversed_numbered_entry.png new file mode 100644 index 000000000..d27153e22 Binary files /dev/null and b/docs/assets/images/ember/reversed_numbered_entry.png differ diff --git a/docs/assets/images/ember/text_entry.png b/docs/assets/images/ember/text_entry.png new file mode 100644 index 000000000..ab36dab16 Binary files /dev/null and b/docs/assets/images/ember/text_entry.png differ diff --git a/docs/assets/images/engineeringclassic.png b/docs/assets/images/engineeringclassic.png deleted file mode 100644 index 6713312b4..000000000 Binary files a/docs/assets/images/engineeringclassic.png and /dev/null differ diff --git a/docs/assets/images/engineeringclassic/bullet_entry.png b/docs/assets/images/engineeringclassic/bullet_entry.png index b50fd820c..49fc85a16 100644 Binary files a/docs/assets/images/engineeringclassic/bullet_entry.png and b/docs/assets/images/engineeringclassic/bullet_entry.png differ diff --git a/docs/assets/images/engineeringclassic/education_entry.png b/docs/assets/images/engineeringclassic/education_entry.png index abab0ef47..c93710b6f 100644 Binary files a/docs/assets/images/engineeringclassic/education_entry.png and b/docs/assets/images/engineeringclassic/education_entry.png differ diff --git a/docs/assets/images/engineeringclassic/engineeringclassic.png b/docs/assets/images/engineeringclassic/engineeringclassic.png new file mode 100644 index 000000000..7e834e979 Binary files /dev/null and b/docs/assets/images/engineeringclassic/engineeringclassic.png differ diff --git a/docs/assets/images/engineeringclassic/experience_entry.png b/docs/assets/images/engineeringclassic/experience_entry.png index be21a7858..1fce0cce6 100644 Binary files a/docs/assets/images/engineeringclassic/experience_entry.png and b/docs/assets/images/engineeringclassic/experience_entry.png differ diff --git a/docs/assets/images/engineeringclassic/normal_entry.png b/docs/assets/images/engineeringclassic/normal_entry.png index 13c82b019..7c412d047 100644 Binary files a/docs/assets/images/engineeringclassic/normal_entry.png and b/docs/assets/images/engineeringclassic/normal_entry.png differ diff --git a/docs/assets/images/engineeringclassic/numbered_entry.png b/docs/assets/images/engineeringclassic/numbered_entry.png index cbd942827..571403eb0 100644 Binary files a/docs/assets/images/engineeringclassic/numbered_entry.png and b/docs/assets/images/engineeringclassic/numbered_entry.png differ diff --git a/docs/assets/images/engineeringclassic/one_line_entry.png b/docs/assets/images/engineeringclassic/one_line_entry.png index 54225317f..a49cf102a 100644 Binary files a/docs/assets/images/engineeringclassic/one_line_entry.png and b/docs/assets/images/engineeringclassic/one_line_entry.png differ diff --git a/docs/assets/images/engineeringclassic/publication_entry.png b/docs/assets/images/engineeringclassic/publication_entry.png index d524059a7..c754fd3ad 100644 Binary files a/docs/assets/images/engineeringclassic/publication_entry.png and b/docs/assets/images/engineeringclassic/publication_entry.png differ diff --git a/docs/assets/images/engineeringclassic/reversed_numbered_entry.png b/docs/assets/images/engineeringclassic/reversed_numbered_entry.png index 21a37fff1..2d4e61bfd 100644 Binary files a/docs/assets/images/engineeringclassic/reversed_numbered_entry.png and b/docs/assets/images/engineeringclassic/reversed_numbered_entry.png differ diff --git a/docs/assets/images/engineeringclassic/text_entry.png b/docs/assets/images/engineeringclassic/text_entry.png index 603777a6b..dab40e244 100644 Binary files a/docs/assets/images/engineeringclassic/text_entry.png and b/docs/assets/images/engineeringclassic/text_entry.png differ diff --git a/docs/assets/images/engineeringresumes.png b/docs/assets/images/engineeringresumes.png deleted file mode 100644 index 0aeba42ab..000000000 Binary files a/docs/assets/images/engineeringresumes.png and /dev/null differ diff --git a/docs/assets/images/engineeringresumes/bullet_entry.png b/docs/assets/images/engineeringresumes/bullet_entry.png index eddd2a70a..db38617f1 100644 Binary files a/docs/assets/images/engineeringresumes/bullet_entry.png and b/docs/assets/images/engineeringresumes/bullet_entry.png differ diff --git a/docs/assets/images/engineeringresumes/education_entry.png b/docs/assets/images/engineeringresumes/education_entry.png index 5f67cdfbe..d5c80d768 100644 Binary files a/docs/assets/images/engineeringresumes/education_entry.png and b/docs/assets/images/engineeringresumes/education_entry.png differ diff --git a/docs/assets/images/engineeringresumes/engineeringresumes.png b/docs/assets/images/engineeringresumes/engineeringresumes.png new file mode 100644 index 000000000..89d8ea5ab Binary files /dev/null and b/docs/assets/images/engineeringresumes/engineeringresumes.png differ diff --git a/docs/assets/images/engineeringresumes/experience_entry.png b/docs/assets/images/engineeringresumes/experience_entry.png index 01af93815..be5ec42af 100644 Binary files a/docs/assets/images/engineeringresumes/experience_entry.png and b/docs/assets/images/engineeringresumes/experience_entry.png differ diff --git a/docs/assets/images/engineeringresumes/normal_entry.png b/docs/assets/images/engineeringresumes/normal_entry.png index 70be51b5b..01331e9ce 100644 Binary files a/docs/assets/images/engineeringresumes/normal_entry.png and b/docs/assets/images/engineeringresumes/normal_entry.png differ diff --git a/docs/assets/images/engineeringresumes/numbered_entry.png b/docs/assets/images/engineeringresumes/numbered_entry.png index 52bb9c64f..80744badd 100644 Binary files a/docs/assets/images/engineeringresumes/numbered_entry.png and b/docs/assets/images/engineeringresumes/numbered_entry.png differ diff --git a/docs/assets/images/engineeringresumes/one_line_entry.png b/docs/assets/images/engineeringresumes/one_line_entry.png index a215fe886..2c93503e0 100644 Binary files a/docs/assets/images/engineeringresumes/one_line_entry.png and b/docs/assets/images/engineeringresumes/one_line_entry.png differ diff --git a/docs/assets/images/engineeringresumes/publication_entry.png b/docs/assets/images/engineeringresumes/publication_entry.png index f51ace0ae..95b319050 100644 Binary files a/docs/assets/images/engineeringresumes/publication_entry.png and b/docs/assets/images/engineeringresumes/publication_entry.png differ diff --git a/docs/assets/images/engineeringresumes/reversed_numbered_entry.png b/docs/assets/images/engineeringresumes/reversed_numbered_entry.png index f672582df..fcf6ed14d 100644 Binary files a/docs/assets/images/engineeringresumes/reversed_numbered_entry.png and b/docs/assets/images/engineeringresumes/reversed_numbered_entry.png differ diff --git a/docs/assets/images/engineeringresumes/text_entry.png b/docs/assets/images/engineeringresumes/text_entry.png index 5451998ff..fc3a39127 100644 Binary files a/docs/assets/images/engineeringresumes/text_entry.png and b/docs/assets/images/engineeringresumes/text_entry.png differ diff --git a/docs/assets/images/examples/classic.png b/docs/assets/images/examples/classic.png new file mode 100644 index 000000000..3e05ba74e Binary files /dev/null and b/docs/assets/images/examples/classic.png differ diff --git a/docs/assets/images/examples/ember.png b/docs/assets/images/examples/ember.png new file mode 100644 index 000000000..ef4e78234 Binary files /dev/null and b/docs/assets/images/examples/ember.png differ diff --git a/docs/assets/images/examples/engineeringclassic.png b/docs/assets/images/examples/engineeringclassic.png new file mode 100644 index 000000000..a3a49701d Binary files /dev/null and b/docs/assets/images/examples/engineeringclassic.png differ diff --git a/docs/assets/images/examples/engineeringresumes.png b/docs/assets/images/examples/engineeringresumes.png new file mode 100644 index 000000000..66e1104da Binary files /dev/null and b/docs/assets/images/examples/engineeringresumes.png differ diff --git a/docs/assets/images/examples/harvard.png b/docs/assets/images/examples/harvard.png new file mode 100644 index 000000000..f033a28ff Binary files /dev/null and b/docs/assets/images/examples/harvard.png differ diff --git a/docs/assets/images/examples/ink.png b/docs/assets/images/examples/ink.png new file mode 100644 index 000000000..462b33995 Binary files /dev/null and b/docs/assets/images/examples/ink.png differ diff --git a/docs/assets/images/examples/moderncv.png b/docs/assets/images/examples/moderncv.png new file mode 100644 index 000000000..046f7bb4b Binary files /dev/null and b/docs/assets/images/examples/moderncv.png differ diff --git a/docs/assets/images/examples/opal.png b/docs/assets/images/examples/opal.png new file mode 100644 index 000000000..b42f39ddb Binary files /dev/null and b/docs/assets/images/examples/opal.png differ diff --git a/docs/assets/images/examples/sb2nov.png b/docs/assets/images/examples/sb2nov.png new file mode 100644 index 000000000..991e19781 Binary files /dev/null and b/docs/assets/images/examples/sb2nov.png differ diff --git a/docs/assets/images/harvard/bullet_entry.png b/docs/assets/images/harvard/bullet_entry.png new file mode 100644 index 000000000..a4acce271 Binary files /dev/null and b/docs/assets/images/harvard/bullet_entry.png differ diff --git a/docs/assets/images/harvard/education_entry.png b/docs/assets/images/harvard/education_entry.png new file mode 100644 index 000000000..7186699b8 Binary files /dev/null and b/docs/assets/images/harvard/education_entry.png differ diff --git a/docs/assets/images/harvard/experience_entry.png b/docs/assets/images/harvard/experience_entry.png new file mode 100644 index 000000000..61c703bc8 Binary files /dev/null and b/docs/assets/images/harvard/experience_entry.png differ diff --git a/docs/assets/images/harvard/normal_entry.png b/docs/assets/images/harvard/normal_entry.png new file mode 100644 index 000000000..d24c79730 Binary files /dev/null and b/docs/assets/images/harvard/normal_entry.png differ diff --git a/docs/assets/images/harvard/numbered_entry.png b/docs/assets/images/harvard/numbered_entry.png new file mode 100644 index 000000000..19848e87f Binary files /dev/null and b/docs/assets/images/harvard/numbered_entry.png differ diff --git a/docs/assets/images/harvard/one_line_entry.png b/docs/assets/images/harvard/one_line_entry.png new file mode 100644 index 000000000..36d765885 Binary files /dev/null and b/docs/assets/images/harvard/one_line_entry.png differ diff --git a/docs/assets/images/harvard/publication_entry.png b/docs/assets/images/harvard/publication_entry.png new file mode 100644 index 000000000..0fe10cb2e Binary files /dev/null and b/docs/assets/images/harvard/publication_entry.png differ diff --git a/docs/assets/images/harvard/reversed_numbered_entry.png b/docs/assets/images/harvard/reversed_numbered_entry.png new file mode 100644 index 000000000..08255f068 Binary files /dev/null and b/docs/assets/images/harvard/reversed_numbered_entry.png differ diff --git a/docs/assets/images/harvard/text_entry.png b/docs/assets/images/harvard/text_entry.png new file mode 100644 index 000000000..c092366ef Binary files /dev/null and b/docs/assets/images/harvard/text_entry.png differ diff --git a/docs/assets/images/icon.svg b/docs/assets/images/icon.svg deleted file mode 100644 index 6182ad233..000000000 --- a/docs/assets/images/icon.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/assets/images/ink/bullet_entry.png b/docs/assets/images/ink/bullet_entry.png new file mode 100644 index 000000000..fc774c4ed Binary files /dev/null and b/docs/assets/images/ink/bullet_entry.png differ diff --git a/docs/assets/images/ink/education_entry.png b/docs/assets/images/ink/education_entry.png new file mode 100644 index 000000000..31e8555bf Binary files /dev/null and b/docs/assets/images/ink/education_entry.png differ diff --git a/docs/assets/images/ink/experience_entry.png b/docs/assets/images/ink/experience_entry.png new file mode 100644 index 000000000..d2d98bb75 Binary files /dev/null and b/docs/assets/images/ink/experience_entry.png differ diff --git a/docs/assets/images/ink/normal_entry.png b/docs/assets/images/ink/normal_entry.png new file mode 100644 index 000000000..fa0eef388 Binary files /dev/null and b/docs/assets/images/ink/normal_entry.png differ diff --git a/docs/assets/images/ink/numbered_entry.png b/docs/assets/images/ink/numbered_entry.png new file mode 100644 index 000000000..e296904a9 Binary files /dev/null and b/docs/assets/images/ink/numbered_entry.png differ diff --git a/docs/assets/images/ink/one_line_entry.png b/docs/assets/images/ink/one_line_entry.png new file mode 100644 index 000000000..1be43b6c6 Binary files /dev/null and b/docs/assets/images/ink/one_line_entry.png differ diff --git a/docs/assets/images/ink/publication_entry.png b/docs/assets/images/ink/publication_entry.png new file mode 100644 index 000000000..45a931044 Binary files /dev/null and b/docs/assets/images/ink/publication_entry.png differ diff --git a/docs/assets/images/ink/reversed_numbered_entry.png b/docs/assets/images/ink/reversed_numbered_entry.png new file mode 100644 index 000000000..8d318e78e Binary files /dev/null and b/docs/assets/images/ink/reversed_numbered_entry.png differ diff --git a/docs/assets/images/ink/text_entry.png b/docs/assets/images/ink/text_entry.png new file mode 100644 index 000000000..8ca05f32b Binary files /dev/null and b/docs/assets/images/ink/text_entry.png differ diff --git a/docs/assets/images/json_schema.gif b/docs/assets/images/json_schema.gif new file mode 100644 index 000000000..d9d15652f Binary files /dev/null and b/docs/assets/images/json_schema.gif differ diff --git a/docs/assets/images/moderncv.png b/docs/assets/images/moderncv.png deleted file mode 100644 index 6ef90ff6f..000000000 Binary files a/docs/assets/images/moderncv.png and /dev/null differ diff --git a/docs/assets/images/moderncv/bullet_entry.png b/docs/assets/images/moderncv/bullet_entry.png index a4306c433..292495d25 100644 Binary files a/docs/assets/images/moderncv/bullet_entry.png and b/docs/assets/images/moderncv/bullet_entry.png differ diff --git a/docs/assets/images/moderncv/education_entry.png b/docs/assets/images/moderncv/education_entry.png index 388cf9d17..d52d2ecc6 100644 Binary files a/docs/assets/images/moderncv/education_entry.png and b/docs/assets/images/moderncv/education_entry.png differ diff --git a/docs/assets/images/moderncv/experience_entry.png b/docs/assets/images/moderncv/experience_entry.png index f60a144e6..2f29ad029 100644 Binary files a/docs/assets/images/moderncv/experience_entry.png and b/docs/assets/images/moderncv/experience_entry.png differ diff --git a/docs/assets/images/moderncv/moderncv.png b/docs/assets/images/moderncv/moderncv.png new file mode 100644 index 000000000..37c4ba404 Binary files /dev/null and b/docs/assets/images/moderncv/moderncv.png differ diff --git a/docs/assets/images/moderncv/normal_entry.png b/docs/assets/images/moderncv/normal_entry.png index 0cc514564..3aa73ce7c 100644 Binary files a/docs/assets/images/moderncv/normal_entry.png and b/docs/assets/images/moderncv/normal_entry.png differ diff --git a/docs/assets/images/moderncv/numbered_entry.png b/docs/assets/images/moderncv/numbered_entry.png index 419ffbc3b..bcea32336 100644 Binary files a/docs/assets/images/moderncv/numbered_entry.png and b/docs/assets/images/moderncv/numbered_entry.png differ diff --git a/docs/assets/images/moderncv/one_line_entry.png b/docs/assets/images/moderncv/one_line_entry.png index 9775fa7ef..291680d46 100644 Binary files a/docs/assets/images/moderncv/one_line_entry.png and b/docs/assets/images/moderncv/one_line_entry.png differ diff --git a/docs/assets/images/moderncv/publication_entry.png b/docs/assets/images/moderncv/publication_entry.png index b4f014153..e95a80fc9 100644 Binary files a/docs/assets/images/moderncv/publication_entry.png and b/docs/assets/images/moderncv/publication_entry.png differ diff --git a/docs/assets/images/moderncv/reversed_numbered_entry.png b/docs/assets/images/moderncv/reversed_numbered_entry.png index 502aa2c90..6c83a38d9 100644 Binary files a/docs/assets/images/moderncv/reversed_numbered_entry.png and b/docs/assets/images/moderncv/reversed_numbered_entry.png differ diff --git a/docs/assets/images/moderncv/text_entry.png b/docs/assets/images/moderncv/text_entry.png index a7f300053..731b7cc47 100644 Binary files a/docs/assets/images/moderncv/text_entry.png and b/docs/assets/images/moderncv/text_entry.png differ diff --git a/docs/assets/images/opal/bullet_entry.png b/docs/assets/images/opal/bullet_entry.png new file mode 100644 index 000000000..21bc3deb0 Binary files /dev/null and b/docs/assets/images/opal/bullet_entry.png differ diff --git a/docs/assets/images/opal/education_entry.png b/docs/assets/images/opal/education_entry.png new file mode 100644 index 000000000..799b29cb5 Binary files /dev/null and b/docs/assets/images/opal/education_entry.png differ diff --git a/docs/assets/images/opal/experience_entry.png b/docs/assets/images/opal/experience_entry.png new file mode 100644 index 000000000..3e5ea66c2 Binary files /dev/null and b/docs/assets/images/opal/experience_entry.png differ diff --git a/docs/assets/images/opal/normal_entry.png b/docs/assets/images/opal/normal_entry.png new file mode 100644 index 000000000..18649425f Binary files /dev/null and b/docs/assets/images/opal/normal_entry.png differ diff --git a/docs/assets/images/opal/numbered_entry.png b/docs/assets/images/opal/numbered_entry.png new file mode 100644 index 000000000..e84a7816e Binary files /dev/null and b/docs/assets/images/opal/numbered_entry.png differ diff --git a/docs/assets/images/opal/one_line_entry.png b/docs/assets/images/opal/one_line_entry.png new file mode 100644 index 000000000..9e92bf479 Binary files /dev/null and b/docs/assets/images/opal/one_line_entry.png differ diff --git a/docs/assets/images/opal/publication_entry.png b/docs/assets/images/opal/publication_entry.png new file mode 100644 index 000000000..a46373400 Binary files /dev/null and b/docs/assets/images/opal/publication_entry.png differ diff --git a/docs/assets/images/opal/reversed_numbered_entry.png b/docs/assets/images/opal/reversed_numbered_entry.png new file mode 100644 index 000000000..2e05bf94d Binary files /dev/null and b/docs/assets/images/opal/reversed_numbered_entry.png differ diff --git a/docs/assets/images/opal/text_entry.png b/docs/assets/images/opal/text_entry.png new file mode 100644 index 000000000..4c80ef8c0 Binary files /dev/null and b/docs/assets/images/opal/text_entry.png differ diff --git a/docs/assets/images/sb2nov.png b/docs/assets/images/sb2nov.png deleted file mode 100644 index 84abd617d..000000000 Binary files a/docs/assets/images/sb2nov.png and /dev/null differ diff --git a/docs/assets/images/sb2nov/bullet_entry.png b/docs/assets/images/sb2nov/bullet_entry.png index bf482ca34..44c87b92c 100644 Binary files a/docs/assets/images/sb2nov/bullet_entry.png and b/docs/assets/images/sb2nov/bullet_entry.png differ diff --git a/docs/assets/images/sb2nov/education_entry.png b/docs/assets/images/sb2nov/education_entry.png index b7fb92162..c09f3227e 100644 Binary files a/docs/assets/images/sb2nov/education_entry.png and b/docs/assets/images/sb2nov/education_entry.png differ diff --git a/docs/assets/images/sb2nov/experience_entry.png b/docs/assets/images/sb2nov/experience_entry.png index 78abf0fa0..cb0b1a4e5 100644 Binary files a/docs/assets/images/sb2nov/experience_entry.png and b/docs/assets/images/sb2nov/experience_entry.png differ diff --git a/docs/assets/images/sb2nov/normal_entry.png b/docs/assets/images/sb2nov/normal_entry.png index 0294c6226..5cbab4ccd 100644 Binary files a/docs/assets/images/sb2nov/normal_entry.png and b/docs/assets/images/sb2nov/normal_entry.png differ diff --git a/docs/assets/images/sb2nov/numbered_entry.png b/docs/assets/images/sb2nov/numbered_entry.png index f13b0481f..33168ee3c 100644 Binary files a/docs/assets/images/sb2nov/numbered_entry.png and b/docs/assets/images/sb2nov/numbered_entry.png differ diff --git a/docs/assets/images/sb2nov/one_line_entry.png b/docs/assets/images/sb2nov/one_line_entry.png index 8e4b0d296..5163f0d3b 100644 Binary files a/docs/assets/images/sb2nov/one_line_entry.png and b/docs/assets/images/sb2nov/one_line_entry.png differ diff --git a/docs/assets/images/sb2nov/publication_entry.png b/docs/assets/images/sb2nov/publication_entry.png index 6d8b6965c..a6c0c9dd3 100644 Binary files a/docs/assets/images/sb2nov/publication_entry.png and b/docs/assets/images/sb2nov/publication_entry.png differ diff --git a/docs/assets/images/sb2nov/reversed_numbered_entry.png b/docs/assets/images/sb2nov/reversed_numbered_entry.png index cc693a8d9..a893ff1b6 100644 Binary files a/docs/assets/images/sb2nov/reversed_numbered_entry.png and b/docs/assets/images/sb2nov/reversed_numbered_entry.png differ diff --git a/docs/assets/images/sb2nov/sb2nov.png b/docs/assets/images/sb2nov/sb2nov.png new file mode 100644 index 000000000..2b1ee35a0 Binary files /dev/null and b/docs/assets/images/sb2nov/sb2nov.png differ diff --git a/docs/assets/images/sb2nov/text_entry.png b/docs/assets/images/sb2nov/text_entry.png index be03655bf..b9682d511 100644 Binary files a/docs/assets/images/sb2nov/text_entry.png and b/docs/assets/images/sb2nov/text_entry.png differ diff --git a/docs/assets/images/schema.gif b/docs/assets/images/schema.gif deleted file mode 100644 index a9c5c4cce..000000000 Binary files a/docs/assets/images/schema.gif and /dev/null differ diff --git a/docs/assets/images/validation.gif b/docs/assets/images/validation.gif new file mode 100644 index 000000000..ab0e05be3 Binary files /dev/null and b/docs/assets/images/validation.gif differ diff --git a/docs/assets/javascripts/katex.js b/docs/assets/javascripts/katex.js index baabe41bf..4dd418d8c 100644 --- a/docs/assets/javascripts/katex.js +++ b/docs/assets/javascripts/katex.js @@ -1,4 +1,4 @@ -document$.subscribe(() => { +document$.subscribe(() => { renderMathInElement(document.body, { delimiters: [ { left: "$$", right: "$$", display: true }, @@ -7,4 +7,4 @@ document$.subscribe(() => { { left: "\\[", right: "\\]", display: true } ], }) -}) \ No newline at end of file +}) diff --git a/docs/assets/javascripts/rendercv-logo.js b/docs/assets/javascripts/rendercv-logo.js new file mode 100644 index 000000000..32e7a74d1 --- /dev/null +++ b/docs/assets/javascripts/rendercv-logo.js @@ -0,0 +1,83 @@ +(function () { + // Persist cursor state on window so it survives across MkDocs + // instant-navigation script re-evaluations. + var S = window.__rcv; + if (!S) { + S = { mx: 0, my: 0, has: false, init: false }; + window.__rcv = S; + } + + var EL = { cx: 228, cy: 332 }; + var ER = { cx: 373, cy: 332 }; + var BC = { x: 300, y: 350 }; + var VBW = 601.3; + var VBH = 595; + var MAX_PUPIL = 8; + var MAX_TILT = 3; + + function clamp(dx, dy, max) { + var d = Math.sqrt(dx * dx + dy * dy); + if (d === 0) return { dx: 0, dy: 0 }; + var s = Math.min(1, max / d); + return { dx: dx * s, dy: dy * s }; + } + + function tick() { + requestAnimationFrame(tick); + if (!S.has) return; + + var svgs = document.querySelectorAll(".rendercv-logo-svg"); + for (var i = 0; i < svgs.length; i++) { + var svg = svgs[i]; + var rect = svg.getBoundingClientRect(); + if (rect.width === 0) continue; + + // Screen coords to SVG viewBox coords (same approach as the web app) + var px = ((S.mx - rect.left) / rect.width) * VBW; + var py = ((S.my - rect.top) / rect.height) * VBH; + + var li = svg.querySelector('[data-eye="left"] .rendercv-iris'); + var ri = svg.querySelector('[data-eye="right"] .rendercv-iris'); + var body = svg.querySelector(".rendercv-body"); + + if (li) { + var c = clamp(px - EL.cx, py - EL.cy, MAX_PUPIL); + li.setAttribute("transform", "translate(" + c.dx + "," + c.dy + ")"); + } + if (ri) { + var c2 = clamp(px - ER.cx, py - ER.cy, MAX_PUPIL); + ri.setAttribute("transform", "translate(" + c2.dx + "," + c2.dy + ")"); + } + + if (body) { + var cx = rect.left + rect.width / 2; + var nx = Math.max(-1, Math.min(1, (S.mx - cx) / (window.innerWidth / 2))); + var ny = Math.max(-1, Math.min(1, + (S.my - (rect.top + rect.height / 2)) / (window.innerHeight / 2))); + body.setAttribute( + "transform", + "translate(" + (nx * 2) + "," + (ny * 1.5) + ") " + + "rotate(" + (nx * MAX_TILT) + "," + BC.x + "," + BC.y + ")" + ); + } + } + } + + // Only set up the listener and animation loop once + if (!S.init) { + S.init = true; + + function capture(e) { + S.mx = e.clientX; + S.my = e.clientY; + S.has = true; + } + + // pointermove: primary tracking + document.addEventListener("pointermove", capture); + // mouseover: fires when new DOM appears under cursor (instant navigation) + document.addEventListener("mouseover", capture); + + requestAnimationFrame(tick); + } +})(); diff --git a/docs/assets/rendercv_skill.zip b/docs/assets/rendercv_skill.zip new file mode 100644 index 000000000..cd6dd7979 Binary files /dev/null and b/docs/assets/rendercv_skill.zip differ diff --git a/docs/assets/stylesheets/rendercv.css b/docs/assets/stylesheets/rendercv.css index 4f3cd5d71..1a3dc35da 100644 --- a/docs/assets/stylesheets/rendercv.css +++ b/docs/assets/stylesheets/rendercv.css @@ -1,3 +1,300 @@ -.mermaid { - text-align: center; - } \ No newline at end of file +[data-md-color-scheme=default] { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.129 0.042 264.695); + --card: oklch(1 0 0); + --card-foreground: oklch(0.129 0.042 264.695); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.129 0.042 264.695); + --primary: oklch(0.208 0.042 265.755); + --primary-foreground: oklch(0.984 0.003 247.858); + --secondary: oklch(0.968 0.007 247.896); + --secondary-foreground: oklch(0.208 0.042 265.755); + --muted: oklch(0.968 0.007 247.896); + --muted-foreground: oklch(0.554 0.046 257.417); + --accent: oklch(0.968 0.007 247.896); + --accent-foreground: oklch(0.208 0.042 265.755); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.929 0.013 255.508); + --input: oklch(0.929 0.013 255.508); + --ring: oklch(0.704 0.04 256.788); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.984 0.003 247.858); + --sidebar-foreground: oklch(0.129 0.042 264.695); + --sidebar-primary: oklch(0.45 0.2 295); + --sidebar-primary-foreground: oklch(0.984 0.003 247.858); + --sidebar-accent: oklch(0.968 0.007 247.896); + --sidebar-accent-foreground: oklch(0.208 0.042 265.755); + --sidebar-border: oklch(0.929 0.013 255.508); + --sidebar-ring: oklch(0.704 0.04 256.788); +} + +:root, +[data-md-color-scheme=slate] { + --background: oklch(0.129 0.042 264.695); + --foreground: oklch(0.984 0.003 247.858); + --card: oklch(0.208 0.042 265.755); + --card-foreground: oklch(0.984 0.003 247.858); + --popover: oklch(0.208 0.042 265.755); + --popover-foreground: oklch(0.984 0.003 247.858); + --primary: oklch(0.929 0.013 255.508); + --primary-foreground: oklch(0.208 0.042 265.755); + --secondary: oklch(0.279 0.041 260.031); + --secondary-foreground: oklch(0.984 0.003 247.858); + --muted: oklch(0.279 0.041 260.031); + --muted-foreground: oklch(0.704 0.04 256.788); + --accent: oklch(0.279 0.041 260.031); + --accent-foreground: oklch(0.984 0.003 247.858); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.551 0.027 264.364); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.208 0.042 265.755); + --sidebar-foreground: oklch(0.984 0.003 247.858); + --sidebar-primary: oklch(0.64 0.17 269.37); + --sidebar-primary-foreground: oklch(0.984 0.003 247.858); + --sidebar-accent: oklch(0.279 0.041 260.031); + --sidebar-accent-foreground: oklch(0.984 0.003 247.858); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.551 0.027 264.364); +} + +[data-md-color-scheme=default] { + color-scheme: light; +} +[data-md-color-scheme=default] img[src$="#only-dark"], +[data-md-color-scheme=default] img[src$="#gh-dark-mode-only"] { + display: none; +} +[data-md-color-scheme=default] { + --md-hue: 225deg; + --md-primary-fg-color: var(--sidebar); + --md-primary-bg-color: var(--sidebar-foreground); + --md-accent-fg-color: var(--sidebar-primary); + --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1); + --md-accent-bg-color: hsla(0, 0%, 100%, 1); + --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7); + --md-default-fg-color: var(--foreground); + --md-default-fg-color--light: var(--muted-foreground); + --md-default-fg-color--lighter: hsla(0, 0%, 0%, 0.32); + --md-default-fg-color--lightest: hsla(0, 0%, 0%, 0.07); + --md-default-bg-color: var(--background); + --md-default-bg-color--light: hsla(0, 0%, 100%, 0.7); + --md-default-bg-color--lighter: hsla(0, 0%, 100%, 0.3); + --md-default-bg-color--lightest: hsla(0, 0%, 100%, 0.12); + --md-code-fg-color: hsla(200, 18%, 26%, 1); + --md-code-bg-color: var(--sidebar); + --md-code-bg-color--light: hsla(200, 0%, 96%, 0); + --md-code-bg-color--lighter: hsla(200, 0%, 96%, 0); + --md-code-hl-color: hsla(218, 100%, 63%, 1); + --md-code-hl-color--light: hsla(218, 100%, 63%, 0.1); + --md-code-hl-number-color: hsla(0, 67%, 50%, 1); + --md-code-hl-special-color: hsla(340, 83%, 47%, 1); + --md-code-hl-function-color: hsla(291, 45%, 50%, 1); + --md-code-hl-constant-color: hsla(250, 63%, 60%, 1); + --md-code-hl-keyword-color: hsla(219, 54%, 51%, 1); + --md-code-hl-string-color: hsla(150, 63%, 30%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + --md-typeset-color: var(--md-default-fg-color); + --md-typeset-a-color: var(--sidebar-primary); + --md-typeset-del-color: hsla(6, 90%, 60%, 0.15); + --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15); + --md-typeset-kbd-color: hsla(0, 0%, 98%, 1); + --md-typeset-kbd-accent-color: hsla(0, 100%, 100%, 1); + --md-typeset-kbd-border-color: hsla(0, 0%, 72%, 1); + --md-typeset-mark-color: hsla(60, 100%, 50%, 0.5); + --md-typeset-table-color: hsla(0, 0%, 0%, 0.12); + --md-typeset-table-color--light: hsla(0, 0%, 0%, 0.035); + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + --md-warning-fg-color: hsla(0, 0%, 0%, 0.87); + --md-warning-bg-color: hsla(60, 100%, 80%, 1); + --md-footer-fg-color: var(--sidebar-foreground); + --md-footer-fg-color--light: var(--sidebar-foreground); + --md-footer-fg-color--lighter: var(--sidebar-foreground); + --md-footer-bg-color: var(--sidebar); + --md-footer-bg-color--dark: var(--sidebar); + --md-shadow-z1: + 0 0.2rem 0.5rem hsla(0, 0%, 0%, 0.05), + 0 0 0.05rem hsla(0, 0%, 0%, 0.1); + --md-shadow-z2: + 0 0.2rem 0.5rem hsla(0, 0%, 0%, 0.1), + 0 0 0.05rem hsla(0, 0%, 0%, 0.25); + --md-shadow-z3: + 0 0.2rem 0.5rem hsla(0, 0%, 0%, 0.2), + 0 0 0.05rem hsla(0, 0%, 0%, 0.35); +} + +[data-md-color-scheme=slate] { + color-scheme: dark; +} +[data-md-color-scheme=slate] img[src$="#only-light"], +[data-md-color-scheme=slate] img[src$="#gh-light-mode-only"] { + display: none; +} +[data-md-color-scheme=slate] { + --md-hue: 225deg; + --md-primary-fg-color: var(--sidebar); + --md-primary-bg-color: var(--sidebar-foreground); + --md-accent-fg-color: var(--sidebar-primary); + --md-accent-fg-color--transparent: hsla(231, 99%, 66%, 0.1); + --md-accent-bg-color: hsla(0, 0%, 100%, 1); + --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7); + --md-default-fg-color: var(--foreground); + --md-default-fg-color--light: var(--muted-foreground); + --md-default-fg-color--lighter: hsla(var(--md-hue), 15%, 90%, 0.32); + --md-default-fg-color--lightest: hsla(var(--md-hue), 15%, 90%, 0.12); + --md-default-bg-color: var(--background); + --md-default-bg-color--light: hsla(var(--md-hue), 15%, 14%, 0.54); + --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 14%, 0.26); + --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 14%, 0.07); + --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 0.82); + --md-code-bg-color: var(--sidebar); + --md-code-bg-color--light: hsla(var(--md-hue), 15%, 18%, 0); + --md-code-bg-color--lighter: hsla(var(--md-hue), 15%, 18%, 0); + --md-code-hl-color: hsla(218, 100%, 58%, 1); + --md-code-hl-color--light: hsla(218, 100%, 58%, 0.1); + --md-code-hl-number-color: hsla(6, 74%, 63%, 1); + --md-code-hl-special-color: hsla(340, 83%, 66%, 1); + --md-code-hl-function-color: hsla(291, 57%, 65%, 1); + --md-code-hl-constant-color: hsla(250, 62%, 70%, 1); + --md-code-hl-keyword-color: hsla(219, 66%, 64%, 1); + --md-code-hl-string-color: hsla(150, 58%, 44%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + --md-typeset-color: var(--md-default-fg-color); + --md-typeset-a-color: var(--sidebar-primary); + --md-typeset-del-color: hsla(6, 90%, 60%, 0.15); + --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15); + --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 90%, 0.12); + --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 90%, 0.2); + --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1); + --md-typeset-mark-color: hsla(218, 100%, 63%, 0.3); + --md-typeset-table-color: hsla(var(--md-hue), 15%, 95%, 0.12); + --md-typeset-table-color--light: hsla(var(--md-hue), 15%, 95%, 0.035); + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + --md-warning-fg-color: hsla(0, 0%, 0%, 0.87); + --md-warning-bg-color: hsla(60, 100%, 80%, 1); + --md-footer-fg-color: var(--sidebar-foreground); + --md-footer-fg-color--light: var(--sidebar-foreground); + --md-footer-fg-color--lighter: var(--sidebar-foreground); + --md-footer-bg-color: var(--sidebar); + --md-footer-bg-color--dark: var(--sidebar); + --md-shadow-z1: + 0 0.2rem 0.5rem hsla(0, 0%, 0%, 0.05), + 0 0 0.05rem hsla(0, 0%, 0%, 0.1); + --md-shadow-z2: + 0 0.2rem 0.5rem hsla(0, 0%, 0%, 0.25), + 0 0 0.05rem hsla(0, 0%, 0%, 0.25); + --md-shadow-z3: + 0 0.2rem 0.5rem hsla(0, 0%, 0%, 0.4), + 0 0 0.05rem hsla(0, 0%, 0%, 0.35); +} + +.md-header__button.md-logo { + overflow: visible; + margin-right: 0; + padding-right: 0; +} + +.md-header__button.md-logo svg { + overflow: visible; + width: 2rem; + height: 2rem; +} + +.md-header__topic:first-child { + margin-left: -0.3rem; + font-size: 1.05rem; + font-weight: 500; +} + +.md-header__site-link { + text-decoration: none; + color: inherit; + cursor: pointer; +} + +/* Hovering logo dims both logo + title (only when not scrolled) */ +.md-header__button.md-logo:hover { + opacity: 0.7; +} +.md-header__button.md-logo:hover ~ .md-header__title:not(.md-header__title--active) .md-header__topic:first-child { + opacity: 0.7; +} + +/* Hovering title dims both logo + title (only when not scrolled) */ +.md-header__inner:has(.md-header__title:not(.md-header__title--active):hover) .md-header__button.md-logo { + opacity: 0.7; +} +.md-header__inner:has(.md-header__title:not(.md-header__title--active):hover) .md-header__topic:first-child { + opacity: 0.7; +} + +/* Filled button for external tab link */ +.md-tabs__item--external .md-tabs__link { + background: var(--md-accent-fg-color); + color: var(--md-primary-fg-color); + border-radius: 999px; + padding: 0.15em 0.85em; + margin-top: calc(0.8rem - 0.15em); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; + will-change: transform; +} + +.md-tabs__item--external .md-tabs__link:hover, +.md-tabs__item--external .md-tabs__link:focus { + transform: scale(1.08) !important; +} + +.md-tabs__item--external .md-tabs__link::after { + content: "\2197"; + margin-left: 0.2em; + font-size: 0.75em; + vertical-align: -0.05em; +} + +/* Lucide-style stroke-only toggle icons */ +[data-md-component="palette"] svg { + fill: none; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + width: 1.1rem; + height: 1.1rem; +} + +@keyframes rendercv-blink { + 0%, 93%, 97%, 100% { transform: scaleY(1); } + 95% { transform: scaleY(0.05); } +} + +.rendercv-eye { + transform-box: fill-box; + transform-origin: center; + animation: rendercv-blink 4s ease-in-out infinite; +} + +.rendercv-eye[data-eye="right"] { + animation-delay: 0.1s; +} diff --git a/docs/ats_compatibility.md b/docs/ats_compatibility.md new file mode 100644 index 000000000..484e60deb --- /dev/null +++ b/docs/ats_compatibility.md @@ -0,0 +1,125 @@ +--- +hide: + - navigation +--- + +# RenderCV ATS Compatibility Report + +## Summary + +We empirically tested whether RenderCV's PDF output can be correctly parsed by Applicant Tracking Systems. We rendered 4 test resumes across 5 themes (20 PDFs total), then ran each PDF through two independent text extraction tools and three commercial resume parsing engines. + +**Every PDF was correctly parsed.** All 20 PDFs passed structural analysis with zero garbled characters. Three commercial parsers (Affinda, Extracta, and Klippa) correctly identified names, emails, phone numbers, companies, job titles, dates, institutions, and degrees across every theme. + +## Background + +When you submit a resume online, the ATS doesn't "read" it the way a human does. It runs the PDF through a **resume parsing engine** that: + +1. **Extracts text** from the PDF's binary structure +2. **Segments** the text into sections (experience, education, skills, etc.) +3. **Identifies fields** within each section (company name, job title, start date, etc.) +4. **Stores structured data** in a database that recruiters search and filter + +A resume is "ATS friendly" if it survives all four steps with its data intact. Most failures happen at step 1: the PDF's text layer is broken, garbled, or missing entirely (common with scanned documents, image-heavy designs, or tools that rasterize text). + +RenderCV generates PDFs via Typst, which produces a clean, programmatic text layer with properly embedded fonts and correct Unicode mappings. This report tests whether that text layer holds up under real parsing conditions. + +## Methodology + +### Test corpus + +We selected 4 test resumes designed to cover the range of content that a parser might encounter: + +| Test case | What it covers | +|-----------|---------------| +| **Standard** | Full resume with 3 work entries, 2 education entries, 21 skills, certifications | +| **Diacritics** | International characters (García-López, Universitat de Barcelona, +34 phone) | +| **Academic** | Publications, grants, 3 positions, 3 degrees, 13 skill categories | +| **Minimal** | Just a name, email, 1 job, 1 degree | + +Each was rendered across all **5 RenderCV themes** (classic, moderncv, sb2nov, engineeringresumes, engineeringclassic), producing **20 PDFs**. + +### Testing layers + +We tested at two independent layers: + +**Layer 1: Text extraction.** We extracted text from every PDF using two tools: `pdftotext` ([Poppler](https://poppler.freedesktop.org/)) and [PyMuPDF](https://pymupdf.readthedocs.io/). For each PDF, we checked whether the extracted text contained every expected field from the source YAML: names, emails, locations, company names, job titles, institution names, degrees, highlights, and skills. + +**Layer 2: Commercial parsing.** We submitted every PDF to three commercial resume parsing engines via [Eden AI](https://www.edenai.run/): [Affinda](https://www.affinda.com/resume-parser), [Extracta](https://extracta.ai/), and [Klippa](https://www.klippa.com/). These are real production parsers that ATS platforms use to extract structured candidate data. We compared their structured output (parsed name, parsed company, parsed dates, etc.) against the known input from our YAML files. + +### Why this is a strong test + +- **Known ground truth.** RenderCV generates PDFs from structured YAML, so we know exactly what every field should be. There is no annotation ambiguity. +- **Multiple independent tools.** Two text extractors and three commercial parsers all analyzing the same PDFs. If all five agree, the result is robust. +- **Theme variation.** All five themes produce different visual layouts but the same underlying content. If parsing succeeds across all themes, the result is not dependent on a specific layout. + +## Results + +### Layer 1: Text extraction + +| Check | Result | +|-------|--------| +| PDFs with extractable text | **20/20** | +| Correct reading order | **20/20** | +| No garbled characters | **20/20** | +| pdftotext average accuracy | **99.1%** | +| pymupdf average accuracy | **99.1%** | + +Both tools extracted text correctly from every PDF. The small gap from 100% accuracy is due to Typst's standard typographic rendering (e.g., straight quotes become curly quotes), not missing content. + +Accuracy was identical across all five themes, which is expected: Typst produces the same text layer regardless of the visual theme. + +### Layer 2: Commercial parsing + +All three parsers correctly extracted every core resume field across all themes: + +| Field | Affinda | Extracta | Klippa | +|-------|:-------:|:--------:|:------:| +| Name | Correct | Correct | Correct | +| Email | Correct | Correct | Correct | +| Phone | Correct | Correct | Correct | +| Location | Partial | Correct | Not extracted | +| Company name | Correct | Correct | Correct | +| Job title | Correct | Correct | Correct | +| Start date | Correct | Correct | Correct | +| End date | Correct | Correct | Correct | +| Institution | Partial | Correct | Correct | + +To illustrate what "correctly parsed" means concretely, here is what the parsers extracted from one test resume (standard layout, classic theme): + +| Field | YAML input (ground truth) | Affinda | Extracta | Klippa | +|-------|--------------------------|---------|----------|--------| +| Name | Alice Chen | Alice Chen | Alice Chen | Alice Chen | +| Email | alice.chen@email.com | alice.chen@email.com | alice.chen@email.com | alice.chen@email.com | +| Phone | +1-415-555-0142 | (415) 555-0142 | (415) 555-0142 | (415) 555-0142 | +| Work (3 entries) | Stripe, Google, AWS | Stripe, Google, AWS | Stripe, Google, AWS | Stripe, Google, AWS | +| Education | Stanford (MS), UC Berkeley (BS) | Stanford (Master), UC Berkeley (Bachelor) | Stanford (MS), UC Berkeley (BS) | Stanford (MS), UC Berkeley (BS) | + +Every parser identified the correct person, the correct companies, the correct job titles, and the correct institutions. Formatting differences (e.g., phone number format, "MS" vs "Master") are standard parser normalization, not extraction failures. + +## Why RenderCV PDFs parse well + +RenderCV uses [Typst](https://typst.app/) as its PDF engine. Typst is a modern typesetting system that produces high-quality, standards-compliant PDFs with properties that make them inherently easy for ATS parsers to read: + +- **Tagged PDF by default.** Since [Typst 0.14](https://typst.app/blog/2025/typst-0.14/), every PDF is a [Tagged PDF](https://typst.app/blog/2025/accessible-pdf/): it contains a structure tree that tells parsers the reading order, which text is a heading, which is a paragraph, and which is emphasized. This is the same structure that screen readers use, and it gives ATS parsers a semantic map of the document instead of forcing them to guess from visual layout. +- **PDF standard compliance.** Typst supports [PDF/UA-1](https://typst.app/docs/reference/pdf/) (the universal accessibility standard) and all conformance levels of [PDF/A](https://typst.app/docs/reference/pdf/) (the archival standard). These standards require proper Unicode text, embedded fonts, and a complete structure tree. A PDF that meets these standards is, by definition, machine-readable. +- **Proper Unicode text layer.** Typst embeds text with correct fonts and Unicode mappings. There is no image-based text, no broken encoding, no garbled copy-paste. Every character is a real Unicode code point, not a glyph index that requires a lookup table. +- **Single-column content flow.** All built-in RenderCV themes use a single-column layout. Multi-column layouts are the most common cause of ATS parsing failures because parsers have to guess reading order from spatial coordinates. With tagged PDFs, the reading order is explicit. +- **Deterministic output.** Every PDF generated from the same YAML input is byte-for-byte identical. If one PDF parses correctly, they all do. + +## Reproduce + +All scripts and test data are in [`scripts/ats_proof/`](https://github.com/rendercv/rendercv/tree/main/scripts/ats_proof). + +```bash +cd scripts/ats_proof +uv sync +uv run python run_all.py # Text extraction tests (free, no API keys) +uv run python run_all.py --full # Full pipeline including commercial parsers +``` + +Commercial parsing requires an [Eden AI](https://app.edenai.run/user/register) API key: + +```bash +EDENAI_API_KEY=your_key uv run python run_all.py --full +``` diff --git a/docs/changelog/index.md b/docs/changelog.md similarity index 66% rename from docs/changelog/index.md rename to docs/changelog.md index 105d430e0..e9832aa70 100644 --- a/docs/changelog/index.md +++ b/docs/changelog.md @@ -1,12 +1,14 @@ --- toc_depth: 1 +hide: + - navigation --- # Changelog All notable changes to this project will be documented in this file. -[Click here to see the unreleased changes.](https://github.com/rendercv/rendercv/compare/v2.2...HEAD) +[Click here to see the unreleased changes.](https://github.com/rendercv/rendercv/compare/v2.8...HEAD) +## [2.8] - March 21, 2026 + +> **Full Changelog**: [v2.7...v2.8] + +### Added + +- Four new themes have been added: `harvard`, `ink`, `opal`, and `ember`. +- Four new centered section title types have been added: `centered_without_line`, `centered_with_partial_line`, `centered_with_centered_partial_line`, and `centered_with_full_line`. +- Built-in locale defaults for 2 additional languages have been added: Vietnamese and Hungarian. +- Multi-platform Docker builds are now available for both `linux/amd64` and `linux/arm64` ([#697](https://github.com/rendercv/rendercv/issues/697)). + +### Changed + +- The `rendercv-typst` Typst package is now bundled inside the Python package. RenderCV no longer requires an internet connection to compile PDFs, as it no longer needs to download the package from Typst Universe at runtime. The package will continue to be published on Typst Universe for standalone Typst users. + +### Fixed + +- Custom fonts folder not being found when using a relative input path has been fixed ([#692](https://github.com/rendercv/rendercv/issues/692)). +- CLI overrides like `--design.theme` no longer crash when the target section is not present in the main YAML file ([#595](https://github.com/rendercv/rendercv/issues/595)). +- The `SUMMARY` placeholder can now be used inline in templates ([#653](https://github.com/rendercv/rendercv/issues/653)). +- Design and locale overlay files specified in YAML `settings.render_command` are now loaded correctly ([#690](https://github.com/rendercv/rendercv/issues/690)). +- Copied template files are now made writable for immutable distributions like NixOS ([#673](https://github.com/rendercv/rendercv/issues/673)). +- Markdown lines are now processed independently to prevent cross-line emphasis interference ([#685](https://github.com/rendercv/rendercv/issues/685)). +- Connector words in phrases no longer appear when their associated placeholder is empty (e.g., "BS in Mechanical Engineering" now correctly renders as "Mechanical Engineering" when the degree is omitted, instead of "in Mechanical Engineering"). +- `EducationEntry` location placement in markdown output has been fixed ([#691](https://github.com/rendercv/rendercv/issues/691)). + +## [2.7] - March 6, 2026 + +> **Full Changelog**: [v2.6...v2.7] + +### Added + +- A new `settings.pdf_title` field has been added to customize the title of produced PDF documents ([#624](https://github.com/rendercv/rendercv/issues/624)). +- A new `locale.phrases` field has been added for customizable phrases in the CV, allowing translations like "DEGREE in AREA" to be adapted per language ([#618](https://github.com/rendercv/rendercv/issues/618), [#650](https://github.com/rendercv/rendercv/issues/650), [#660](https://github.com/rendercv/rendercv/issues/660)). +- A new `design.entries.degree_width` field has been added ([#671](https://github.com/rendercv/rendercv/issues/671)). +- `--output-folder` option has been added to the `rendercv render` command to specify the output directory ([#578](https://github.com/rendercv/rendercv/issues/578)). +- Watch mode now monitors included config files and re-renders when they change ([#579](https://github.com/rendercv/rendercv/issues/579)). +- `DAY` and `DAY_IN_TWO_DIGITS` placeholders have been added to date formatting ([#548](https://github.com/rendercv/rendercv/issues/548)). +- Reddit has been added as a social network type ([#658](https://github.com/rendercv/rendercv/issues/658)). +- Right-to-left (RTL) language support has been added, with Arabic ([#591](https://github.com/rendercv/rendercv/issues/591)), Hebrew, and Persian as built-in locales ([#452](https://github.com/rendercv/rendercv/issues/452), [#645](https://github.com/rendercv/rendercv/issues/645)). +- Built-in locale defaults for 3 additional languages have been added: Dutch ([#585](https://github.com/rendercv/rendercv/issues/585)), Norwegian Bokmål, and Norwegian Nynorsk ([#652](https://github.com/rendercv/rendercv/issues/652)). +- URLs are now allowed for the `cv.photo` field. +- Empty sections are now allowed. +- `"today"` is now supported as a value for `settings.current_date`. + +### Changed + +- PyPI version check is now non-blocking ([#615](https://github.com/rendercv/rendercv/issues/615)). +- `bold_keywords` now only matches full words instead of sub-words. +- Extra keys are no longer allowed in the top-level YAML input and in the `cv` field. +- Obsolete PNG files are now automatically deleted when re-rendering ([#590](https://github.com/rendercv/rendercv/issues/590)). +- YAML aliases are now treated as literal strings. + +### Fixed + +- The `--quiet` option of the `rendercv render` command now works correctly ([#608](https://github.com/rendercv/rendercv/issues/608)). +- The design file not applying when used with a settings file has been fixed ([#642](https://github.com/rendercv/rendercv/issues/642)). +- `bold_keywords` no longer applies to placeholder variables in file paths and PDF titles ([#557](https://github.com/rendercv/rendercv/issues/557)). +- `DAY` and `DAY_IN_TWO_DIGITS` placeholders now work correctly in output file paths ([#684](https://github.com/rendercv/rendercv/issues/684)). +- Vertical alignment of titles with icons for education entries has been fixed ([#603](https://github.com/rendercv/rendercv/issues/603)). +- Mandarin Chinese locale spelling and schema validation have been corrected ([#617](https://github.com/rendercv/rendercv/issues/617), [#678](https://github.com/rendercv/rendercv/issues/678)). +- `settings.current_date` issues have been fixed. +- Arabic, Hebrew, and Persian locale issues have been fixed. +- Empty links no longer cause failures. +- A duplicate font has been removed from `available_font_families` ([#643](https://github.com/rendercv/rendercv/issues/643)). +- YAML error handling has been improved. +- Pydantic error handling for multiple YAML sources has been improved. + +### Removed + +- The `ex` unit is no longer accepted in dimension fields (e.g., margins, spacing). + +## [2.6] - December 23, 2025 + +> **Full Changelog**: [v2.5...v2.6] + +### Added + +- Bluesky has been added as a social network type ([#560](https://github.com/rendercv/rendercv/issues/560)). +- Built-in locale defaults for 2 additional languages have been added: Danish and Indonesian ([#556](https://github.com/rendercv/rendercv/issues/556), [#567](https://github.com/rendercv/rendercv/issues/567)). + +### Fixed + +- Unicode corruption in sample YAML name generation has been fixed ([#570](https://github.com/rendercv/rendercv/issues/570)). +- Typst syntax is no longer included in Markdown and HTML outputs ([#563](https://github.com/rendercv/rendercv/issues/563), [#564](https://github.com/rendercv/rendercv/issues/564)). + +## [2.5] - December 13, 2025 + +> **Full Changelog**: [v2.4...v2.5] + +### Changed + +- Top note and footer now have more placeholder options available. + +### Fixed + +- The `--design`, `--locale`, and `--settings` options of the `rendercv render` command now work correctly ([#543](https://github.com/rendercv/rendercv/issues/543)). +- Multiline summary rendering issues in entries have been fixed. + +## [2.4] - December 10, 2025 + +> **Full Changelog**: [v2.3...v2.4] + +### Added + +- A new optional `cv.headline` field has been added to display a position title at the top of the CV ([#442](https://github.com/rendercv/rendercv/issues/442)). +- Built-in locale defaults for 11 languages have been added: French, German, Hindi, Italian, Japanese, Korean, Mandarin Chinese, Portuguese, Russian, Spanish, and Turkish. Users can now use these locales without writing all the translations themselves. +- Nested bullet points are now supported in highlights ([#177](https://github.com/rendercv/rendercv/issues/177)). +- WhatsApp has been added as a social network type ([#319](https://github.com/rendercv/rendercv/issues/319)). +- The `cv.custom_connections` field has been added to allow users to define custom header connections with a placeholder (displayed text), optional URL, and Font Awesome icon name ([#408](https://github.com/rendercv/rendercv/issues/408)). +- Support for multiple email addresses, websites, and phone numbers has been added ([#541](https://github.com/rendercv/rendercv/issues/541)). +- `--quiet` option has been added `rendercv render` command to suppress all messages ([#394](https://github.com/rendercv/rendercv/issues/394)). + +### Changed + +- RenderCV now uses its own [Typst package](https://typst.app/universe/package/rendercv), making Typst templates much clearer and simpler. The package is maintained at [rendercv/rendercv-typst](https://github.com/rendercv/rendercv-typst). +- The [documentation](https://docs.rendercv.com) has been completely rewritten, including the user guide and developer guide. +- The `design` field structure has been completely redesigned for better clarity and organization. +- The `rendercv_settings` field has been renamed to `settings`. +- The `rendercv_settings.date` field has been renamed to `settings.current_date`. + +### Fixed + +- Image paths are now correctly handled when providing a full image path for the photo field ([#361](https://github.com/rendercv/rendercv/issues/361)). +- The less than symbol `<` no longer causes an "unclosed label" error ([#364](https://github.com/rendercv/rendercv/issues/364)). +- Typst commands with parentheses (e.g., `#h(1cm)`) are now properly recognized and not escaped ([#383](https://github.com/rendercv/rendercv/issues/383)). +- `C++` and other strings ending with `++` or special characters are now formatted correctly ([#388](https://github.com/rendercv/rendercv/issues/388), [#446](https://github.com/rendercv/rendercv/issues/446)). +- Rendering issues when modifying `design.entry_types` templates have been fixed ([#413](https://github.com/rendercv/rendercv/issues/413)). +- The `--watch` option now correctly triggers re-rendering when the YAML file changes ([#513](https://github.com/rendercv/rendercv/issues/513)). +- `bold_keywords` are now properly applied to `PublicationEntry` authors ([#516](https://github.com/rendercv/rendercv/issues/516)). +- Calling `rendercv` with invalid arguments now displays help text instead of raising a TypeError ([#526](https://github.com/rendercv/rendercv/issues/526)). +- Page margin parsing issues in v2.3 have been resolved ([#531](https://github.com/rendercv/rendercv/issues/531)). +- Arbitrary keys in entries are now correctly recognized and substituted in templates ([#334](https://github.com/rendercv/rendercv/issues/334), [#376](https://github.com/rendercv/rendercv/issues/376), [#534](https://github.com/rendercv/rendercv/issues/534)). + +## [2.3] - October 29, 2025 + +> **Full Changelog**: [v2.2...v2.3] + +### Added + +- A new command-line option has been added: `--nopdf` to skip PDF generation ([#482](https://github.com/rendercv/rendercv/pull/482)). +- Two new social networks have been added: `Leetcode` ([#483](https://github.com/rendercv/rendercv/pull/483)) and `IMDB` ([#479](https://github.com/rendercv/rendercv/pull/479)). +- More system fonts have been added ([#466](https://github.com/rendercv/rendercv/pull/466)). +- `grade` field has been added to `EducationEntry` ([#463](https://github.com/rendercv/rendercv/pull/463)). +- Optional automatic sorting capabilities for entries have been added ([#461](https://github.com/rendercv/rendercv/pull/461)). + +### Changed + +- Docker image has been optimized for a smaller runtime size ([#511](https://github.com/rendercv/rendercv/pull/511)). +- Header connection order now follows the YAML key order in the input file ([#455](https://github.com/rendercv/rendercv/pull/455)). + +### Fixed + +- Bold keywords now correctly ignore case and don't bold sub-words ([#348](https://github.com/rendercv/rendercv/pull/348)). +- Typo "parial" has been corrected to "partial" throughout the codebase ([#380](https://github.com/rendercv/rendercv/pull/380)). +- Arbitrary keys functionality has been fixed ([#457](https://github.com/rendercv/rendercv/pull/457)). + ## [2.2] - January 25, 2025 > **Full Changelog**: [v2.1...v2.2] @@ -34,7 +193,7 @@ All notable changes to this project will be documented in this file. - `None` values in the entries are now handled correctly. - `--png-path` option of the `rendercv render` command has been fixed ([#332](https://github.com/rendercv/rendercv/issues/332)). - Issues with escaping Markdown characters have been fixed ([#347](https://github.com/rendercv/rendercv/issues/347)). - + ## [2.1] - January 25, 2025 @@ -52,7 +211,7 @@ All notable changes to this project will be documented in this file. - Path issues in `rendercv_settings` and CLI have been fixed ([#312](https://github.com/rendercv/rendercv/pull/312)). - Bold and italic text rendering issues have been fixed ([#303](https://github.com/rendercv/rendercv/pull/303)). - Asterisk is now escaped in Typst ([#303](https://github.com/rendercv/rendercv/pull/303)). - + ## [2.0] - January 7, 2025 > **Full Changelog**: [v1.18...v2.0] @@ -69,8 +228,8 @@ RenderCV has transitioned from using $\LaTeX$ to Typst. RenderCV is now much fas ### Changed - $\LaTeX$ has been replaced with Typst. -- The `design` field has been changed completely. See the [documentation](https://docs.rendercv.com/user_guide/structure_of_the_yaml_input_file/#design-field) for details. -- The `locale_catalog` field has been renamed to `locale`, and some fields have been moved from `design` to `locale`. See the [documentation](https://docs.rendercv.com/user_guide/structure_of_the_yaml_input_file/#locale-field) for details. +- The `design` field has been changed completely. +- The `locale_catalog` field has been renamed to `locale`, and some fields have been moved from `design` to `locale`. - The `moderncv` theme's header has been changed. @@ -144,7 +303,7 @@ RenderCV has transitioned from using $\LaTeX$ to Typst. RenderCV is now much fas ### Added -- `rendercv_settings` field has been added to the YAML input file. For details, see [here](../user_guide/structure_of_the_yaml_input_file.md#rendercv_settings-field). It will be extended in the future. +- `rendercv_settings` field has been added to the YAML input file. It will be extended in the future. ## [1.13] - July 23, 2024 @@ -153,8 +312,8 @@ RenderCV has transitioned from using $\LaTeX$ to Typst. RenderCV is now much fas ### Added -- Arbitrary keys are now allowed in the `cv` field. For details, see [here](../user_guide/structure_of_the_yaml_input_file.md#using-arbitrary-keys). -- Two new fields have been added to the `locale` field: `phone_number_format` and `date_style` ([#130](https://github.com/rendercv/rendercv/issues/130)). For details, see [here](../user_guide/structure_of_the_yaml_input_file.md#locale-field). +- Arbitrary keys are now allowed in the `cv` field. +- Two new fields have been added to the `locale` field: `phone_number_format` and `date_style` ([#130](https://github.com/rendercv/rendercv/issues/130)). ### Changed @@ -172,7 +331,7 @@ RenderCV has transitioned from using $\LaTeX$ to Typst. RenderCV is now much fas ### Added -- Arbitrary keys are now allowed in entry types. Users can use these keys in their templates. For details, see the [documentation](../user_guide/structure_of_the_yaml_input_file.md#using-arbitrary-keys). +- Arbitrary keys are now allowed in entry types. Users can use these keys in their templates. - The `locale.full_names_of_months` field has been added to the data model ([#111](https://github.com/rendercv/rendercv/issues/111)). - The `TODAY` placeholder can be used in the `design.page_numbering_style` field now. @@ -191,7 +350,7 @@ RenderCV has transitioned from using $\LaTeX$ to Typst. RenderCV is now much fas ### Added -- CLI options now have short versions. See the [CLI documentation](https://docs.rendercv.com/user_guide/cli/) for more information. +- CLI options now have short versions. - CLI now notifies the user when a new version is available ([#89](https://github.com/rendercv/rendercv/issues/89)). - `Google Scholar` has been added as a social network type ([#85](https://github.com/rendercv/rendercv/issues/85)). - Two new design options have been added to the `classic`, `sb2nov`, and `engineeringresumes` themes: `separator_between_connections` and `use_icons_for_connections`. @@ -236,7 +395,7 @@ RenderCV has transitioned from using $\LaTeX$ to Typst. RenderCV is now much fas ### Added -- RenderCV is now a multilingual tool. English strings can be overridden with `locale` section in the YAML input file ([#26](https://github.com/rendercv/rendercv/issues/26), [#20](https://github.com/rendercv/rendercv/pull/20)). See the [documentation](../user_guide/structure_of_the_yaml_input_file.md#locale-field) for more information. +- RenderCV is now a multilingual tool. English strings can be overridden with `locale` section in the YAML input file ([#26](https://github.com/rendercv/rendercv/issues/26), [#20](https://github.com/rendercv/rendercv/pull/20)). - PNG files for each page can be generated now ([#57](https://github.com/rendercv/rendercv/issues/57)). - `rendercv new` command now generates Markdown and $\LaTeX$ source files in addition to the YAML input file so that the default templates can be modified easily. - A new CLI command has been added, `rendercv create-theme`, to allow users to create their own themes easily. @@ -509,6 +668,12 @@ RenderCV has transitioned from using $\LaTeX$ to Typst. RenderCV is now much fas The first release of RenderCV. +[v2.7...v2.8]: https://github.com/rendercv/rendercv/compare/v2.7...v2.8 +[v2.6...v2.7]: https://github.com/rendercv/rendercv/compare/v2.6...v2.7 +[v2.5...v2.6]: https://github.com/rendercv/rendercv/compare/v2.5...v2.6 +[v2.4...v2.5]: https://github.com/rendercv/rendercv/compare/v2.4...v2.5 +[v2.3...v2.4]: https://github.com/rendercv/rendercv/compare/v2.3...v2.4 +[v2.2...v2.3]: https://github.com/rendercv/rendercv/compare/v2.2...v2.3 [v2.1...v2.2]: https://github.com/rendercv/rendercv/compare/v2.1...v2.2 [v2.0...v2.1]: https://github.com/rendercv/rendercv/compare/v2.0...v2.1 [v1.18...v2.0]: https://github.com/rendercv/rendercv/compare/v1.18...v2.0 @@ -539,6 +704,12 @@ The first release of RenderCV. [v0.3...v0.4]: https://github.com/rendercv/rendercv/compare/v0.3...v0.4 [v0.2...v0.3]: https://github.com/rendercv/rendercv/compare/v0.2...v0.3 [v0.1...v0.2]: https://github.com/rendercv/rendercv/compare/v0.1...v0.2 +[2.8]: https://github.com/rendercv/rendercv/releases/tag/v2.8 +[2.7]: https://github.com/rendercv/rendercv/releases/tag/v2.7 +[2.6]: https://github.com/rendercv/rendercv/releases/tag/v2.6 +[2.5]: https://github.com/rendercv/rendercv/releases/tag/v2.5 +[2.4]: https://github.com/rendercv/rendercv/releases/tag/v2.4 +[2.3]: https://github.com/rendercv/rendercv/releases/tag/v2.3 [2.2]: https://github.com/rendercv/rendercv/releases/tag/v2.2 [2.1]: https://github.com/rendercv/rendercv/releases/tag/v2.1 [2.0]: https://github.com/rendercv/rendercv/releases/tag/v2.0 diff --git a/docs/developer_guide/dockerfile.md b/docs/developer_guide/dockerfile.md new file mode 100644 index 000000000..48381c67d --- /dev/null +++ b/docs/developer_guide/dockerfile.md @@ -0,0 +1,38 @@ +--- +toc_depth: 3 +--- + +# Dockerfile + +## What is Docker? + +Docker lets software carry **its entire working environment** with it: the right language runtime, libraries, and configuration, all bundled into a single file called an *image*. Think of an image as a **frozen filesystem where everything is already installed and configured correctly**. + +When you run an image, Docker creates a **container**: a live, isolated instance of that environment running on your machine. When you're done, you can delete it without a trace. Your actual system stays untouched. + +## Why Docker for RenderCV? + +RenderCV installs easily with `pip install rendercv` if you have Python. Most users don't need Docker. + +But Docker makes sense if you want: + +- **No installation at all** — no Python, no packages, nothing added to your system +- **A reproducible environment** — the exact same setup on every machine, every time +- **To bypass restrictions** — some systems block software installation but allow containers + +The RenderCV Docker image is a ready-made environment with Python and RenderCV pre-installed. Just run: +```bash +docker run --rm -v "$PWD":/work -u $(id -u):$(id -g) -e HOME=/tmp -w /work ghcr.io/rendercv/rendercv new "Your Name" +``` + +## How the Image Gets Published + +Docker images are stored in **registries**, which are servers that host images so anyone can download and run them. Docker Hub is the most popular, but GitHub has its own called GitHub Container Registry (GHCR). + +When RenderCV publishes a GitHub release, the [`release.yaml` workflow](https://github.com/rendercv/rendercv/blob/main/.github/workflows/release.yaml) automatically builds and publishes the RenderCV image to GHCR at `ghcr.io/rendercv/rendercv`. + +When users run `docker run ghcr.io/rendercv/rendercv`, Docker automatically pulls the image from the registry if it's not already on their machine. + +## Learn More + +To learn more about writing `Dockerfile`, see the `uv`'s guide on [Docker](https://docs.astral.sh/uv/guides/integration/docker/). diff --git a/docs/developer_guide/documentation.md b/docs/developer_guide/documentation.md new file mode 100644 index 000000000..f9a3c4fdb --- /dev/null +++ b/docs/developer_guide/documentation.md @@ -0,0 +1,86 @@ +--- +toc_depth: 3 +--- + +# Documentation + +## The Goal + +We want documentation at `docs.rendercv.com`, a proper website with navigation, search, theming, and interactive features. + +**What is a website?** A collection of HTML, CSS, and JavaScript files. Browsers download these files and render them as the pages you see. To have a website, you need: + +1. HTML/CSS/JavaScript files +2. A server hosting those files +3. A domain (e.g. `docs.rendercv.com`) pointing to that server + +**The problem:** We don't want to develop a web app (writing HTML/CSS/JavaScript). We just want to put some content online. + +What if we could write content in Markdown and use some software to automatically generate HTML/CSS/JavaScript files from it? + +**The solution:** [MkDocs](https://github.com/mkdocs/mkdocs) with [Material theme](https://github.com/squidfunk/mkdocs-material). You write Markdown in `docs/`, MkDocs generates HTML/CSS/JavaScript, and GitHub Pages hosts it at `docs.rendercv.com`. + +Tools like MkDocs exist because documentation sites follow a stable, well-understood pattern. They aren’t open-ended web applications with unique interfaces or behaviors; they’re a specific kind of website with predictable needs: structured pages, cross-page navigation, search, consistent styling, and readable content. + +Once a pattern becomes that well defined, entire ecosystems form around it. Just as you reach for Python rather than designing a new programming language, you reach for MkDocs rather than hand-assembling HTML, CSS, and JavaScript files for a documentation site. + +## Configuration: [`mkdocs.yaml`](https://github.com/rendercv/rendercv/blob/main/mkdocs.yaml) + +`mkdocs.yaml` controls how MkDocs builds the website: + +- **Site metadata:** name, description, repository +- **Theme:** Material theme with colors and features +- **Navigation:** sidebar structure +- **Plugins:** see below + +## Plugins + +MkDocs plugins extend functionality beyond Markdown → HTML conversion. + +### [`mkdocstrings`](https://github.com/mkdocstrings/mkdocstrings): API Reference + +Generates the API reference from Python docstrings. The entire [API Reference](../api_reference/index.md) section is auto-generated from `src/rendercv/`. + +### [`mkdocs-macros-plugin`](https://mkdocs-macros-plugin.readthedocs.io/): Dynamic Content + +Lets you inject code-generated values into Markdown. [`docs/docs_templating.py`](https://github.com/rendercv/rendercv/blob/main/docs/docs_templating.py) runs during the build. It imports values directly from RenderCV's code and exposes them as variables. It's heavily used in [YAML Input Structure: `cv` Field](../user_guide/yaml_input_structure/cv.md) page. + +## Entry Type Figures + +The [YAML Input Structure: `cv` Field](../user_guide/yaml_input_structure/cv.md) page shows example images of each entry type rendered in each theme. + +These are auto-generated PNG images. Run `just update-entry-figures` to regenerate them from [`docs/user_guide/sample_entries.yaml`](https://github.com/rendercv/rendercv/blob/main/docs/user_guide/sample_entries.yaml). + +## Local Preview + +```bash +just serve-docs +``` + +Starts a local server at `http://127.0.0.1:8000` with live reload. Edit Markdown files and see changes instantly. + +```bash +just build-docs +``` + +Generates the final website in the `site/` directory. Mainly used by GitHub workflows for final deployment (see [GitHub Workflows](github_workflows.md)). + +## Deployment + +Every push to `main` triggers automatic deployment. + +**The workflow** ([`.github/workflows/deploy-docs.yaml`](https://github.com/rendercv/rendercv/blob/main/.github/workflows/deploy-docs.yaml)): + +1. **Trigger:** Runs on every push to `main` +2. **Build step:** + - Installs `uv` and `just` + - Runs `just build-docs` to generate the website + - Uploads the `site/` directory as an artifact +3. **Deploy step:** + - Takes the uploaded artifact + - Deploys it to GitHub Pages (a free static website hosting service) + - Makes it available at `docs.rendercv.com` + +## Learn More + +See the [MkDocs Material documentation](https://squidfunk.github.io/mkdocs-material/) for more information. diff --git a/docs/developer_guide/faq.md b/docs/developer_guide/faq.md deleted file mode 100644 index c191102a4..000000000 --- a/docs/developer_guide/faq.md +++ /dev/null @@ -1,29 +0,0 @@ - -# Frequently Asked Questions (FAQ) - -## How can I add a new social network to RenderCV? - -To add a new social network to RenderCV, go to the `rendercv/data/models/curriculum_vitae.py` file and follow these steps: - -1. Append the social network name (for example, "Facebook") to the `SocialNetworkName` type. -2. If necessary, implement its username validation in the `SocialNetwork.check_username` method. -3. Implement its URL generation using the `SocialNetwork.url` method. If the URL can be generated by appending the username to a hostname, only update `url_dictionary`. -4. Finally, include the Typst icon of the social network to the `icon_dictionary` in the `CurriculumVitae.connections` method. RenderCV uses the [`fontawesome`](https://typst.app/universe/package/fontawesome/) package. - -Then, the tests should be implemented for the new social network with the following steps: - -1. Go to `tests/test_data.py` and update `test_social_network_url` accordingly, i.e., add a new `(network, username, expected_url)` tuple to the `pytest.mark.parametrize` decorator. -2. Go to `tests/conftest.py` and add the new social network to `rendercv_filled_curriculum_vitae_data_model`. -3. Set `update_testdata` to `True` in `conftest.py` and run the tests to update the `testdata` folder. -4. Review the updated `testdata` folder manually to ensure everything works as expected. Then, set `update_testdata` to `False` and push the changes. - -## When should we consider adding a new entry type to RenderCV? - -We should add a new entry type if and only if the proposed design of the entry type cannot be achieved using any of the existing entry types. This is because RenderCV's entry types are not designed to function as data models. Their purpose is not to store specific data but rather to determine how a given set of strings will appear in the PDF. - -For example, JSON Resume follows a data-oriented approach. In JSON Resume, each entry type acts as a data model specifically designed to store structured information. RenderCV takes a design-oriented approach for two reasons: - -- There would be too many different data models that would ultimately look more or less the same in the PDF. -- It would be impossible to provide all the different data models people might need. - -Therefore, we decided to create entry types solely for their design output. diff --git a/docs/developer_guide/github_workflows.md b/docs/developer_guide/github_workflows.md new file mode 100644 index 000000000..c4f082d80 --- /dev/null +++ b/docs/developer_guide/github_workflows.md @@ -0,0 +1,108 @@ +--- +toc_depth: 3 +--- + +# GitHub Actions + +## The Problem + +Every software project has repetitive tasks that must run consistently: + +- **On every update:** Run tests, redeploy documentation +- **On every release:** Run tests, update `schema.json` and examples, build executables for 4 platforms, build package, upload to PyPI, publish Docker image + +You could do these manually. But manual means: + +- Forgetting steps ("Did I update `schema.json`? Did I build the Windows executable?") +- Wasted time ("Why am I doing the same 15 steps every release?") + +**What if you could write down these tasks once, and have them run automatically every time?** + +That's what **CI/CD (Continuous Integration/Continuous Deployment)** is. And **GitHub Actions** is GitHub's platform for it. + +## What are GitHub Actions? + +GitHub actions are **automation scripts that run on GitHub's servers when certain events happen**. + +You define them in `.github/workflows/*.yaml` files. Each file describes: + +1. **When to run:** push to main? Pull request? New release? +2. **What to do:** Run tests? Build docs? Publish package? +3. **Where to run:** Linux? Windows? macOS? Multiple versions? + +GitHub reads these files and executes them automatically when the triggering events occur. + +**Why GitHub's servers?** Because you don't want to worry about it. Push your code, turn off your computer, you're done. GitHub handles the rest (running tests, deploying docs, building packages) without you having to keep your machine on or manually run anything. + +## RenderCV's Workflows + +RenderCV has 4 workflows. Each handles a specific automation task. + +**How workflows start:** Every workflow begins the same way: clone the repository, install `uv`, install `just`, then run some `just` commands. This recreates the same environment you'd have locally (see [Setup](index.md)). + +### 1. [`test.yaml`](https://github.com/rendercv/rendercv/blob/main/.github/workflows/test.yaml): Run Tests + +**When it runs:** + +- Every push to `main` branch +- Every pull request +- Manually (via GitHub UI) +- When called by other workflows + +**What it does:** + +1. Checks out the repository with submodules (the [rendercv-skill](https://github.com/rendercv/rendercv-skill) submodule is needed for generated-file staleness tests) +2. Runs `just test-coverage` across **9 different environments** (3 operating systems × 3 Python versions: 3.12, 3.13, 3.14) +3. Combines all coverage reports and uploads them to show the coverage report + +### 2. [`deploy-docs.yaml`](https://github.com/rendercv/rendercv/blob/main/.github/workflows/deploy-docs.yaml): Deploy Documentation + +**When it runs:** + +- Every push to `main` branch +- Manually (via GitHub UI) + +**What it does:** + +1. Builds the documentation website using `just build-docs` +2. Uploads it to GitHub Pages +3. Documentation is now live at https://docs.rendercv.com + +### 3. [`create-executables.yaml`](https://github.com/rendercv/rendercv/blob/main/.github/workflows/create-executables.yaml): Create Executables + +**When it runs:** + +- Manually (via GitHub UI) +- When called by the release workflow + +**What it does:** + +1. Builds standalone executables using `just create-executable` for 4 platforms: + - Linux (x86_64 and ARM64) + - macOS (ARM64) + - Windows (x86_64) +2. Uploads executables as artifacts + +These are single-file executables that users can download and run without installing Python. + +### 4. [`release.yaml`](https://github.com/rendercv/rendercv/blob/main/.github/workflows/release.yaml): Publish a Release + +**When it runs:** + +- When a new GitHub release is published + +**What it does:** + +This is the complete release pipeline. It orchestrates everything: + +1. **Run tests:** Calls `test.yaml` to ensure everything works +2. **Build package:** Installs `uv`, builds Python wheel and source distribution using `uv build` +3. **Create executables:** Calls `create-executables.yaml` for all platforms +4. **Add assets to GitHub release:** Downloads and adds executables and wheel to the release +5. **Publish to PyPI:** Downloads and uploads package so users can `pip install rendercv` +6. **Publish Docker image:** Builds and pushes Docker image to GitHub Container Registry + +## Learn More + +- See [`.github/workflows/`](https://github.com/rendercv/rendercv/tree/main/.github/workflows) for RenderCV's workflow files +- See [GitHub Actions Documentation](https://docs.github.com/en/actions) for the official documentation. diff --git a/docs/developer_guide/how_to/add_locale.md b/docs/developer_guide/how_to/add_locale.md new file mode 100644 index 000000000..5a08eeab4 --- /dev/null +++ b/docs/developer_guide/how_to/add_locale.md @@ -0,0 +1,83 @@ +# Add a New Locale + +1. Create a YAML file in `src/rendercv/schema/models/locale/other_locales/` + + ```bash + touch src/rendercv/schema/models/locale/other_locales/mylanguage.yaml + ``` + +2. Add the schema reference and provide translations + + ```yaml + # yaml-language-server: $schema=../../../../../../schema.json + locale: + language: mylanguage + last_updated: "Your translation" + month: "Your translation" + months: "Your translation" + year: "Your translation" + years: "Your translation" + present: "Your translation" + month_abbreviations: + - Jan + - Feb + - Mar + - Apr + - May + - Jun + - Jul + - Aug + - Sep + - Oct + - Nov + - Dec + month_names: + - January + - February + - March + - April + - May + - June + - July + - August + - September + - October + - November + - December + ``` + +3. Add ISO 639-1 language code and flag emoji to `english_locale.py` + + Edit `src/rendercv/schema/models/locale/english_locale.py` and add your language to both mappings: + + In `language_iso_639_1`: + ```python + return { + "english": "en", + # ... existing languages + "mylanguage": "xx", # Add your two-letter ISO 639-1 code + }[self.language] + ``` + + In `flag_emoji`: + ```python + country = { + "english": "GB", + # ... existing languages + "mylanguage": "XX", # Add your two-letter ISO 3166-1 country code + }[self.language] + ``` + +4. Update the JSON Schema + + ```bash + just update-schema + ``` + + This regenerates `schema.json` so that editors can provide autocomplete and validation for the new locale. See [JSON Schema](../json_schema.md) for details. + +5. Done. Use it: + + ```bash + rendercv new "John Doe" --locale mylanguage + ``` diff --git a/docs/developer_guide/how_to/add_social_network.md b/docs/developer_guide/how_to/add_social_network.md new file mode 100644 index 000000000..a5e58b018 --- /dev/null +++ b/docs/developer_guide/how_to/add_social_network.md @@ -0,0 +1,114 @@ +# Add a New Social Network + +1. Add network name to `SocialNetworkName` type + + Edit `src/rendercv/schema/models/cv/social_network.py`: + + ```python + type SocialNetworkName = Literal[ + "LinkedIn", + "GitHub", + # ... existing networks + "MyNetwork", # Add your network here + ] + ``` + +2. Add URL pattern to `url_dictionary` + + Edit `src/rendercv/schema/models/cv/social_network.py`: + + ```python + url_dictionary: dict[SocialNetworkName, str] = { + "LinkedIn": "https://linkedin.com/in/", + # ... existing networks + "MyNetwork": "https://mynetwork.com/profile/", # Add URL base here + } + ``` + +3. (Optional) Add username validation + + If your network has special username format requirements, edit `src/rendercv/schema/models/cv/social_network.py`: + + ```python + match network: + case "Mastodon": + # ... existing validations + case "MyNetwork": + # ... your custom validation logic + ``` + +4. (Optional) Add custom URL generation + + If URL generation requires special logic (not just base + username), edit `src/rendercv/schema/models/cv/social_network.py`: + + ```python + @functools.cached_property + def url(self) -> str: + if self.network == "Mastodon": + # ... existing custom logic + elif self.network == "MyNetwork": + # ... your custom URL generation logic + else: + url = url_dictionary[self.network] + self.username + return url + ``` + +5. Add Font Awesome icon + + Edit `src/rendercv/renderer/templater/connections.py`: + + ```python + typst_fa_icons = { + "LinkedIn": "linkedin", + # ... existing icons + "MyNetwork": "my-icon-name", # Add your icon name here + } + ``` + + See available icons at: [fontawesome.com/search](https://fontawesome.com/search) + +6. Add test for URL generation + + Edit `tests/schema/models/cv/test_social_network.py`: + + ```python + @pytest.mark.parametrize( + ("network", "username", "expected_url"), + [ + # ... existing tests + ( + "MyNetwork", + "myusername", + "https://mynetwork.com/profile/myusername", + ), + ], + ) + def test_url(self, network, username, expected_url): + # test implementation + ``` + +7. Add network to test fixtures + + Edit `tests/renderer/conftest.py`, add your network to the `social_networks` list: + + ```python + social_networks=[ + SocialNetwork(network="LinkedIn", username="johndoe"), + # ... existing networks + SocialNetwork(network="MyNetwork", username="johndoe"), + ] + ``` + +8. Update test data and verify visual output + + ```bash + just update-testdata + ``` + + Check the generated PDFs in `tests/renderer/testdata/test_pdf_png/` to ensure your network appears correctly with the icon. + +9. Run tests to verify everything passes + + ```bash + just test + ``` diff --git a/docs/developer_guide/how_to/add_theme.md b/docs/developer_guide/how_to/add_theme.md new file mode 100644 index 000000000..ba5ccc1dd --- /dev/null +++ b/docs/developer_guide/how_to/add_theme.md @@ -0,0 +1,35 @@ +# Add a New Theme + +1. Create a YAML file in `src/rendercv/schema/models/design/other_themes/` + + ```bash + touch src/rendercv/schema/models/design/other_themes/mytheme.yaml + ``` + +2. Add the schema reference and override Classic theme defaults + + ```yaml + # yaml-language-server: $schema=../../../../../../schema.json + design: + theme: mytheme + # Override any defaults from classic_theme.py here + colors: + name: rgb(0,0,0) + typography: + font_family: New Computer Modern + # ... add any other overrides + ``` + +3. Update the JSON Schema + + ```bash + just update-schema + ``` + + This regenerates `schema.json` so that editors can provide autocomplete and validation for the new theme. See [JSON Schema](../json_schema.md) for details. + +4. Done. Use it: + + ```bash + rendercv new "John Doe" --theme mytheme + ``` diff --git a/docs/developer_guide/index.md b/docs/developer_guide/index.md index 1c1b014da..d394900a8 100644 --- a/docs/developer_guide/index.md +++ b/docs/developer_guide/index.md @@ -1,114 +1,82 @@ -# Developer Guide +--- +toc_depth: 1 +--- -All contributions to RenderCV are welcome! +# Setup -The source code is thoroughly documented and well-commented, making it an enjoyable read and easy to understand. A detailed documentation of the source code is available in the [API reference](../reference/index.md). +## Prerequisites +You need two tools to develop RenderCV: -## Getting Started +- **[`uv`](https://docs.astral.sh/uv/)**: Package and project manager. It also handles Python installations, so you don't need to install Python separately. +- **[`just`](https://github.com/casey/just)**: Command runner. Development commands are defined in the [`justfile`](https://github.com/rendercv/rendercv/blob/main/justfile), and you need `just` to run them. -There are two ways of developing RenderCV: [locally](#develop-locally) or [with GitHub Codespaces](#develop-with-github-codespaces). +Install them by following their official installation guides: -### Develop Locally +- [Install `uv`](https://docs.astral.sh/uv/getting-started/installation/) +- [Install `just`](https://github.com/casey/just#installation) -1. Install [Hatch](https://hatch.pypa.io/latest/). The installation guide for Hatch can be found [here](https://hatch.pypa.io/latest/install/#installation). - - Hatch is a Python project manager. It mainly allows you to define the virtual environments you need in [`pyproject.toml`](https://github.com/rendercv/rendercv/blob/main/pyproject.toml). Then, it takes care of the rest. Also, you don't need to install Python. Hatch will install it when you follow the steps below. +## Setting Up the Development Environment -2. Clone the repository. - ``` - git clone https://github.com/rendercv/rendercv.git - ``` -3. Go to the `rendercv` directory. - ``` - cd rendercv +1. Clone the repository: + + ```bash + git clone --recursive https://github.com/rendercv/rendercv.git ``` -4. Start using one of the virtual environments by activating it in the terminal. - Default development environment with Python 3.13: + and change to the repository directory: + ```bash - hatch shell default + cd rendercv ``` - The same environment, but with Python 3.10 (or 3.11, 3.12, 3.13): + > [!NOTE] + > The `--recursive` flag clones the [rendercv-skill](https://github.com/rendercv/rendercv-skill) submodule. If you forgot it, run `git submodule update --init` inside the repo. + +2. Set up the development environment (creates a virtual environment in `./.venv` with all dependencies): + ```bash - hatch shell test.py3.10 + just sync ``` -5. Finally, activate the virtual environment in your integrated development environment (IDE). In Visual Studio Code: +3. Run `just test` to verify all tests pass and everything is set up correctly. + +4. Finally, activate the virtual environment in your integrated development environment (IDE). In Visual Studio Code: - Press `Ctrl+Shift+P`. - Type `Python: Select Interpreter`. - - Select one of the virtual environments created by Hatch. + - Select the one in `./.venv`. -### Develop with GitHub Codespaces +That's it! You're now ready to start developing RenderCV. -1. [Fork](https://github.com/rendercv/rendercv/fork) the repository. -2. Navigate to the forked repository. -3. Click the <> **Code** button, then click the **Codespaces** tab, and then click **Create codespace on main**. +## Available Commands -Then, [Visual Studio Code for the Web](https://code.visualstudio.com/docs/editor/vscode-web) will be opened with a ready-to-use development environment. +### Development -This is done with [Development containers](https://containers.dev/), and the environment is defined in the [`.devcontainer/devcontainer.json`](https://github.com/rendercv/rendercv/blob/main/.devcontainer/devcontainer.json) file. Dev containers can also be run locally using various [supporting tools and editors](https://containers.dev/supporting). +- `just sync`: Sync all dependencies (including extras and dev groups) +- `just format`: Format code with `black` and `ruff` +- `just check`: Run all checks (`ruff`, `ty`, `pre-commit`) +- `just lock`: Update `uv.lock` file -## Available Commands +### Testing -These commands are defined in the [`pyproject.toml`](https://github.com/rendercv/rendercv/blob/main/pyproject.toml) file. +- `just test`: Run tests with pytest +- `just test-coverage`: Run tests with coverage report +- `just update-testdata`: Update test data files (see [Testing](testing.md) for more details) -- Build the package - ```bash - hatch run build - ``` -- Format the code with [Black](https://github.com/psf/black) and [Ruff](https://github.com/astral-sh/ruff) - ```bash - hatch run format - ``` -- Lint the code with [Ruff](https://github.com/astral-sh/ruff) - ```bash - hatch run lint - ``` -- Run [pre-commit](https://pre-commit.com/) - ```bash - hatch run precommit - ``` -- Check the types with [Pyright](https://github.com/RobertCraigie/pyright-python) - ```bash - hatch run check-types - ``` -- Run the tests with Python 3.13 - ```bash - hatch run test - ``` -- Run the tests with Python 3.13 and generate the coverage report - ```bash - hatch run test-and-report - ``` -- Update [schema.json](https://github.com/rendercv/rendercv/blob/main/schema.json) - ```bash - hatch run update-schema - ``` -- Update [`examples`](https://github.com/rendercv/rendercv/tree/main/examples) folder - ```bash - hatch run update-examples - ``` -- Create an executable version of RenderCV with [PyInstaller](https://www.pyinstaller.org/) - ```bash - hatch run exe:create - ``` -- Preview the documentation as you write it - ```bash - hatch run docs:serve - ``` -- Build the documentation - ```bash - hatch run docs:build - ``` -- Update figures of the entry types in the "[Structure of the YAML Input File](../user_guide/structure_of_the_yaml_input_file.md)" - ```bash - hatch run docs:update-entry-figures - ``` +### Documentation + +- `just build-docs`: Build documentation +- `just serve-docs`: Serve documentation locally with live reload + +### Scripts + +- `just update-schema`: Update JSON schema +- `just update-entry-figures`: Update entry figures for documentation +- `just update-examples`: Update example files +- `just create-executable`: Create standalone executable -## About [`pyproject.toml`](https://github.com/rendercv/rendercv/blob/main/pyproject.toml) +### Utilities -[`pyproject.toml`](https://github.com/rendercv/rendercv/blob/main/pyproject.toml) contains the metadata, dependencies, and tools required for the project. Please read through the file to understand the project's technical details. +- `just count-lines`: Count lines of Python code in the `src/` directory diff --git a/docs/developer_guide/json_schema.md b/docs/developer_guide/json_schema.md new file mode 100644 index 000000000..6f6aa4ed3 --- /dev/null +++ b/docs/developer_guide/json_schema.md @@ -0,0 +1,141 @@ +--- +toc_depth: 3 +--- + +# JSON Schema + +## The Problem + +You may have encountered this before, even if you didn't realize it: + +**VS Code settings** (`settings.json`): +```json +{ + "editor.fontSize": 14, + "editor.tabSiz": 4 // ← Typo! VS Code highlights it red immediately +} +``` + +**GitHub Actions workflows** (`.github/workflows/test.yaml`): +```yaml +on: + push: + branchs: # ← Typo! Your editor underlines it, suggests "branches" + - main +``` + +**These files are completely different (VS Code settings, GitHub workflows). But you get autocomplete and validation in both.** How? + +VS Code doesn't just "know" what's valid in `settings.json`. GitHub Actions workflows don't magically get autocomplete. + +**Someone had to tell your editor:** "Here are all the valid fields, their types, and what they mean." + +That "someone" is [**JSON Schema**](https://json-schema.org). + +## What is JSON Schema? + +JSON Schema is a **standard way to describe the structure of JSON/YAML documents**. + +Think of it as a specification, a formal description of what's valid: + +```json +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Your full name" + }, + "age": { + "type": "integer", + "minimum": 0, + "description": "Your age in years" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "required": ["name"] +} +``` + +This schema says: + +- A valid document is an object +- It must have a `name` field (string, required) +- It can have an `age` field (non-negative integer, optional) +- It can have an `email` field (string matching email format, optional) + +**Why does JSON Schema exist?** + +Because JSON and YAML files are **everywhere**: configuration files, API requests/responses, CI/CD workflows, application settings, data files. + +You could write documentation on how to write those JSON/YAML files: "The `name` field is required and must be a string. The `age` field is optional and must be a non-negative integer." But **documentation is for humans to read, not machines**. + +JSON Schema is the **same information in machine-readable format** so editors can understand it. + +Once your editor has a schema, it can provide autocomplete, catch typos, and show inline documentation as you type. + +This is why: + +- **Microsoft publishes a JSON Schema for VS Code settings:** your editor fetches it and provides autocomplete +- **GitHub publishes a JSON Schema for Actions workflows:** that's how you get field suggestions +- **Thousands of tools do the same:** Kubernetes, Docker, Terraform, ESLint, `package.json`, `tsconfig.json`, the list goes on + +## RenderCV's JSON Schema + +RenderCV has the same problem. Users write their CVs in YAML, and we want them to have a smooth editor experience with autocomplete, typo detection, and inline documentation. + +**Solution:** Publish a JSON Schema for RenderCV YAML files. + +![JSON Schema of RenderCV](../assets/images/json_schema.gif) + +That's why [`schema.json`](https://github.com/rendercv/rendercv/blob/main/schema.json) exists in the repository. + +## How the Schema is Generated? + +We don't write `schema.json` by hand. **It's automatically generated from Pydantic models.** + +RenderCV's entire data structure is defined using Pydantic models (see [Understanding RenderCV](understanding_rendercv.md) for details). Pydantic has a built-in feature: `model_json_schema()`, which generates JSON Schema from your models. + +Whenever data models change, run: + +```bash +just update-schema +``` + +This runs [`scripts/update_schema.py`](https://github.com/rendercv/rendercv/blob/main/scripts/update_schema.py), which regenerates `schema.json`. + +## How Editors Know to Use RenderCV's Schema? + +There are two ways editors discover and use RenderCV's schema: + +### 1. Manual Declaration + +Add a special comment at the top of your YAML file: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.4/schema.json + +cv: + name: John Doe +``` + +This tells the editor: "Use RenderCV's schema for this file." Note the version tag in the URL, which ensures you get the schema matching your RenderCV version. + +**Requirements:** Your editor needs to support this. For VS Code, install the [YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml). + +### 2. Schema Store (Automatic) + +RenderCV's schema is listed in [SchemaStore](https://github.com/SchemaStore/schemastore), a central registry of schemas that most IDEs use. + +In SchemaStore, RenderCV's schema is configured to automatically activate for files ending with `_CV.yaml`. This means: + +- If your file is named `John_Doe_CV.yaml` +- And your editor uses SchemaStore (VS Code with YAML extension does) +- You get autocomplete automatically, no comment needed + +## Learn More + +See [Pydantic JSON Schema](https://docs.pydantic.dev/latest/concepts/json_schema/) for how Pydantic generates schemas from models. diff --git a/docs/developer_guide/project_management.md b/docs/developer_guide/project_management.md new file mode 100644 index 000000000..ca065b9e7 --- /dev/null +++ b/docs/developer_guide/project_management.md @@ -0,0 +1,134 @@ +--- +toc_depth: 3 +--- + +# Project Management + +## What is "Project Management"? + +When you look at RenderCV's repository, you see: + +``` +. +├── src/ ← The actual RenderCV code +├── pyproject.toml ← Project configuration +├── justfile ← Command shortcuts +├── scripts/ ← Supplementary Python scripts +├── .pre-commit-config.yaml ← Pre-commit configuration +└── uv.lock ← Dependency lock file +``` + +**Project management is everything except `src/`.** It's all the infrastructure that lets us: + +- Share RenderCV with users (`pip install rendercv`) +- Manage dependencies consistently +- Automate testing, building, and releases +- Ensure reproducibility across machines and time + +## Why Can't We Just Write Python Code? + +RenderCV is a Python project. The actual source code lives in `src/rendercv/`. Why do we need all these other files - `pyproject.toml`, `uv.lock`, `justfile`, `.github/workflows/`, etc.? + +**Because code alone doesn't solve two critical problems: distribution and development environment.** + +### Problem 1: Distribution + +**How do users get your code?** + +You could tell them "download these files, install dependencies with `pip install -r requirements.txt`, and run them with `python main.py`". But users want `pip install rendercv` and have it work instantly with `rendercv` command. + +**This requires:** Packaging your code and uploading to [PyPI](https://pypi.org) (Python Package Index). + +### Problem 2: Development Environment + +You have the source code. Two developers want to contribute. + +Developer A installs today with Python 3.11 and gets `pydantic==2.10`. Tests pass. Developer B installs one month later with Python 3.12 and gets `pydantic==2.11` which has breaking changes. Tests fail. "Works on A's machine" but not B's. B asks: "What formatter do you use? What settings? How do I run tests?" + +**What needs to happen:** Everyone gets the same Python version, same package versions (locked, not "latest"), same development tools with same settings. All in one command. + +**This requires:** Locking dependencies (Python version, every package, frozen in time), configuring all tools in one place, and automating setup so it's identical for everyone. + +### The Solution + +All those files you see in the repository (`pyproject.toml`, `uv.lock`, `justfile`, and more) work together to solve these problems. The result: + +**For users:** +```bash +pip install rendercv +``` + +Works instantly. Every time. Anywhere. All dependencies installed automatically. + +**For developers** ([Setup](index.md)): +```bash +just sync +``` + +One command. Identical environment for everyone: correct Python version, exact dependency versions, all dev tools ready. Works today, works in 2027. Bug from 6 months ago? Check out that commit, run `just sync`, exact environment recreated. + +The rest of this guide explains what each file does. + +## Files and Folders in the Root + +### [`pyproject.toml`](https://github.com/rendercv/rendercv/blob/main/pyproject.toml) + +The project definition file. This is the [standard way to configure a Python project](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/). + +This file defines: + +- Project metadata (name, version, description) +- Dependencies (what packages RenderCV needs) +- Entry points (makes `rendercv` a command) +- Build configuration (how to package RenderCV) +- Tool settings (`ruff`, `ty`, `pytest`, etc.) + +Open the file to see the full configuration with detailed comments. + +### [`justfile`](https://github.com/rendercv/rendercv/blob/main/justfile) + +[just](https://github.com/casey/just) is a command runner, a tool that lets you define terminal commands in a file and run them easily. + +**Why do we need it?** During development, you constantly run commands like "run tests with coverage", "format all code", "build and serve docs". Without standardization: + +- Everyone types different commands with different options +- You have to remember long command strings + +The `justfile` solves this: define each command once, and everyone runs the same thing: + +```bash +just test # Runs pytest with the right options +just format # Formats code with ruff +just serve-docs # Builds and serves documentation locally +just update-schema # Regenerates schema.json +``` + +### [`scripts/`](https://github.com/rendercv/rendercv/tree/main/scripts) + +Python scripts that automate some repetitive tasks that are not part of RenderCV's functionality. + +**Why do we need it?** Some tasks need to be done repeatedly but are too complex for simple shell commands: + +- `update_schema.py`: Generate `schema.json` (see [JSON Schema](json_schema.md) for more details) from pydantic models +- `update_examples.py`: Regenerate all example YAML files and PDFs in [`examples/`](https://github.com/rendercv/rendercv/tree/main/examples) folder +- `create_executable.py`: Build standalone executable of RenderCV + +These scripts are called by `just` commands (`just update-schema`, `just update-examples`, etc.). + +### [`.pre-commit-config.yaml`](https://github.com/rendercv/rendercv/blob/main/.pre-commit-config.yaml) + +Configuration file for [`pre-commit`](https://pre-commit.com/), a tool that runs code quality checks. + +**Why do we need it?** Pre-commit's value is **fast CI/CD**. [pre-commit.ci](https://pre-commit.ci/) (free for open-source projects) automatically checks if the source code has any `ruff` or `ty` errors on every push and pull request. Forgot to format your code? The workflow fails, making it immediately obvious. + +### [`uv.lock`](https://github.com/rendercv/rendercv/blob/main/uv.lock) + +A dependency lock file. This is a record of the exact version of every package RenderCV uses (including dependencies of dependencies of dependencies...). + +**Why do we need it?** Remember development environment problem? This file solves it. When you run `just sync`, `uv` reads this file and installs the exact same versions everyone else has, not "the latest version", but "the exact version that's known to work". Without this file, developers would get different package versions and environments would drift apart. + +**Never edit this manually.** Use `just lock` to update it. **Always commit it to git.** That's how everyone gets identical environments. + +## Learn More + +See the [`uv` documentation](https://docs.astral.sh/uv/) for more information on project management. diff --git a/docs/developer_guide/testing.md b/docs/developer_guide/testing.md index 835a1d93c..3aecfeb68 100644 --- a/docs/developer_guide/testing.md +++ b/docs/developer_guide/testing.md @@ -1,36 +1,75 @@ +--- +toc_depth: 2 +--- + # Testing -After implementing a new feature or fixing a bug, one can run all the tests to see if anything is broken. +Tests check if your code does what it's supposed to do. Every time you change something, you need to verify it still works. Instead of manually checking everything, you can write test code once and rerun it after each change. -To run all the tests with each Python version (3.10, 3.11, 3.12, and 3.13), use the following command. +Here's a simple example: -```bash -hatch run test:test +```python +def sum(a, b): + return a + b + +def test_sum(): + assert sum(2, 3) == 5 + assert sum(-1, 1) == 0 + assert sum(0, 0) == 0 ``` -To run the tests with a specific Python version, use the following command. +If you change something in `sum`, you can run `test_sum` again to see if it's still working. + +All of the tests of RenderCV are written in [`tests/`](https://github.com/rendercv/rendercv/tree/main/tests) directory. + +## [`pytest`](https://github.com/pytest-dev/pytest): Testing Framework + +`pytest` is a Python library that provides utilities to write and run tests. + +**How does it work?** When you run `pytest`, it searches for files matching `test_*.py` in the `tests/` directory and executes all functions starting with `test_`. + +## Running RenderCV Tests + +Whenever you make changes to RenderCV's source code, run the tests to ensure everything still works. If all tests pass, your changes didn't break anything. ```bash -hatch run test.py3.10:test +just test ``` -To run the tests with Python 3.13 and generate a coverage report, use the following command. +## Reference File Comparison + +Some tests in [`tests/renderer/`](https://github.com/rendercv/rendercv/tree/main/tests/renderer) (specifically [`test_pdf_png.py`](https://github.com/rendercv/rendercv/blob/main/tests/renderer/test_pdf_png.py), [`test_typst.py`](https://github.com/rendercv/rendercv/blob/main/tests/renderer/test_typst.py), [`test_markdown.py`](https://github.com/rendercv/rendercv/blob/main/tests/renderer/test_markdown.py), and [`test_html.py`](https://github.com/rendercv/rendercv/blob/main/tests/renderer/test_html.py)) use reference file comparison: + +1. Tests generate output files by running RenderCV +2. Generated files are compared against reference files in `tests/renderer/testdata/` +3. If they match exactly, the test passes. Any difference fails the test. + +### Updating Reference Files + +You fix a bug that changes RenderCV's output. Tests fail because the new output doesn't match old reference files. + +**This is expected.** You intentionally changed the output. You need to update the reference files: ```bash -hatch run test-and-report +just update-testdata ``` -Once new commits are pushed to the `main` branch, the [`test.yaml`](https://github.com/rendercv/rendercv/blob/main/.github/workflows/test.yaml) workflow will be automatically triggered, and the tests will run. -## About [`testdata`](https://github.com/rendercv/rendercv/tree/main/tests/testdata) folder +!!! warning + **Manually verify new reference files before committing.** These become the source of truth. If you commit broken reference files, tests will pass even when RenderCV produces bad output. Always check generated PDFs and PNGs carefully. + +## [`pytest-cov`](https://github.com/pytest-dev/pytest-cov): Coverage Plugin for `pytest` -In some of the tests: +Coverage is a measure of which code lines are executed when tests run. If tests execute a line, it's included in coverage. If tests execute all lines in `src/rendercv/`, coverage is 100%. -- RenderCV generates an output with a sample input. -- Then, the output is compared with a reference output, which has been manually generated and stored in `testdata`. If the files differ, the tests fail. +**Why does it matter?** Coverage reports show you which parts of your code aren't tested yet, so you know where to write more tests. +Run tests with coverage: -When the `testdata` folder needs to be updated, it can be manually regenerated by setting `update_testdata` to `True` in `conftest.py` and running the tests. +```bash +just test-coverage +``` -!!! warning - - Whenever the `testdata` folder is generated, the files should be reviewed manually to ensure everything works as expected. - - `update_testdata` should be set to `False` before committing the changes. +This generates two outputs: + +- **Terminal:** Overall coverage percentage +- **HTML report:** Open `htmlcov/index.html` to see exactly which lines are covered (green) and which aren't (red) diff --git a/docs/developer_guide/understanding_rendercv.md b/docs/developer_guide/understanding_rendercv.md new file mode 100644 index 000000000..3a42e6901 --- /dev/null +++ b/docs/developer_guide/understanding_rendercv.md @@ -0,0 +1,315 @@ +--- +toc_depth: 3 +--- + +# Understanding RenderCV + +This guide walks you through how RenderCV works, explaining each step and the tools we use. + +## The Core Workflow + +RenderCV does more than this (Markdown, HTML, PNG outputs, watching files, etc.), but at its core, what happens is: + +```mermaid +flowchart LR + A[YAML file] --> B[Typst file] + B --> C[PDF] +``` + +Read a YAML file, generate a Typst file, compile it to PDF. Everything else is built on top of this foundation. + +## What is Typst? + +Before we dive into the steps, let's understand what [Typst](https://typst.app/) is. + +Typst is a computer language. Just like Python, HTML, or JavaScript. You write Typst code to describe what a page should look like and what content it contains. You save it as a text file (`.typ` extension). When you compile a `*.typ` file with Typst compiler, you get a PDF. + +RenderCV generates a Typst file from your YAML and compiles it with the Typst compiler to produce your CV as a PDF. + +## Step 1: Reading the YAML File + +When a user gives us a YAML file like this: + +```yaml +cv: + name: John Doe + location: San Francisco, CA + sections: + education: + - institution: MIT + degree: PhD + start_date: 2020-09 + end_date: 2024-05 +``` + +We need to: + +1. Parse the YAML into Python dictionaries +2. Validate the data (Does `start_date` come before `end_date`? Is `name` actually provided and is it a string?) + +### [`ruamel.yaml`](https://github.com/pycontribs/ruamel-yaml): YAML Parser + +Python doesn't have a built-in YAML library. To read YAML files, you need a library. **We use `ruamel.yaml`**, one of the best YAML parsers available. + +What does it do? Simple: **converts YAML text into Python dictionaries.** + +**YAML file** (`cv.yaml`): +```yaml +cv: + name: John Doe + location: San Francisco, CA + sections: + education: + - institution: MIT + degree: PhD + start_date: 2020-09 +``` + +**After parsing with `ruamel.yaml`:** +```python +from ruamel.yaml import YAML + +yaml = YAML() +data = yaml.load(open("cv.yaml")) + +# Now data is a Python dictionary: +{ + "cv": { + "name": "John Doe", + "location": "San Francisco, CA", + "sections": { + "education": [ + { + "institution": "MIT", + "degree": "PhD", + "start_date": "2020-09" + } + ] + } + } +} + +# You can access it like any Python dict: +data["cv"]["name"] # "John Doe" +data["cv"]["sections"]["education"][0]["institution"] # "MIT" +``` + +That's it. YAML text becomes a Python dictionary we can work with. + +`ruamel.yaml` is being called in [`src/rendercv/schema/yaml_reader.py`](https://github.com/rendercv/rendercv/blob/main/src/rendercv/schema/yaml_reader.py). + +### [`pydantic`](https://github.com/pydantic/pydantic): Python Dictionary Validator + +Now we have a dictionary. We need to validate it. Without a library, you'd write: + +```python +if "name" not in data["cv"]: + raise ValueError("Missing 'name' field") + +if not isinstance(data["cv"]["name"], str): + raise ValueError("name must be a string") + +if "sections" in data["cv"]: + for section_name, entries in data["cv"]["sections"].items(): + for entry in entries: + if "start_date" in entry and "end_date" in entry: + # Parse dates, compare them... + # This is already hundreds of lines and we're barely started +``` + +With `pydantic`, we can define the structure once: + +```python +from pydantic import BaseModel +from datetime import date as Date + +class Education(BaseModel): + institution: str + start_date: Date + end_date: Date + + @pydantic.model_validator(mode="after") + def check_dates(self): + if self.start_date > self.end_date: + raise ValueError("start_date cannot be after end_date") + return self + +class Cv(BaseModel): + name: str + location: str | None = None + education: list[Education] +``` + +Then validate: + +```python +# This dictionary (from ruamel.yaml): +data = { + "name": "John Doe", + "location": "San Francisco", + "education": [ + { + "institution": "MIT", + "start_date": "2020-09", + "end_date": "2024-05" + } + ] +} + +# Becomes this validated object: +cv = Cv.model_validate(data) + +# Now you have clean, validated objects: +cv.name # "John Doe" +cv.education[0].institution # "MIT" +cv.education[0].start_date # "2020-09", guaranteed dates are valid +``` + +That's the power. Dictionary goes in, `pydantic` checks everything, clean Python object comes out. + +![Strict Validation Feature of RenderCV](../assets/images/validation.gif) + +**RenderCV's entire data model is `pydantic` models all the way down:** + +```python +class RenderCVModel(BaseModel): + cv: Cv # ← pydantic model + design: Design # ← pydantic model + locale: Locale # ← pydantic model + settings: Settings # ← pydantic model +``` + +Each field is another `pydantic` model. `Cv` contains more `pydantic` models like `EducationEntry`, `ExperienceEntry`, etc. It's nested validation: when you validate `RenderCVModel`, `pydantic` automatically validates every nested model too. One `model_validate()` call checks the entire structure. + +See [`src/rendercv/schema/models/rendercv_model.py`](https://github.com/rendercv/rendercv/blob/main/src/rendercv/schema/models/rendercv_model.py) for the top-level model. + +## Step 2: Generating the Typst File + +Now we need to generate a Typst file: + +```typst += John Doe +San Francisco, CA + +== Education +#strong[MIT] #h(1fr) 2020 – 2024 +PhD in Computer Science +``` + +You could try string concatenation: + +```python +typst = f"= {cv.name}\n" +if cv.location: + typst += f"{cv.location}\n" +typst += "\n" + +for section_title, entries in cv.sections.items(): + typst += f"== {section_title}\n" + for entry in entries: + typst += f"#strong[{entry.institution}]" + # What about optional fields? Spacing? Line breaks? + # Multiple themes with different layouts? + # This is impossible to maintain! +``` + +This doesn't work. You're building hundreds of lines of string concatenation logic, handling conditionals, managing whitespace. It's unworkable. + +This is why **templating engines were invented**. When you need to programmatically generate complex text files, you need templates. + +### [`jinja2`](https://github.com/pallets/jinja): Templating Engine + +`jinja2` is the most famous templating engine for Python. + +**Template file** (`Header.j2.typ`): +```jinja2 += {{ cv.name }} +{% if cv.location %} +{{ cv.location }} +{% endif %} + +{% if cv.email %} +#link("mailto:{{ cv.email }}") +{% endif %} +``` + +**Python code:** +```python +template = jinja2_env.get_template("Header.j2.typ") +output = template.render(cv=cv) +``` + +**Result:** +```typst += John Doe +San Francisco, CA + +#link("mailto:john@example.com") +``` + +Clean separation: templates define layout, Python code provides data. Users can override templates to customize their CV without touching Python code. + +Typst templates live in [`src/rendercv/renderer/templater/templates/typst/`](https://github.com/rendercv/rendercv/blob/main/src/rendercv/renderer/templater/templates/typst/). + +`jinja2` is being called in [`src/rendercv/renderer/templater/templater.py`](https://github.com/rendercv/rendercv/blob/main/src/rendercv/renderer/templater/templater.py). + +### [`markdown`](https://github.com/Python-Markdown/markdown): Markdown to Typst + +Users want to write Markdown in their YAML: + +```yaml +highlights: + - "**Published** [3 papers](https://example.com) on neural networks" + - "Collaborated with *Professor Smith*" +``` + +But Typst doesn't understand `**bold**` or `[links](url)`. We need Typst syntax: `#strong[bold]` and `#link("url")[text]`. + +**We use the `markdown` library.** It parses Markdown into an XML tree. Then we walk the tree and convert each element to Typst: + +```python +match element.tag: + case "strong": + return f"#strong[{content}]" + case "em": + return f"#emph[{content}]" + case "a": + href = element.get("href") + return f'#link("{href}")[{content}]' +``` + +Result: `#strong[Published] #link("https://example.com")[3 papers]` + +See [`src/rendercv/renderer/templater/markdown_parser.py`](https://github.com/rendercv/rendercv/blob/main/src/rendercv/renderer/templater/markdown_parser.py). The `markdown_to_typst()` function does this conversion. + +## Step 3: Compiling to PDF + +### [`typst`](https://github.com/messense/typst-py): Typst Compiler + +`typst` library is the Python bindings for the Typst compiler. + +```python +from typst import compile +compile("cv.typ", output="cv.pdf") +``` + +Done. Typst file has been compiled to PDF. + +`typst` is being called in [`src/rendercv/renderer/pdf_png.py`](https://github.com/rendercv/rendercv/blob/main/src/rendercv/renderer/pdf_png.py). + +## The Complete Pipeline + +When you run `rendercv render cv.yaml`: + +1. **Parse** - `ruamel.yaml` reads YAML → Python dict +2. **Validate** - `pydantic` validates dict → `RenderCVModel` object +3. **Generate** - `jinja2` renders templates with data → Typst file +4. **Compile** - `typst` compiles Typst → PDF + +Everything else (Markdown support, watch mode, PNG output, HTML export) builds on this core. + +## Learn More + +- [`src/rendercv/cli/render_command/run_rendercv.py`](https://github.com/rendercv/rendercv/blob/main/src/rendercv/cli/render_command/run_rendercv.py): The complete flow +- [`src/rendercv/schema/models/rendercv_model.py`](https://github.com/rendercv/rendercv/blob/main/src/rendercv/schema/models/rendercv_model.py): The top-level Pydantic model +- [`src/rendercv/renderer/templater/templater.py`](https://github.com/rendercv/rendercv/blob/main/src/rendercv/renderer/templater/templater.py): Template rendering diff --git a/docs/developer_guide/writing_documentation.md b/docs/developer_guide/writing_documentation.md deleted file mode 100644 index 39da76ef8..000000000 --- a/docs/developer_guide/writing_documentation.md +++ /dev/null @@ -1,52 +0,0 @@ -# Writing Documentation - -The documentation's source files are located in the [`docs`](https://github.com/rendercv/rendercv/tree/main/docs) directory and it is built using the [MkDocs](https://github.com/mkdocs/mkdocs) package. To work on the documentation and see the changes in real-time, run the following command. - -```bash -hatch run docs:serve -``` - -Once the changes are pushed to the `main` branch, the [`deploy-docs.yaml`](https://github.com/rendercv/rendercv/blob/main/.github/workflows/deploy-docs.yaml) workflow will be automatically triggered, and [docs.rendercv.com](https://docs.rendercv.com/) will be updated to the most recent version. - - -## Updating the [`examples`](https://github.com/rendercv/rendercv/tree/main/examples) folder - -The `examples` folder includes example YAML files for all the built-in themes, along with their corresponding PDF outputs. Also, there are PNG files of the first pages of each theme in [`docs/assets/images`](https://github.com/rendercv/rendercv/tree/main/docs/assets/images). These examples are shown in [`README.md`](https://github.com/rendercv/rendercv/blob/main/README.md). - -These files are generated using [`scripts/update_examples.py`](https://github.com/rendercv/rendercv/blob/main/scripts/update_examples.py). The contents of the examples are taken from the [`create_a_sample_data_model`](https://docs.rendercv.com/reference/data/#rendercv.data.create_a_sample_data_model) function from [`rendercv.data`](https://docs.rendercv.com/reference/data/). - -Run the following command to update the `examples` folder. - -```bash -hatch run update-examples -``` - -Once a new release is created on GitHub, the [`publish-to-pypi.yaml`](https://github.com/rendercv/rendercv/blob/main/.github/workflows/publish-to-pypi.yaml) workflow will be automatically triggered, and the `examples` folder will be updated to the most recent version. - -## Updating figures of the entry types in the "[Structure of the YAML Input File](../user_guide/structure_of_the_yaml_input_file.md)" - -There are example figures for each entry type for each theme in the "[Structure of the YAML Input File](../user_guide/structure_of_the_yaml_input_file.md)" page. - -The figures are generated using [`scripts/update_entry_figures.py`](https://github.com/rendercv/rendercv/blob/main/scripts/update_entry_figures.py). - -Run the following command to update the figures. - -```bash -hatch run docs:update-entry-figures -``` - -Once a new release is created on GitHub, the [`publish-to-pypi.yaml`](https://github.com/rendercv/rendercv/blob/main/.github/workflows/publish-to-pypi.yaml) workflow will be automatically triggered, and the figures will be updated to the most recent version. - -## Updating the JSON Schema ([`schema.json`](https://github.com/rendercv/rendercv/blob/main/schema.json)) - -The schema of RenderCV's input file is defined using [Pydantic](https://docs.pydantic.dev/latest/). Pydantic allows automatic creation and customization of JSON schemas from Pydantic models. - -The JSON Schema is also generated using [`scripts/update_schema.py`](https://github.com/rendercv/rendercv/blob/main/scripts/update_schema.py). It uses [`generate_json_schema`](https://docs.rendercv.com/reference/data/#rendercv.data.generate_json_schema) function from [`rendercv.data`](https://docs.rendercv.com/reference/data/). - -Run the following command to update the JSON Schema. - -```bash -hatch run update-schema -``` - -Once a new release is created on GitHub, the [`publish-to-pypi.yaml`](https://github.com/rendercv/rendercv/blob/main/.github/workflows/publish-to-pypi.yaml) workflow will be automatically triggered, and `schema.json` will be updated to the most recent version. diff --git a/docs/docs_templating.py b/docs/docs_templating.py new file mode 100644 index 000000000..4e2394203 --- /dev/null +++ b/docs/docs_templating.py @@ -0,0 +1,149 @@ +"""This script generates the example entry figures and creates an environment for +documentation templates using `mkdocs-macros-plugin`. For example, the content of the +example entries found in +"[Structure of the YAML Input File](https://docs.rendercv.com/user_guide/structure_of_the_yaml_input_file/)" +are coming from this script. +""" + +import io +import pathlib +from typing import get_args + +import pydantic +import ruamel.yaml + +from rendercv.schema.models.cv.section import ( + BulletEntry, + EducationEntry, + ExperienceEntry, + NormalEntry, + NumberedEntry, + OneLineEntry, + PublicationEntry, + ReversedNumberedEntry, +) +from rendercv.schema.models.cv.social_network import available_social_networks +from rendercv.schema.models.design.built_in_design import available_themes +from rendercv.schema.models.design.classic_theme import ( + Alignment, + BodyAlignment, + Bullet, + PageSize, + PhoneNumberFormatType, + SectionTitleType, +) +from rendercv.schema.models.design.font_family import available_font_families +from rendercv.schema.models.locale.locale import available_locales +from rendercv.schema.yaml_reader import read_yaml + +repository_root = pathlib.Path(__file__).parent.parent +rendercv_path = repository_root / "src" / "rendercv" +image_assets_directory = pathlib.Path(__file__).parent / "assets" / "images" + + +class SampleEntries(pydantic.BaseModel): + education_entry: EducationEntry + experience_entry: ExperienceEntry + normal_entry: NormalEntry + publication_entry: PublicationEntry + one_line_entry: OneLineEntry + bullet_entry: BulletEntry + numbered_entry: NumberedEntry + reversed_numbered_entry: ReversedNumberedEntry + text_entry: str + + +def dictionary_to_yaml(dictionary: dict): + """Converts a dictionary to a YAML string. + + Args: + dictionary: The dictionary to be converted to YAML. + Returns: + The YAML string. + """ + yaml_object = ruamel.yaml.YAML() + yaml_object.width = 60 + yaml_object.indent(mapping=2, sequence=4, offset=2) + with io.StringIO() as string_stream: + yaml_object.dump(dictionary, string_stream) + return string_stream.getvalue() + + +def define_env(env): + # See https://mkdocs-macros-plugin.readthedocs.io/en/latest/macros/ + sample_entries = read_yaml( + repository_root / "docs" / "user_guide" / "sample_entries.yaml" + ) + # validate the parsed dictionary by creating an instance of SampleEntries: + sample_entries = SampleEntries(**sample_entries).model_dump() + + entries_showcase = {} + for entry_name, entry in sample_entries.items(): + proper_entry_name = entry_name.replace("_", " ").title().replace(" ", "") + entries_showcase[proper_entry_name] = { + "yaml": dictionary_to_yaml(entry), + "figures": [ + { + "path": f"../../assets/images/{theme}/{entry_name}.png", + "alt_text": f"{proper_entry_name} in {theme}", + "theme": theme, + } + for theme in available_themes + ], + } + + env.variables["sample_entries"] = entries_showcase + env.variables["entry_count"] = len(sample_entries) + env.variables["entry_names"] = [ + f"[{entry_name}](#{entry_name.lower()})" for entry_name in entries_showcase + ] + + # Available themes strings (put available themes between ``) + themes = [ + f"`{theme}`" if theme != "classic" else "`classic` (default)" + for theme in available_themes + ] + env.variables["available_themes"] = ", ".join(themes) + env.variables["theme_count"] = len(available_themes) + + # Available locales string + locales = [ + f"`{locale}`" if locale != "english" else "`english` (default)" + for locale in available_locales + ] + env.variables["available_locales"] = ", ".join(locales) + + # Available social networks strings (put available social networks between ``) + social_networks = [ + f"`{social_network}`" for social_network in available_social_networks + ] + env.variables["available_social_networks"] = ", ".join(social_networks) + + # Others: + env.variables["available_page_sizes"] = ", ".join( + [f"`{page_size}`" for page_size in get_args(PageSize.__value__)] + ) + env.variables["available_font_families"] = ", ".join( + [f"`{font_family}`" for font_family in available_font_families] + ) + env.variables["available_body_alignments"] = ", ".join( + [f"`{text_alignment}`" for text_alignment in get_args(BodyAlignment.__value__)] + ) + env.variables["available_phone_number_formats"] = ", ".join( + [ + f"`{phone_number_format}`" + for phone_number_format in get_args(PhoneNumberFormatType.__value__) + ] + ) + env.variables["available_alignments"] = ", ".join( + [f"`{alignment}`" for alignment in get_args(Alignment.__value__)] + ) + env.variables["available_section_title_types"] = ", ".join( + [ + f"`{section_title_type}`" + for section_title_type in get_args(SectionTitleType.__value__) + ] + ) + env.variables["available_bullets"] = ", ".join( + [f"`{bullet}`" for bullet in get_args(Bullet.__value__)] + ) diff --git a/docs/dynamic_content_generation.py b/docs/dynamic_content_generation.py deleted file mode 100644 index 24bbd3a6e..000000000 --- a/docs/dynamic_content_generation.py +++ /dev/null @@ -1,162 +0,0 @@ -"""This script generates the example entry figures and creates an environment for -documentation templates using `mkdocs-macros-plugin`. For example, the content of the -example entries found in -"[Structure of the YAML Input File](https://docs.rendercv.com/user_guide/structure_of_the_yaml_input_file/)" -are coming from this script. -""" - -import io -import pathlib -from typing import get_args - -import pydantic -import ruamel.yaml - -import rendercv.data as data -import rendercv.themes.options as theme_options - -repository_root = pathlib.Path(__file__).parent.parent -rendercv_path = repository_root / "rendercv" -image_assets_directory = pathlib.Path(__file__).parent / "assets" / "images" - - -class SampleEntries(pydantic.BaseModel): - education_entry: data.EducationEntry - experience_entry: data.ExperienceEntry - normal_entry: data.NormalEntry - publication_entry: data.PublicationEntry - one_line_entry: data.OneLineEntry - bullet_entry: data.BulletEntry - numbered_entry: data.NumberedEntry - reversed_numbered_entry: data.ReversedNumberedEntry - text_entry: str - - -def dictionary_to_yaml(dictionary: dict): - """Converts a dictionary to a YAML string. - - Args: - dictionary: The dictionary to be converted to YAML. - Returns: - The YAML string. - """ - yaml_object = ruamel.yaml.YAML() - yaml_object.width = 60 - yaml_object.indent(mapping=2, sequence=4, offset=2) - with io.StringIO() as string_stream: - yaml_object.dump(dictionary, string_stream) - return string_stream.getvalue() - - -def define_env(env): - # See https://mkdocs-macros-plugin.readthedocs.io/en/latest/macros/ - sample_entries = data.read_a_yaml_file( - repository_root / "docs" / "user_guide" / "sample_entries.yaml" - ) - # validate the parsed dictionary by creating an instance of SampleEntries: - SampleEntries(**sample_entries) - - entries_showcase = {} - for entry_name, entry in sample_entries.items(): - proper_entry_name = entry_name.replace("_", " ").title().replace(" ", "") - entries_showcase[proper_entry_name] = { - "yaml": dictionary_to_yaml(entry), - "figures": [ - { - "path": f"../assets/images/{theme}/{entry_name}.png", - "alt_text": f"{proper_entry_name} in {theme}", - "theme": theme, - } - for theme in data.available_themes - ], - } - - env.variables["sample_entries"] = entries_showcase - - # For theme templates reference docs - themes_path = rendercv_path / "themes" - theme_templates = {} - for theme in data.available_themes: - theme_templates[theme] = {} - for theme_file in themes_path.glob(f"{theme}/*.typ"): - theme_templates[theme][theme_file.stem] = theme_file.read_text() - - # Update ordering of theme templates, if there are more files, add them to the - # end - order = [ - "Preamble.j2", - "Header.j2", - "SectionBeginning.j2", - "SectionEnding.j2", - "TextEntry.j2", - "BulletEntry.j2", - "NumberedEntry.j2", - "ReversedNumberedEntry.j2", - "OneLineEntry.j2", - "EducationEntry.j2", - "ExperienceEntry.j2", - "NormalEntry.j2", - "PublicationEntry.j2", - ] - remaining_files = set(theme_templates[theme].keys()) - set(order) - order += list(remaining_files) - theme_templates[theme] = {key: theme_templates[theme][key] for key in order} - - if theme != "markdown": - theme_templates[theme] = { - f"{key}.typ": value for key, value in theme_templates[theme].items() - } - else: - theme_templates[theme] = { - f"{key}.md": value for key, value in theme_templates[theme].items() - } - - env.variables["theme_templates"] = theme_templates - - theme_components = {} - for theme_file in themes_path.glob("components/*.typ"): - theme_components[theme_file.stem] = theme_file.read_text() - theme_components = {f"{key}.typ": value for key, value in theme_components.items()} - - env.variables["theme_components"] = theme_components - - # Available themes strings (put available themes between ``) - themes = [f"`{theme}`" for theme in data.available_themes] - env.variables["available_themes"] = ", ".join(themes) - - # Available social networks strings (put available social networks between ``) - social_networks = [ - f"`{social_network}`" for social_network in data.available_social_networks - ] - env.variables["available_social_networks"] = ", ".join(social_networks) - - # Others: - env.variables["available_page_sizes"] = ", ".join( - [f"`{page_size}`" for page_size in get_args(theme_options.PageSize)] - ) - env.variables["available_font_families"] = ", ".join( - [f"`{font_family}`" for font_family in get_args(theme_options.FontFamily)] - ) - env.variables["available_text_alignments"] = ", ".join( - [ - f"`{text_alignment}`" - for text_alignment in get_args(theme_options.TextAlignment) - ] - ) - env.variables["available_header_alignments"] = ", ".join( - [ - f"`{header_alignment}`" - for header_alignment in get_args(theme_options.Alignment) - ] - ) - env.variables["available_section_title_types"] = ", ".join( - [ - f"`{section_title_type}`" - for section_title_type in get_args( - get_args(theme_options.SectionTitleType)[0] - ) - ] - ) - env.variables["available_bullets"] = ", ".join( - [f"`{bullet}`" for bullet in get_args(theme_options.BulletPoint)] - ) diff --git a/docs/index.md b/docs/index.md index d0a241cfc..6b973ef2f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,73 +1,169 @@ -# The engine of the [RenderCV App](https://rendercv.com) +# RenderCV + +
+*Resume builder for academics and engineers, deployed at [rendercv.com](https://rendercv.com)* [![test](https://github.com/rendercv/rendercv/actions/workflows/test.yaml/badge.svg?branch=main)](https://github.com/rendercv/rendercv/actions/workflows/test.yaml) [![coverage](https://coverage-badge.samuelcolvin.workers.dev/rendercv/rendercv.svg)](https://coverage-badge.samuelcolvin.workers.dev/redirect/rendercv/rendercv) [![docs](https://img.shields.io/badge/docs-mkdocs-rgb(0%2C79%2C144))](https://docs.rendercv.com) [![pypi-version](https://img.shields.io/pypi/v/rendercv?label=PyPI%20version&color=rgb(0%2C79%2C144))](https://pypi.python.org/pypi/rendercv) [![pypi-downloads](https://img.shields.io/pepy/dt/rendercv?label=PyPI%20downloads&color=rgb(0%2C%2079%2C%20144))](https://pypistats.org/packages/rendercv) +
+ +Write your CV or resume as YAML, then run RenderCV, -RenderCV engine is a Typst-based Python package with a command-line interface (CLI) that allows you to version-control your CV/resume as source code. It reads a CV written in a YAML file with Markdown syntax, converts it into a [Typst](https://typst.app) code, and generates a PDF. +```bash +rendercv render John_Doe_CV.yaml +``` -RenderCV engine's focus is to provide these three features: +and get a PDF with perfect typography. -- **Content-first approach:** Users should be able to focus on the content instead of worrying about the formatting. -- **A mechanism to version-control a CV's content and design separately:** The content and design of a CV are separate issues and they should be treated separately. -- **Robustness:** A PDF should be delivered if there aren't any errors. If errors exist, they should be clearly explained along with solutions. +With RenderCV, you can: +- Version-control your CV — it's just text. +- Focus on content — don't worry about the formatting. +- Get perfect typography — consistent alignment and spacing, handled for you. -It takes a YAML file that looks like this: +A YAML file like this: ```yaml cv: name: John Doe - location: Location - email: john.doe@example.com - phone: tel:+1-609-999-9995 + location: San Francisco, CA + email: john.doe@email.com + website: https://rendercv.com/ social_networks: - network: LinkedIn - username: john.doe + username: rendercv - network: GitHub - username: john.doe + username: rendercv sections: - welcome_to_RenderCV!: - - '[RenderCV](https://rendercv.com) is a Typst-based CV - framework designed for academics and engineers, with Markdown - syntax support.' - - Each section title is arbitrary. Each section contains - a list of entries, and there are 7 different entry types - to choose from. + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + - See the [documentation](https://docs.rendercv.com) for more details. education: - - institution: Stanford University + - institution: Princeton University area: Computer Science degree: PhD - location: Stanford, CA, USA - start_date: 2023-09 - end_date: present + date: + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ + summary: highlights: - - Working on the optimization of autonomous vehicles - in urban environments + - "Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment" + - "Advisor: Prof. Sanjeev Arora" + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) ... ``` -Then, it produces one of these PDFs with its corresponding Typst file, Markdown file, HTML file, and images as PNGs. Click on the images below to preview PDF files. +becomes one of these PDFs. Click on the images to preview. + +| [![Classic Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/classic.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_ClassicTheme_CV.pdf) | [![Engineeringresumes Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/engineeringresumes.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_EngineeringresumesTheme_CV.pdf) | [![Sb2nov Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/sb2nov.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_Sb2novTheme_CV.pdf) | +| --- | --- | --- | +| [![Moderncv Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/moderncv.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_ModerncvTheme_CV.pdf) | [![Engineeringclassic Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/engineeringclassic.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_EngineeringclassicTheme_CV.pdf) | [![Harvard Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/harvard.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_HarvardTheme_CV.pdf) | +| [![Ink Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/ink.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_InkTheme_CV.pdf) | [![Opal Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/opal.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_OpalTheme_CV.pdf) | [![Ember Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/examples/ember.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_EmberTheme_CV.pdf) | + + +## JSON Schema + +RenderCV's JSON Schema lets you fill out the YAML interactively, with autocompletion and inline documentation. + +![JSON Schema of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/json_schema.gif) + + +## Extensive Design Options + +You have full control over every detail. + +```yaml +design: + theme: classic + page: + size: us-letter + top_margin: 0.7in + bottom_margin: 0.7in + left_margin: 0.7in + right_margin: 0.7in + show_footer: true + show_top_note: true + colors: + body: rgb(0, 0, 0) + name: rgb(0, 79, 144) + headline: rgb(0, 79, 144) + connections: rgb(0, 79, 144) + section_titles: rgb(0, 79, 144) + links: rgb(0, 79, 144) + footer: rgb(128, 128, 128) + top_note: rgb(128, 128, 128) + typography: + line_spacing: 0.6em + alignment: justified + date_and_location_column_alignment: right + font_family: Source Sans 3 + # ...and much more +``` + +![Design Options of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/design_options.gif) + +> [!TIP] +> Want to set up a live preview environment like the one shown above? See [how to set up VS Code for RenderCV](https://docs.rendercv.com/user_guide/how_to/set_up_vs_code_for_rendercv). -| [![Classic Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/classic.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_ClassicTheme_CV.pdf) | [![Sb2nov Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/sb2nov.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_Sb2novTheme_CV.pdf) | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| [![Moderncv Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/moderncv.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_ModerncvTheme_CV.pdf) | [![Engineeringresumes Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/engineeringresumes.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_EngineeringresumesTheme_CV.pdf) | -| [![Engineeringclassic Theme Example of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/engineeringclassic.png)](https://github.com/rendercv/rendercv/blob/main/examples/John_Doe_EngineeringclassicTheme_CV.pdf) | ![Custom themes can be added.](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/customtheme.png) | +## Strict Validation -RenderCV comes with a JSON Schema so that the YAML input file can be filled out interactively. +No surprises. If something's wrong, you'll know exactly what and where. If it's valid, you get a perfect PDF. -![JSON Schema of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/schema.gif) +![Strict Validation Feature of RenderCV](https://raw.githubusercontent.com/rendercv/rendercv/main/docs/assets/images/validation.gif) -## Getting Started -RenderCV engine is very easy to install (`pip install "rendercv[full]"`) and easy to use (`rendercv new "John Doe"`). Follow the [user guide](https://docs.rendercv.com/user_guide) to get started. +## Any Language -## Motivation +Fill out the locale field for your language. -We are developing a [purpose-built app](https://rendercv.com) for writing CVs and resumes that will be available on mobile and web. This Python project is the foundation of that app. Check out [our blog post](https://rendercv.com/introducing-rendercv/) to learn more about why one would use such an app. +```yaml +locale: + language: english + last_updated: Last updated in + month: month + months: months + year: year + years: years + present: present + month_abbreviations: + - Jan + - Feb + - Mar + ... +``` -## Contributing +## AI Agent Skill + +Let AI coding agents create and edit your CV. Install the RenderCV skill: + +```bash +npx skills add rendercv/rendercv-skill +``` + +Works with Claude Code, Claude Desktop, Cursor, Codex, Copilot, Windsurf, Gemini CLI, and [20+ other agents](https://docs.rendercv.com/user_guide/how_to/use_the_ai_agent_skill). + +## Get Started + +Install RenderCV (Requires Python 3.12+): + +``` +pip install "rendercv[full]" +``` + +Create a new CV yaml file: + +``` +rendercv new "John Doe" +``` + +Edit the YAML, then render: + +``` +rendercv render "John_Doe_CV.yaml" +``` -All contributions to RenderCV are welcome! To get started, please read [the developer guide](https://docs.rendercv.com/developer_guide). +For more details, see the [user guide](https://docs.rendercv.com/user_guide/). diff --git a/docs/llms.txt b/docs/llms.txt new file mode 100644 index 000000000..2e2817ec4 --- /dev/null +++ b/docs/llms.txt @@ -0,0 +1,805 @@ +--- +name: rendercv +description: >- + Create professional CVs and resumes with perfect typography using RenderCV + (v2.8). Users write content in YAML, and RenderCV produces + publication-quality PDFs via Typst typesetting. Full control over every visual + detail: colors, fonts, margins, spacing, section title styles, entry layouts, + and more. 6 built-in themes with unlimited + customization. Any language supported (22 built-in, or define your own). Outputs + PDF, PNG, HTML, and Markdown. Use when the user wants to create, edit, + customize, or render a CV or resume. +--- + +## Quick Start + +**Available themes:** `classic`, `harvard`, `engineeringresumes`, `engineeringclassic`, `sb2nov`, `moderncv` +**Available locales:** `english`, `arabic`, `danish`, `dutch`, `french`, `german`, `hebrew`, `hindi`, `hungarian`, `indonesian`, `italian`, `japanese`, `korean`, `mandarin_chinese`, `norwegian_bokmål`, `norwegian_nynorsk`, `persian`, `portuguese`, `russian`, `spanish`, `turkish`, `vietnamese` + +These are starting points — every aspect of the design and locale can be fully customized in the YAML file. + +```bash +# Install RenderCV +uv tool install "rendercv[full]" + +# Create a starter YAML file (you can specify theme and locale) +rendercv new "John Doe" +rendercv new "John Doe" --theme moderncv --locale german + +# Render to PDF (also generates Typst, Markdown, HTML, PNG by default) +rendercv render John_Doe_CV.yaml + +# Watch mode: auto-re-render whenever the YAML file changes +rendercv render John_Doe_CV.yaml --watch + +# Render only PNG (useful for previewing or checking page count) +rendercv render John_Doe_CV.yaml --dont-generate-pdf --dont-generate-html --dont-generate-markdown + +# Override fields from the CLI without editing the YAML +rendercv render cv.yaml --cv.name "Jane Doe" --design.theme "moderncv" +``` + +## YAML Structure + +A RenderCV input has four sections. Only `cv` is required — the others have sensible defaults. + +```yaml +cv: # Your content: name, contact info, and all sections +design: # Visual styling: theme, colors, fonts, margins, spacing, layouts +locale: # Language: month names, phrases, translations +settings: # Behavior: output paths, bold keywords, current date +``` + +**Single file vs. separate files:** All four sections can live in one YAML file, or each can be a separate file. Separate files are useful for reusing the same design/locale across multiple CVs: + +```bash +# Single self-contained file (all sections in one file) +rendercv render John_Doe_CV.yaml + +# Separate files: CV content + design + locale loaded independently +rendercv render cv.yaml --design design.yaml --locale-catalog locale.yaml --settings settings.yaml +``` + +When using separate files, each file contains only its section (e.g., `design.yaml` has `design:` as the top-level key). CLI-loaded files override values in the main YAML file. + +The YAML maps directly to Pydantic models. The complete type-safe schema is provided below so you can understand every field, its type, and its default value. + +## Pydantic Schema + +The YAML input is validated against these Pydantic models. + +### Top-Level Model + +```python +class RenderCVModel(BaseModelWithoutExtraKeys): + cv: Cv = pydantic.Field(default_factory=Cv, title='CV', description='The content of the CV.') + design: Design = pydantic.Field(default_factory=ClassicTheme, title='Design') + locale: Locale = pydantic.Field(default_factory=EnglishLocale, title='Locale Catalog') + settings: Settings = pydantic.Field(default_factory=Settings, title='RenderCV Settings', description='The settings of the RenderCV.') + +``` + +### CV Content (`cv`) + +The `cv.sections` field is a dictionary where keys are section titles (any string you want) and values are lists of entries. Each section contains entries of the same type. + +```python +class Cv(BaseModelWithoutExtraKeys): + name: str | None = pydantic.Field(default=None, examples=['John Doe', 'Jane Smith']) + headline: str | None = pydantic.Field(default=None, examples=['Software Engineer', 'Data Scientist', 'Product Manager']) + location: str | None = pydantic.Field(default=None, examples=['New York, NY', 'London, UK', 'Istanbul, Türkiye']) + email: pydantic.EmailStr | list[pydantic.EmailStr] | None = pydantic.Field(default=None, examples=['john.doe@example.com', ['john.doe.1@example.com', 'john.doe.2@example.com']]) + photo: ExistingPathRelativeToInput | pydantic.HttpUrl | None = pydantic.Field(default=None, union_mode='left_to_right', examples=['photo.jpg', 'images/profile.png', 'https://example.com/photo.jpg']) + phone: pydantic_phone_numbers.PhoneNumber | list[pydantic_phone_numbers.PhoneNumber] | None = pydantic.Field(default=None, examples=['+1-234-567-8900', ['+1-234-567-8900', '+44 20 1234 5678']]) + website: pydantic.HttpUrl | list[pydantic.HttpUrl] | None = pydantic.Field(default=None, examples=['https://johndoe.com', ['https://johndoe.com', 'https://www.janesmith.dev']]) + social_networks: list[SocialNetwork] | None = pydantic.Field(default=None) + custom_connections: list[CustomConnection] | None = pydantic.Field(default=None, examples=[[{'placeholder': 'Book a call', 'url': 'https://cal.com/johndoe', 'fontawesome_icon': 'calendar-days'}]]) + sections: dict[str, Section] | None = pydantic.Field(default=None, examples=[{'Experience': '...', 'Education': '...', 'Projects': '...', 'Skills': '...'}]) + +``` + +```python +type SocialNetworkName = Literal['LinkedIn', 'GitHub', 'GitLab', 'IMDB', 'Instagram', 'ORCID', 'Mastodon', 'StackOverflow', 'ResearchGate', 'YouTube', 'Google Scholar', 'Telegram', 'WhatsApp', 'Leetcode', 'X', 'Bluesky', 'Reddit'] + +available_social_networks = get_args(SocialNetworkName.__value__) + +class SocialNetwork(BaseModelWithoutExtraKeys): + network: SocialNetworkName = pydantic.Field() + username: str = pydantic.Field(examples=['john_doe', '@johndoe@mastodon.social', '12345/john-doe']) + +``` + +```python +class CustomConnection(BaseModelWithoutExtraKeys): + fontawesome_icon: str + placeholder: str + url: pydantic.HttpUrl | None + +``` + +### Entry Types + +`cv.sections` is a dictionary: keys are section titles (any string), values are lists of entries. Each section must use a **single** entry type — you cannot mix different entry types within the same section. The entry type is auto-detected from the fields present in each entry. + +**Shared fields** — these are available on entry types that support dates and complex fields (ExperienceEntry, EducationEntry, NormalEntry, PublicationEntry): + +| Field | Type | Default | Notes | +|---|---|---|---| +| `date` | `str \| int \| null` | `null` | Free-form: `"2020-09"`, `"Fall 2023"`, etc. Mutually exclusive with `start_date`/`end_date`. | +| `start_date` | `str \| int \| null` | `null` | Strict format: YYYY-MM-DD, YYYY-MM, or YYYY. | +| `end_date` | `str \| int \| "present" \| null` | `null` | Same formats as `start_date`, or `"present"`. Omitting defaults to `"present"` when `start_date` is set. | +| `location` | `str \| null` | `null` | | +| `summary` | `str \| null` | `null` | | +| `highlights` | `list[str] \| null` | `null` | Bullet points. | + +**9 entry types:** + +| Entry Type | Required Fields | Optional Fields | Typical Use | +|---|---|---|---| +| **ExperienceEntry** | `company`, `position` | all shared fields | Jobs, positions | +| **EducationEntry** | `institution`, `area` | `degree` + all shared fields | Degrees, schools | +| **PublicationEntry** | `title`, `authors` | `doi`, `url`, `journal`, `summary`, `date` | Papers, articles | +| **NormalEntry** | `name` | all shared fields | Projects, awards | +| **OneLineEntry** | `label`, `details` | — | Skills, languages | +| **BulletEntry** | `bullet` | — | Simple bullet points | +| **NumberedEntry** | `number` | — | Numbered list items | +| **ReversedNumberedEntry** | `reversed_number` | — | Reverse-numbered items (5, 4, 3...) | +| **TextEntry** | *(plain string)* | — | Free-form paragraphs | + +Example: + +```yaml +cv: + sections: + experience: # list of ExperienceEntry (detected by company + position) + - company: Google + position: Engineer + start_date: 2020-01 + highlights: + - Did something impactful + skills: # list of OneLineEntry (detected by label + details) + - label: Languages + details: Python, C++ + about_me: # list of TextEntry (plain strings) + - This is a free-form paragraph about me. +``` + +Entries also accept arbitrary extra keys (silently ignored during rendering). A typo in a field name will NOT cause an error. + +### Design (`design`) + +All built-in themes share the same structure — they only differ in default values. See the sample designs below for every available field and its default. Set `design.theme` to pick a theme, then override any field. + +### Locale (`locale`) + +Built-in locales: `english`, `arabic`, `danish`, `dutch`, `french`, `german`, `hebrew`, `hindi`, `hungarian`, `indonesian`, `italian`, `japanese`, `korean`, `mandarin_chinese`, `norwegian_bokmål`, `norwegian_nynorsk`, `persian`, `portuguese`, `russian`, `spanish`, `turkish`, `vietnamese` + +Set `locale.language` to a built-in locale name to use it. Override any field to customize translations. Set `language` to any string and provide all translations for a fully custom locale. + +### Settings (`settings`) + +Key fields: `bold_keywords` (list of strings to auto-bold), `current_date` (override today's date), `render_command.*` (output paths, generation flags). + +## Important Patterns + +### YAML quoting + +**ALWAYS quote string values that contain a colon (`:`).** This is the most common cause of invalid YAML. Highlights, titles, summaries, and any free-form text often contain colons: + +```yaml +# WRONG — colon breaks YAML parsing: +- title: Catalytic Mechanisms: A New Approach + highlights: + - Relevant coursework: Distributed Systems, ML + +# RIGHT — wrap in double quotes: +- title: "Catalytic Mechanisms: A New Approach" + highlights: + - "Relevant coursework: Distributed Systems, ML" +``` + +Rule: if a string value contains `:`, it MUST be quoted. When in doubt, quote it. + +### Bullet characters + +The `design.highlights.bullet` field only accepts these exact characters: `●`, `•`, `◦`, `-`, `◆`, `★`, `■`, `—`, `○`. Do not use en-dash (`–`), `>`, `*`, or any other character. When in doubt, omit `bullet` to use the theme default. + +### Phone numbers + +Phone numbers MUST be in international format with country code (E.164). Never invent a phone number — only include one if the user provides it. + +```yaml +# WRONG: +phone: "(555) 123-4567" +phone: "555-123-4567" + +# RIGHT: +phone: "+15551234567" +``` + +If the user provides a local number without country code, ask which country, or omit the phone field. + +### Text formatting + +All text fields support inline Markdown: `**bold**`, `*italic*`, `[link text](url)`. Block-level Markdown (headers, lists, blockquotes, code blocks) is not supported. Raw Typst commands and math (`$$f(x)$$`) also pass through. + +### Date handling + +- `date` and `start_date`/`end_date` are mutually exclusive. If `date` is provided, `start_date` and `end_date` are ignored. +- If only `start_date` is given, `end_date` defaults to `"present"`. +- `start_date`/`end_date` require strict formats: YYYY-MM-DD, YYYY-MM, or YYYY. +- `date` is flexible: accepts any string ("Fall 2023") in addition to date formats. + +### Section titles + +- `snake_case` keys auto-capitalize: `work_experience` → "Work Experience" +- Keys with spaces or uppercase are used as-is. + +### Publication authors + +Use `*Name*` (single asterisks, italic) to highlight the CV owner in author lists. + +### Nested highlights (sub-bullets) + +```yaml +highlights: + - Main bullet point + - Sub-bullet 1 + - Sub-bullet 2 +``` + +## CLI Reference + +### `rendercv new "Full Name"` + +Generate a starter YAML file. + +| Option | Short | What it does | +|---|---|---| +| `--theme THEME` | | Theme to use (default: `classic`) | +| `--locale LOCALE` | | Locale to use (default: `english`) | +| `--create-typst-templates` | | Also create editable Typst template files for full design control | + +### `rendercv render ` + +Generate PDF, Typst, Markdown, HTML, and PNG from a YAML file. + +| Option | Short | What it does | +|---|---|---| +| `--watch` | `-w` | Re-render automatically when the YAML file changes | +| `--quiet` | `-q` | Suppress all output messages | +| `--design FILE` | `-d` | Load design section from a separate YAML file | +| `--locale-catalog FILE` | `-lc` | Load locale section from a separate YAML file | +| `--settings FILE` | `-s` | Load settings section from a separate YAML file | +| `--output-folder DIR` | `-o` | Custom output directory | + +Per-format controls: `--{format}-path PATH` sets custom output path, `--dont-generate-{format}` skips generation. Formats: `pdf`, `typst`, `markdown`, `html`, `png`. + +**Override any YAML field from the CLI** using dot notation (overrides without editing the file): + +```bash +rendercv render CV.yaml --cv.name "Jane Doe" --design.theme "moderncv" +rendercv render CV.yaml --cv.sections.education.0.institution "MIT" +``` + +### `rendercv create-theme "theme-name"` + +Scaffold a custom theme directory with editable Typst templates for complete design control. + +## JSON Schema + +For YAML editor autocompletion and validation: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.8/schema.json +``` + +## Complete Example + +### Sample CV + +```yaml +cv: + name: John Doe + headline: + location: San Francisco, CA + email: john.doe@email.com + photo: + phone: + website: https://rendercv.com/ + social_networks: + - network: LinkedIn + username: rendercv + - network: GitHub + username: rendercv + custom_connections: + sections: + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with + professional typography. + - Each section title is arbitrary. + education: + - institution: Princeton University + area: Computer Science + degree: PhD + date: + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ + summary: + highlights: + - 'Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment' + - 'Advisor: Prof. Sanjeev Arora' + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) + - institution: Boğaziçi University + area: Computer Engineering + degree: BS + date: + start_date: 2014-09 + end_date: 2018-06 + location: Istanbul, Türkiye + summary: + highlights: + - 'GPA: 3.97/4.00, Valedictorian' + - Fulbright Scholarship recipient for Graduate Studies + experience: + - company: Nexus AI + position: Co-Founder & CTO + date: + start_date: 2023-06 + end_date: present + location: San Francisco, CA + summary: + highlights: + - Built foundation model infrastructure serving 2M+ monthly API requests + with 99.97% uptime + - Raised $18M Series A led by Sequoia Capital, with participation from + a16z and Founders Fund + - Scaled engineering team from 3 to 28 across ML research, platform, and + applied AI divisions + - Developed proprietary inference optimization reducing latency by 73% + compared to baseline + - company: NVIDIA Research + position: Research Intern + date: + start_date: 2022-05 + end_date: 2022-08 + location: Santa Clara, CA + summary: + highlights: + - Designed sparse attention mechanism reducing transformer memory + footprint by 4.2x + - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top + 5% of submissions) + projects: + - name: '[FlashInfer](https://github.com/)' + date: + start_date: 2023-01 + end_date: present + location: + summary: Open-source library for high-performance LLM inference kernels + highlights: + - Achieved 2.8x speedup over baseline attention implementations on A100 + GPUs + - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors + - name: '[NeuralPrune](https://github.com/)' + date: '2021' + start_date: + end_date: + location: + summary: Automated neural network pruning toolkit with differentiable + masks + highlights: + - Reduced model size by 90% with less than 1% accuracy degradation on + ImageNet + - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars + publications: + - title: 'Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter + Models' + authors: + - '*John Doe*' + - Sarah Williams + - David Park + summary: + doi: 10.1234/neurips.2023.1234 + url: + journal: NeurIPS 2023 + date: 2023-07 + - title: Neural Architecture Search via Differentiable Pruning + authors: + - James Liu + - '*John Doe*' + summary: + doi: 10.1234/neurips.2022.5678 + url: + journal: NeurIPS 2022, Spotlight + date: 2022-12 + selected_honors: + - bullet: MIT Technology Review 35 Under 35 Innovators (2024) + - bullet: Forbes 30 Under 30 in Enterprise Technology (2024) + skills: + - label: Languages + details: Python, C++, CUDA, Rust, Julia + - label: ML Frameworks + details: PyTorch, JAX, TensorFlow, Triton, ONNX + patents: + - number: Adaptive Quantization for Neural Network Inference on Edge Devices + (US Patent 11,234,567) + - number: Dynamic Sparsity Patterns for Efficient Transformer Attention (US + Patent 11,345,678) + invited_talks: + - reversed_number: Scaling Laws for Efficient Inference — Stanford HAI + Symposium (2024) + - reversed_number: Building AI Infrastructure for the Next Decade — + TechCrunch Disrupt (2024) + +``` + +### Sample Design (classic — complete reference) + +This shows every available design field with its default value. All themes share the same structure. + +```yaml +design: + theme: classic + page: + size: us-letter + top_margin: 0.7in + bottom_margin: 0.7in + left_margin: 0.7in + right_margin: 0.7in + show_footer: true + show_top_note: true + colors: + body: rgb(0, 0, 0) + name: rgb(0, 79, 144) + headline: rgb(0, 79, 144) + connections: rgb(0, 79, 144) + section_titles: rgb(0, 79, 144) + links: rgb(0, 79, 144) + footer: rgb(128, 128, 128) + top_note: rgb(128, 128, 128) + typography: + line_spacing: 0.6em + alignment: justified + date_and_location_column_alignment: right + font_family: + body: Source Sans 3 + name: Source Sans 3 + headline: Source Sans 3 + connections: Source Sans 3 + section_titles: Source Sans 3 + font_size: + body: 10pt + name: 30pt + headline: 10pt + connections: 10pt + section_titles: 1.4em + small_caps: + name: false + headline: false + connections: false + section_titles: false + bold: + name: true + headline: false + connections: false + section_titles: true + links: + underline: false + show_external_link_icon: false + header: + alignment: center + photo_width: 3.5cm + photo_position: left + photo_space_left: 0.4cm + photo_space_right: 0.4cm + space_below_name: 0.7cm + space_below_headline: 0.7cm + space_below_connections: 0.7cm + connections: + phone_number_format: national + hyperlink: true + show_icons: true + display_urls_instead_of_usernames: false + separator: '' + space_between_connections: 0.5cm + section_titles: + type: with_partial_line + line_thickness: 0.5pt + space_above: 0.5cm + space_below: 0.3cm + sections: + allow_page_break: true + space_between_regular_entries: 1.2em + space_between_text_based_entries: 0.3em + show_time_spans_in: + - experience + entries: + date_and_location_width: 4.15cm + side_space: 0.2cm + space_between_columns: 0.1cm + allow_page_break: false + short_second_row: true + degree_width: 1cm + summary: + space_above: 0cm + space_left: 0cm + highlights: + bullet: • + nested_bullet: • + space_left: 0.15cm + space_above: 0cm + space_between_items: 0cm + space_between_bullet_and_text: 0.5em + templates: + footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' + top_note: '*LAST_UPDATED CURRENT_DATE*' + single_date: MONTH_ABBREVIATION YEAR + date_range: START_DATE – END_DATE + time_span: HOW_MANY_YEARS YEARS HOW_MANY_MONTHS MONTHS + one_line_entry: + main_column: '**LABEL:** DETAILS' + education_entry: + main_column: |- + **INSTITUTION**, AREA + SUMMARY + HIGHLIGHTS + degree_column: '**DEGREE**' + date_and_location_column: |- + LOCATION + DATE + normal_entry: + main_column: |- + **NAME** + SUMMARY + HIGHLIGHTS + date_and_location_column: |- + LOCATION + DATE + experience_entry: + main_column: |- + **COMPANY**, POSITION + SUMMARY + HIGHLIGHTS + date_and_location_column: |- + LOCATION + DATE + publication_entry: + main_column: |- + **TITLE** + SUMMARY + AUTHORS + URL (JOURNAL) + date_and_location_column: DATE + +``` + +### Other Theme Overrides + +Other themes only override specific fields from the classic defaults above. To use a theme, set `design.theme` and optionally override any field. Each theme also customizes `design.templates` (entry layout patterns) — see the classic sample above for the full template structure. The override YAMLs below omit templates for brevity. + +#### harvard + +```yaml +# yaml-language-server: $schema=../../../../../../schema.json +design: + theme: harvard + page: + top_margin: 0.5in + bottom_margin: 0.5in + left_margin: 0.5in + right_margin: 0.5in + show_top_note: false + colors: + name: rgb(0,0,0) + headline: rgb(0,0,0) + connections: rgb(0,0,0) + section_titles: rgb(0,0,0) + links: rgb(0,0,0) + typography: + font_family: + body: XCharter + name: XCharter + headline: XCharter + connections: XCharter + section_titles: XCharter + font_size: + name: 25pt + connections: 9pt + section_titles: 1.3em + header: + space_below_name: 0.5cm + space_below_headline: 0.5cm + space_below_connections: 0.5cm + connections: + show_icons: false + separator: • + space_between_connections: 0.4cm + section_titles: + type: centered_with_centered_partial_line + space_below: 0.2cm + sections: + space_between_regular_entries: 1em + show_time_spans_in: [] + entries: + short_second_row: false +``` + +#### engineeringresumes + +```yaml +# yaml-language-server: $schema=../../../../../../schema.json +design: + theme: engineeringresumes + page: + show_footer: false + typography: + font_family: + body: XCharter + name: XCharter + headline: XCharter + connections: XCharter + section_titles: XCharter + font_size: + name: 25pt + section_titles: 1.2em + bold: + name: false + header: + connections: + separator: '|' + show_icons: false + display_urls_instead_of_usernames: true + colors: + name: rgb(0,0,0) + connections: rgb(0,0,0) + headline: rgb(0,0,0) + section_titles: rgb(0,0,0) + links: rgb(0,0,0) + links: + underline: true + show_external_link_icon: false + section_titles: + type: with_full_line + space_above: 0.5cm + space_below: 0.3cm + sections: + space_between_regular_entries: 0.42cm + space_between_text_based_entries: 0.15cm + show_time_spans_in: [] + entries: + short_second_row: false + summary: + space_above: 0.08cm + side_space: 0cm + highlights: + bullet: ● + nested_bullet: ● + space_left: 0cm + space_above: 0.08cm + space_between_items: 0.08cm + space_between_bullet_and_text: 0.3em +``` + +#### engineeringclassic + +```yaml +# yaml-language-server: $schema=../../../../../../schema.json +design: + theme: engineeringclassic + typography: + font_family: + body: Raleway + name: Raleway + headline: Raleway + connections: Raleway + section_titles: Raleway + bold: + name: false + section_titles: false + header: + alignment: left + links: + show_external_link_icon: false + section_titles: + type: with_full_line + sections: + show_time_spans_in: [] + entries: + short_second_row: false + summary: + space_above: 0.12cm + highlights: + space_left: 0cm + space_above: 0.12cm + space_between_items: 0.12cm +``` + +#### sb2nov + +```yaml +# yaml-language-server: $schema=../../../../../../schema.json +design: + theme: sb2nov + typography: + font_family: + body: New Computer Modern + name: New Computer Modern + headline: New Computer Modern + connections: New Computer Modern + section_titles: New Computer Modern + colors: + name: rgb(0,0,0) + connections: rgb(0,0,0) + section_titles: rgb(0,0,0) + headline: rgb(0,0,0) + links: rgb(0,0,0) + links: + underline: true + show_external_link_icon: false + section_titles: + type: with_full_line + sections: + show_time_spans_in: [] + header: + connections: + hyperlink: true + show_icons: false + display_urls_instead_of_usernames: true + separator: • + entries: + short_second_row: false + highlights: + bullet: ◦ + nested_bullet: ◦ +``` + +#### moderncv + +```yaml +# yaml-language-server: $schema=../../../../../../schema.json +design: + theme: moderncv + typography: + line_spacing: 0.6em + font_family: + body: Fontin + name: Fontin + headline: Fontin + connections: Fontin + section_titles: Fontin + font_size: + name: 25pt + section_titles: 1.4em + bold: + name: false + section_titles: false + header: + alignment: left + photo_width: 4.15cm + photo_space_left: 0cm + photo_space_right: 0.3cm + links: + underline: true + show_external_link_icon: false + section_titles: + type: moderncv + space_above: 0.55cm + space_below: 0.3cm + line_thickness: 0.15cm + sections: + show_time_spans_in: [] + entries: + short_second_row: false + side_space: 0cm + space_between_columns: 0.3cm + summary: + space_above: 0.1cm + highlights: + space_left: 0cm + space_above: 0.15cm + space_between_items: 0.1cm + space_between_bullet_and_text: 0.3em +``` + diff --git a/docs/overrides/.icons/custom/moon.svg b/docs/overrides/.icons/custom/moon.svg new file mode 100644 index 000000000..51eafa898 --- /dev/null +++ b/docs/overrides/.icons/custom/moon.svg @@ -0,0 +1 @@ + diff --git a/docs/overrides/.icons/custom/rendercv.svg b/docs/overrides/.icons/custom/rendercv.svg new file mode 100644 index 000000000..b650c5ad4 --- /dev/null +++ b/docs/overrides/.icons/custom/rendercv.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/docs/overrides/.icons/custom/sun.svg b/docs/overrides/.icons/custom/sun.svg new file mode 100644 index 000000000..1f4f9ba52 --- /dev/null +++ b/docs/overrides/.icons/custom/sun.svg @@ -0,0 +1 @@ + diff --git a/docs/overrides/partials/header.html b/docs/overrides/partials/header.html new file mode 100644 index 000000000..ec36020dc --- /dev/null +++ b/docs/overrides/partials/header.html @@ -0,0 +1,69 @@ +{#- + This file was automatically generated - do not edit +-#} +{% set class = "md-header" %} +{% if "navigation.tabs.sticky" in features %} + {% set class = class ~ " md-header--shadow md-header--lifted" %} +{% elif "navigation.tabs" not in features %} + {% set class = class ~ " md-header--shadow" %} +{% endif %} +
+ + {% if "navigation.tabs.sticky" in features %} + {% if "navigation.tabs" in features %} + {% include "partials/tabs.html" %} + {% endif %} + {% endif %} +
diff --git a/docs/overrides/partials/logo.html b/docs/overrides/partials/logo.html new file mode 100644 index 000000000..c1d9252f6 --- /dev/null +++ b/docs/overrides/partials/logo.html @@ -0,0 +1,3 @@ + + {% include ".icons/custom/rendercv.svg" %} + diff --git a/docs/overrides/partials/tabs-item.html b/docs/overrides/partials/tabs-item.html new file mode 100644 index 000000000..fc7a3eb1a --- /dev/null +++ b/docs/overrides/partials/tabs-item.html @@ -0,0 +1,36 @@ +{% macro render_content(nav_item, ref) %} + {% set ref = ref or nav_item %} + {% if nav_item == ref or "navigation.indexes" in features %} + {% if nav_item.is_index and nav_item.meta.icon %} + {% include ".icons/" ~ nav_item.meta.icon ~ ".svg" %} + {% endif %} + {% endif %} + {{ ref.title }} +{% endmacro %} +{% macro render(nav_item, ref) %} + {% set ref = ref or nav_item %} + {% set class = "md-tabs__item" %} + {% if ref.active %} + {% set class = class ~ " md-tabs__item--active" %} + {% endif %} + {% if nav_item.children %} + {% set first = nav_item.children | first %} + {% if first.children %} + {{ render(first, ref) }} + {% else %} +
  • + + {{ render_content(first, ref) }} + +
  • + {% endif %} + {% else %} + {% set url = nav_item.url | url %} + {% set is_external = url.startswith("http://") or url.startswith("https://") %} +
  • + + {{ render_content(nav_item) }} + +
  • + {% endif %} +{% endmacro %} diff --git a/docs/overrides/partials/toc-item.html b/docs/overrides/partials/toc-item.html index 0cc97fd6f..55ed51a1b 100644 --- a/docs/overrides/partials/toc-item.html +++ b/docs/overrides/partials/toc-item.html @@ -27,7 +27,7 @@ {{ toc_item.title }} - + {% if toc_item.children %} {% endif %} - \ No newline at end of file + diff --git a/docs/reference/api/functions.md b/docs/reference/api/functions.md deleted file mode 100644 index 48d0213ad..000000000 --- a/docs/reference/api/functions.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.api.functions` - -::: rendercv.api.functions diff --git a/docs/reference/api/index.md b/docs/reference/api/index.md deleted file mode 100644 index 7c6ca1fc9..000000000 --- a/docs/reference/api/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.api` - -::: rendercv.api diff --git a/docs/reference/cli/commands.md b/docs/reference/cli/commands.md deleted file mode 100644 index fcc0e8de5..000000000 --- a/docs/reference/cli/commands.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.cli.commands` - -::: rendercv.cli.commands diff --git a/docs/reference/cli/index.md b/docs/reference/cli/index.md deleted file mode 100644 index 96b10fde4..000000000 --- a/docs/reference/cli/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.cli` - -::: rendercv.cli diff --git a/docs/reference/cli/printer.md b/docs/reference/cli/printer.md deleted file mode 100644 index 2aa1af946..000000000 --- a/docs/reference/cli/printer.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.cli.printer` - -::: rendercv.cli.printer diff --git a/docs/reference/cli/utilities.md b/docs/reference/cli/utilities.md deleted file mode 100644 index 4b4d50a46..000000000 --- a/docs/reference/cli/utilities.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.cli.utilities` - -::: rendercv.cli.utilities diff --git a/docs/reference/data/generator.md b/docs/reference/data/generator.md deleted file mode 100644 index 774383546..000000000 --- a/docs/reference/data/generator.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data.generator` - -::: rendercv.data.generator diff --git a/docs/reference/data/index.md b/docs/reference/data/index.md deleted file mode 100644 index 166cf0482..000000000 --- a/docs/reference/data/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data` - -::: rendercv.data diff --git a/docs/reference/data/models/base.md b/docs/reference/data/models/base.md deleted file mode 100644 index cdd6565eb..000000000 --- a/docs/reference/data/models/base.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data.models.base` - -::: rendercv.data.models.base diff --git a/docs/reference/data/models/computers.md b/docs/reference/data/models/computers.md deleted file mode 100644 index 3d407563f..000000000 --- a/docs/reference/data/models/computers.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data.models.computers` - -::: rendercv.data.models.computers diff --git a/docs/reference/data/models/curriculum_vitae.md b/docs/reference/data/models/curriculum_vitae.md deleted file mode 100644 index 332cd554b..000000000 --- a/docs/reference/data/models/curriculum_vitae.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data.models.curriculum_vitae` - -::: rendercv.data.models.curriculum_vitae diff --git a/docs/reference/data/models/design.md b/docs/reference/data/models/design.md deleted file mode 100644 index 118157d61..000000000 --- a/docs/reference/data/models/design.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data.models.design` - -::: rendercv.data.models.design diff --git a/docs/reference/data/models/entry_types.md b/docs/reference/data/models/entry_types.md deleted file mode 100644 index 44e11462c..000000000 --- a/docs/reference/data/models/entry_types.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data.models.entry_types` - -::: rendercv.data.models.entry_types diff --git a/docs/reference/data/models/index.md b/docs/reference/data/models/index.md deleted file mode 100644 index 313fba01b..000000000 --- a/docs/reference/data/models/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data.models` - -::: rendercv.data.models diff --git a/docs/reference/data/models/locale.md b/docs/reference/data/models/locale.md deleted file mode 100644 index cdf43d31d..000000000 --- a/docs/reference/data/models/locale.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data.models.locale` - -::: rendercv.data.models.locale diff --git a/docs/reference/data/models/rendercv_data_model.md b/docs/reference/data/models/rendercv_data_model.md deleted file mode 100644 index 6edd1cc3f..000000000 --- a/docs/reference/data/models/rendercv_data_model.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data.models.rendercv_data_model` - -::: rendercv.data.models.rendercv_data_model diff --git a/docs/reference/data/models/rendercv_settings.md b/docs/reference/data/models/rendercv_settings.md deleted file mode 100644 index bbce49c50..000000000 --- a/docs/reference/data/models/rendercv_settings.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data.models.rendercv_settings` - -::: rendercv.data.models.rendercv_settings \ No newline at end of file diff --git a/docs/reference/data/reader.md b/docs/reference/data/reader.md deleted file mode 100644 index 5264bb389..000000000 --- a/docs/reference/data/reader.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.data.reader` - -::: rendercv.data.reader diff --git a/docs/reference/index.md b/docs/reference/index.md deleted file mode 100644 index a35c4c5a6..000000000 --- a/docs/reference/index.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -hide: - - toc ---- - -# API Reference - -RenderCV is a Typst-based Python package with a command-line interface (CLI) that allows -you to version-control your CV/resume as source code. - -In this section, you can find how RenderCV's components are structured and how they interact with each other. The flowchart below illustrates the general operations of RenderCV. - -```mermaid -flowchart TD - subgraph rendercv.data - A[YAML Input File] --parsing with ruamel.yaml package--> B(Python Dictionary) - B --validation with pydantic package--> C((Pydantic Object)) - end - subgraph rendercv.themes - C --> AA[(Jinja2 Templates)] - end - AA --> D - AA --> E - subgraph rendercv.renderer - E[Markdown File] --markdown package--> K[HTML FIle] - D[Typst File] --typst package--> L[PDF File] - D --typst package--> Z[PNG Files] - end -``` - -- [`api`](api/index.md) package contains the functions to create a clean and simple API for RenderCV. - - [`functions.py`](api/functions.md) module contains the basic functions that are used to interact with RenderCV. -- [`cli`](cli/index.md) package contains the command-line interface (CLI) related code for RenderCV. - - [`commands.py`](cli/commands.md) module contains the CLI commands. - - [`printer.py`](cli/printer.md) module contains the functions and classes that are used to print nice-looking messages to the terminal. - - [`utilities.py`](cli/utilities.md) module contains utility functions that are required by the CLI. -- [`data`](data/index.md) package contains classes and functions to parse and validate a YAML input file. - - [`models`](data/models/index.md) package contains the Pydantic data models, validators, and computed fields that are used in RenderCV. - - [`computers.py`](data/models/computers.md) module contains functions that compute some properties based on the input data. - - [`base.py`](data/models/base.md) module contains the base data model for the other data models. - - [`entry_types.py`](data/models/entry_types.md) module contains the data models of the available entry types in RenderCV. - - [`curriculum_vitae.py`](data/models/curriculum_vitae.md) module contains the data model of the `cv` field of the input file. - - [`design.py`](data/models/design.md) module contains the data model of the `design` field of the input file. - - [`locale.py`](data/models/locale.md) module contains the data model of the `locale` field of the input file. - - [`rendercv_data_model.py`](data/models/rendercv_data_model.md) module contains the `RenderCVDataModel` data model, which is the main data model that defines the whole input file structure. - - [`generator.py`](data/generator.md) module contains the functions for generating the JSON Schema of the input data format and a sample YAML input file. - - [`reader.py`](data/reader.md) module contains the functions that are used to read the input files. -- [`renderer`](renderer/index.md) package contains the necessary classes and functions for generating the output files from the `RenderCVDataModel` object. - - [`renderer.py`](renderer/renderer.md) module contains the necessary functions for rendering Typst, PDF, Markdown, HTML, and PNG files from the data model. - - [`templater.py`](renderer/templater.md) module contains the necessary classes and functions for templating the Typst and Markdown files from the data model. -- [`themes`](themes/index.md) package contains the built-in themes of RenderCV. - - [`options.py`](themes/options.md) module contains the standard data models for built-in Typst themes' design options - - [`classic`](themes/classic.md) package contains the `classic` theme templates and data models for its design options. - - [`engineeringresumes`](themes/engineeringresumes.md) package contains the `engineeringresumes` theme templates and data models for its design options. - - [`sb2nov`](themes/sb2nov.md) package contains the `sb2nov` theme templates and data models for its design options. - - [`moderncv`](themes/moderncv.md) package contains the `moderncv` theme templates and data models for its design options. - - [`engineeringclassic`](themes/engineeringclassic.md) package contains the `engineeringclassic` theme templates and data models for its design options. diff --git a/docs/reference/renderer/index.md b/docs/reference/renderer/index.md deleted file mode 100644 index 51ca22d4a..000000000 --- a/docs/reference/renderer/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.renderer` - -::: rendercv.renderer diff --git a/docs/reference/renderer/renderer.md b/docs/reference/renderer/renderer.md deleted file mode 100644 index 07beb1940..000000000 --- a/docs/reference/renderer/renderer.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.renderer.renderer` - -::: rendercv.renderer.renderer diff --git a/docs/reference/renderer/templater.md b/docs/reference/renderer/templater.md deleted file mode 100644 index 0a249fa74..000000000 --- a/docs/reference/renderer/templater.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.renderer.templater` - -::: rendercv.renderer.templater diff --git a/docs/reference/themes/classic.md b/docs/reference/themes/classic.md deleted file mode 100644 index b0d174ebb..000000000 --- a/docs/reference/themes/classic.md +++ /dev/null @@ -1,14 +0,0 @@ -# `rendercv.themes.classic` - -::: rendercv.themes.classic - -## Jinja Templates - -{% for template_name, template in theme_templates["classic"].items() %} -### {{ template_name }} - -```typst -{{ template }} -``` - -{% endfor %} \ No newline at end of file diff --git a/docs/reference/themes/components.md b/docs/reference/themes/components.md deleted file mode 100644 index b42771ffc..000000000 --- a/docs/reference/themes/components.md +++ /dev/null @@ -1,13 +0,0 @@ -# `rendercv.themes.components` - -## Jinja Templates - -{% for template_name, template in theme_components.items() %} - -### {{ template_name }} - -```typst -{{ template }} -``` - -{% endfor %} \ No newline at end of file diff --git a/docs/reference/themes/engineeringclassic.md b/docs/reference/themes/engineeringclassic.md deleted file mode 100644 index 77987bd88..000000000 --- a/docs/reference/themes/engineeringclassic.md +++ /dev/null @@ -1,15 +0,0 @@ -# `rendercv.themes.engineeringclassic` - -::: rendercv.themes.engineeringclassic - -## Jinja Templates - -{% for template_name, template in theme_templates["engineeringclassic"].items() %} - -### {{ template_name }} - -```typst -{{ template }} -``` - -{% endfor %} \ No newline at end of file diff --git a/docs/reference/themes/engineeringresumes.md b/docs/reference/themes/engineeringresumes.md deleted file mode 100644 index bf68aad75..000000000 --- a/docs/reference/themes/engineeringresumes.md +++ /dev/null @@ -1,14 +0,0 @@ -# `rendercv.themes.engineeringresumes` - -::: rendercv.themes.engineeringresumes - -## Jinja Templates - -{% for template_name, template in theme_templates["engineeringresumes"].items() %} -### {{ template_name }} - -```typst -{{ template }} -``` - -{% endfor %} \ No newline at end of file diff --git a/docs/reference/themes/index.md b/docs/reference/themes/index.md deleted file mode 100644 index a8e52c3e6..000000000 --- a/docs/reference/themes/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.themes` - -::: rendercv.themes diff --git a/docs/reference/themes/moderncv.md b/docs/reference/themes/moderncv.md deleted file mode 100644 index 4af21a999..000000000 --- a/docs/reference/themes/moderncv.md +++ /dev/null @@ -1,14 +0,0 @@ -# `rendercv.themes.moderncv` - -::: rendercv.themes.moderncv - -## Jinja Templates - -{% for template_name, template in theme_templates["moderncv"].items() %} -### {{ template_name }} - -```typst -{{ template }} -``` - -{% endfor %} \ No newline at end of file diff --git a/docs/reference/themes/options.md b/docs/reference/themes/options.md deleted file mode 100644 index 17947739e..000000000 --- a/docs/reference/themes/options.md +++ /dev/null @@ -1,3 +0,0 @@ -# `rendercv.themes.options` - -::: rendercv.themes.options diff --git a/docs/reference/themes/sb2nov.md b/docs/reference/themes/sb2nov.md deleted file mode 100644 index 8d07dd63c..000000000 --- a/docs/reference/themes/sb2nov.md +++ /dev/null @@ -1,14 +0,0 @@ -# `rendercv.themes.sb2nov` - -::: rendercv.themes.sb2nov - -## Jinja Templates - -{% for template_name, template in theme_templates["sb2nov"].items() %} -### {{ template_name }} - -```typst -{{ template }} -``` - -{% endfor %} \ No newline at end of file diff --git a/docs/user_guide/cli.md b/docs/user_guide/cli.md deleted file mode 100644 index 98907bd1b..000000000 --- a/docs/user_guide/cli.md +++ /dev/null @@ -1,201 +0,0 @@ -# Command Line Interface (CLI) - -This page lists the available commands and options of the RenderCV CLI. - -## `rendercv` command - -- `#!bash --version` or `#!bash -v` - - Shows the version of RenderCV. - - ```bash - rendercv --version - ``` - -- `#!bash --help` or `#!bash -h` - - Shows the help message. - - ```bash - rendercv --help - ``` - -## `rendercv new` command - -- `#!bash --theme "THEME_NAME"` - - Generates files for a specific built-in theme, instead of the default `classic` theme. Currently, the available themes are: {{available_themes}}. - - ```bash - rendercv new "Full Name" --theme "THEME_NAME" - ``` - -- `#!bash --dont-create-theme-source-files` or `#!bash -notypst` - - Prevents the creation of the theme source files. By default, the theme source files are created. - - ```bash - rendercv new "Full Name" --dont-create-theme-source-files - ``` - -- `#!bash --dont-create-markdown-source-files` or `#!bash -nomd` - - Prevents the creation of the Markdown source files. By default, the Markdown source files are created. - - ```bash - rendercv new "Full Name" --dont-create-markdown-source-files - ``` - -- `#!bash --help` or `#!bash -h` - - Shows the help message. - - ```bash - rendercv new --help - ``` - - -## `rendercv render` command - -- `#!bash --watch` or `#!bash -w` - - Watches the input YAML file for changes and automatically renders if there is any change. - - ```bash - rendercv render "Full_Name_CV.yaml" --watch - ``` - -- `#!bash --output-folder-name "OUTPUT_FOLDER_NAME"` or `#!bash -o "OUTPUT_FOLDER_NAME"` - - Generates the output files in a folder with the given name. By default, the output folder name is `rendercv_output`. The output folder will be created in the current working directory. - - ```bash - rendercv render "Full_Name_CV.yaml" --output-folder-name "OUTPUT_FOLDER_NAME" - ``` - -- `#!bash --typst-path "PATH"` or `#!bash -typst "PATH"` - - Copies the generated Typst source code from the output folder and pastes it to the specified path. - - ```bash - rendercv render "Full_Name_CV.yaml" --typst-path "PATH" - ``` - -- `#!bash --pdf-path "PATH"` or `#!bash -pdf "PATH"` - - Copies the generated PDF file from the output folder and pastes it to the specified path. - - ```bash - rendercv render "Full_Name_CV.yaml" --pdf-path "PATH" - ``` - -- `#!bash --markdown-path "PATH"` or `#!bash -md "PATH"` - - Copies the generated Markdown file from the output folder and pastes it to the specified path. - - ```bash - rendercv render "Full_Name_CV.yaml" --markdown-path "PATH" - ``` - -- `#!bash --html-path "PATH"` or `#!bash -html "PATH"` - - Copies the generated HTML file from the output folder and pastes it to the specified path. - - ```bash - rendercv render "Full_Name_CV.yaml" --html-path "PATH" - ``` - -- `#!bash --png-path "PATH"` or `#!bash -png "PATH"` - - Copies the generated PNG files from the output folder and pastes them to the specified path. - - ```bash - rendercv render "Full_Name_CV.yaml" --png-path "PATH" - ``` - -- `#!bash --dont-generate-markdown` or `#!bash -nomd` - - Prevents the generation of the Markdown file. - - ```bash - rendercv render "Full_Name_CV.yaml" --dont-generate-markdown - ``` - -- `#!bash --dont-generate-html` or `#!bash -nohtml` - - Prevents the generation of the HTML file. - - ```bash - rendercv render "Full_Name_CV.yaml" --dont-generate-html - ``` - -- `#!bash --dont-generate-png` or `#!bash -nopng` - - Prevents the generation of the PNG files. - - ```bash - rendercv render "Full_Name_CV.yaml" --dont-generate-png - ``` -- `#!bash --design design.yaml` - - Uses the given design file for the `design` field of the input YAML file. - - ```bash - rendercv render "Full_Name_CV.yaml" --design "design.yaml" - ``` - -- `#!bash --locale-catalog locale.yaml` - - Uses the given locale catalog file for the `locale` field of the input YAML file. - - ```bash - rendercv render "Full_Name_CV.yaml" --locale-catalog "locale.yaml" - ``` - -- `#!bash --rendercv-settings rendercv_settings.yaml` - - Uses the given RenderCV settings file for the `rendercv_settings` field of the input YAML file. - - ```bash - rendercv render "Full_Name_CV.yaml" --rendercv-settings "rendercv_settings.yaml" - ``` - -- `#!bash --ANY.LOCATION.IN.THE.YAML.FILE "VALUE"` - - Overrides the value of `ANY.LOCATION.IN.THE.YAML.FILE` with `VALUE`. This option can be used to avoid storing sensitive information in the YAML file. Sensitive information, like phone numbers, can be passed as a command-line argument with environment variables. This method is also beneficial for creating multiple CVs using the same YAML file by changing only a few values. Here are a few examples: - - ```bash - rendercv render "Full_Name_CV.yaml" --cv.phone "+905555555555" - ``` - - ```bash - rendercv render "Full_Name_CV.yaml" --cv.sections.education.1.institution "Your University" - ``` - - Multiple `#!bash --ANY.LOCATION.IN.THE.YAML.FILE "VALUE"` options can be used in the same command. - -- `#!bash --help` or `#!bash -h` - - Shows the help message. - - ```bash - rendercv render --help - ``` - -## `rendercv create-theme` command - -- `#!bash --based-on "THEME_NAME"` - - Generates a custom theme based on the specified built-in theme, instead of the default `classic` theme. Currently, the available themes are: {{available_themes}}. - - ```bash - rendercv create-theme "mycustomtheme" --based-on "THEME_NAME" - ``` - -- `#!bash --help` or `#!bash -h` - - Shows the help message. - - ```bash - rendercv create-theme --help - ``` diff --git a/docs/user_guide/cli_reference.md b/docs/user_guide/cli_reference.md new file mode 100644 index 000000000..f08bb4ed3 --- /dev/null +++ b/docs/user_guide/cli_reference.md @@ -0,0 +1,158 @@ +--- +toc_depth: 1 +--- + +# CLI Reference + +RenderCV provides a command-line interface with three main commands: + +- **`rendercv new`** - Generate a sample CV to get started +- **`rendercv render`** - Generate PDF, Markdown, HTML, and PNG from your YAML input +- **`rendercv create-theme`** - Create a custom theme with editable templates + +!!! tip "New to command line?" + Commands are typed in your terminal/command prompt. Options starting with `--` modify behavior: + + ```bash + rendercv new "John Doe" --theme moderncv + ``` + + **You can combine multiple options** in a single command: + + ```bash + rendercv render CV.yaml --watch --dont-generate-html --dont-generate-png + ``` + + This renders your CV with auto-reload enabled, skipping HTML and PNG generation. + +## `rendercv` + +Check your installed version: + +```bash +rendercv --version +``` + +Get help anytime: + +```bash +rendercv --help +``` + +## `rendercv new` + +Generate a sample CV file to start editing. + +**Basic usage:** + +```bash +rendercv new "John Doe" +``` + +This creates `John_Doe_CV.yaml` in your current folder. + +**Choose a different theme:** + +```bash +rendercv new "John Doe" --theme moderncv +``` + +Available themes: << available_themes >> + +**Use a different language:** + +```bash +rendercv new "John Doe" --locale french +``` + +Available locales: << available_locales >> + +**For advanced users - generate editable templates:** + +```bash +rendercv new "John Doe" --create-typst-templates +``` + +This creates template files you can customize for complete design control. See [Override Default Templates](how_to/override_default_templates.md) for details. + +## `rendercv render` + +Generate your CV outputs (PDF, Markdown, HTML, PNG) from a YAML file. + +**Basic usage:** + +```bash +rendercv render John_Doe_CV.yaml +``` + +This creates a `rendercv_output` folder with all formats. + +### Common Scenarios + +**Auto-reload while editing:** + +```bash +rendercv render John_Doe_CV.yaml --watch +``` + +The CV regenerates automatically whenever you save changes. Great for live preview! + +**Only generate PDF:** + +```bash +rendercv render John_Doe_CV.yaml --dont-generate-markdown --dont-generate-html --dont-generate-png +``` + +Or use the short form: + +```bash +rendercv render John_Doe_CV.yaml -nomd -nohtml -nopng +``` + +**Custom output location:** + +```bash +rendercv render John_Doe_CV.yaml --pdf-path ~/Desktop/MyCV.pdf +``` + +### All Options + +| Option | Short | What it does | +| -------------------------- | --------- | -------------------------------- | +| `--watch` | `-w` | Re-render when file changes | +| `--quiet` | `-q` | Hide all messages | +| `--design FILE` | `-d` | Load design from separate file | +| `--locale-catalog FILE` | `-lc` | Load locale from separate file | +| `--settings FILE` | `-s` | Load settings from separate file | +| `--pdf-path PATH` | `-pdf` | Custom PDF location | +| `--typst-path PATH` | `-typ` | Custom Typst location | +| `--markdown-path PATH` | `-md` | Custom Markdown location | +| `--html-path PATH` | `-html` | Custom HTML location | +| `--png-path PATH` | `-png` | Custom PNG location | +| `--dont-generate-pdf` | `-nopdf` | Skip PDF generation | +| `--dont-generate-typst` | `-notyp` | Skip Typst generation | +| `--dont-generate-markdown` | `-nomd` | Skip Markdown generation | +| `--dont-generate-html` | `-nohtml` | Skip HTML generation | +| `--dont-generate-png` | `-nopng` | Skip PNG generation | + +**Override any YAML value:** + +Use dot notation to change specific fields. This overrides values in the YAML without editing the file. + +```bash +rendercv render CV.yaml --cv.phone "+1-555-555-5555" +rendercv render CV.yaml --cv.sections.education.0.institution "MIT" +rendercv render CV.yaml --design.theme "moderncv" +``` + +## `rendercv create-theme` + +Create your own theme with full control over the design. + +**Basic usage:** + +```bash +rendercv create-theme "mytheme" +``` + +This creates a `mytheme/` folder with template files you can edit. See [Override Default Templates](how_to/override_default_templates.md) for details. diff --git a/docs/user_guide/faq.md b/docs/user_guide/faq.md deleted file mode 100644 index 11be0ff0f..000000000 --- a/docs/user_guide/faq.md +++ /dev/null @@ -1,121 +0,0 @@ -# Frequently Asked Questions (FAQ) - -## How to use it with JSON Resume? - -You can use [jsonresume-to-rendercv](https://github.com/guruor/jsonresume-to-rendercv) to convert your JSON Resume file to a RenderCV input file. - -## How to use it with Docker? - -RenderCV's Docker image is available [on Docker Hub](https://hub.docker.com/r/rendercv/rendercv). - -If you have Docker installed, you can use RenderCV without installing anything else. Run the command below to open a Docker container with RenderCV installed. - -```bash -docker run -it -v ./rendercv:/rendercv docker.io/rendercv/rendercv:latest -``` - -Then, you can use RenderCV CLI as if it were installed on your machine. The files will be saved in the `rendercv` directory. - -## How to create a custom theme? - -RenderCV is a general Typst-based CV framework. It allows you to use any Typst code to generate your CVs. To begin developing a custom theme, run the command below. - -```bash -rendercv create-theme "mycustomtheme" -``` - -This command will create a directory called `mycustomtheme`, which contains the following files: - -``` { .sh .no-copy } -├── mycustomtheme -│ ├── __init__.py -│ ├── Preamble.j2.typ -│ ├── Header.j2.typ -│ ├── EducationEntry.j2.typ -│ ├── ExperienceEntry.j2.typ -│ ├── NormalEntry.j2.typ -│ ├── OneLineEntry.j2.typ -│ ├── PublicationEntry.j2.typ -│ ├── TextEntry.j2.typ -│ ├── SectionBeginning.j2.typ -│ └── SectionEnding.j2.typ -└── Your_Full_Name_CV.yaml -``` - -The files are copied from the `classic` theme. You can update the contents of these files to create your custom theme. - -To use your custom theme, update the `design.theme` field in the YAML input file as shown below. - -```yaml -cv: - ... - -design: - theme: mycustomtheme -``` - -Then, run the `render` command to render your CV with `mycustomtheme`. - -!!! note - Since JSON Schema will not recognize the name of the custom theme, it may show a warning in your IDE. This warning can be ignored. - -Each of these `*.j2.typ` files is Typst code with some Python in it. These files allow RenderCV to create your CV out of the YAML input. - -The best way to understand how they work is to look at the templates of the built-in themes: - -- [templates of the `classic` theme](../reference/themes/classic.md#jinja-templates) - -For example, the content of `ExperienceEntry.j2.typ` for the `classic` theme is shown below: - -```typst -\cventry{ - ((* if design.show_only_years *)) - <> - ((* else *)) - <> - ((* endif *)) -}{ - <> -}{ - <> -}{ - <> -}{}{} -((* for item in entry.highlights *)) -\cvline{}{\small <>} -((* endfor *)) -``` - -The values between `<<` and `>>` are the names of Python variables, allowing you to write a Typst CV without writing any content. They will be replaced with the values found in the YAML input. The values between `((*` and `*))` are Python blocks, allowing you to use loops and conditional statements. - -The process of generating Typst files like this is called "templating," and it is achieved with a Python package called [Jinja](https://jinja.palletsprojects.com/en/3.1.x/). - -The `__init__.py` file found in the theme directory defines the design options of the custom theme. You can define your custom design options in this file. - -For example, an `__init__.py` file is shown below: - -```python -from typing import Literal - -import pydantic - -class YourcustomthemeThemeOptions(pydantic.BaseModel): - theme: Literal["yourcustomtheme"] - option1: str - option2: str - option3: int - option4: bool -``` - -RenderCV will then parse your custom design options from the YAML input. You can use these variables inside your `*.j2.typ` files as shown below: - -```typst -<> -<> -((* if design.option4 *)) - <> -((* endif *)) -``` - -!!! info - Refer [here](cli.md#rendercv-create-theme-command) for the complete list of CLI options available for the `create-theme` command. diff --git a/docs/user_guide/how_to/arbitrary_keys_in_entries.md b/docs/user_guide/how_to/arbitrary_keys_in_entries.md new file mode 100644 index 000000000..820dc14b5 --- /dev/null +++ b/docs/user_guide/how_to/arbitrary_keys_in_entries.md @@ -0,0 +1,30 @@ +# Arbitrary Keys in Entries + +The `design.templates` field controls how entry data is displayed. Add any custom field to your entries and reference it in templates using UPPERCASE placeholders. + +## How It Works + +Templates use **UPPERCASE PLACEHOLDERS** that map to entry keys. + +Given this entry: + +```yaml +company: Google +position: Software Engineer +tech_stack: Python, Go, Kubernetes +``` + +And this template: + +```yaml +design: + templates: + experience_entry: + main_column: |- + **COMPANY**, POSITION + *Tech stack:* TECH_STACK +``` + +RenderCV replaces `COMPANY` → "Google", `POSITION` → "Software Engineer", `TECH_STACK` → "Python, Go, Kubernetes". + +Any key you add to an entry becomes available as an uppercase placeholder. diff --git a/docs/user_guide/how_to/custom_fonts.md b/docs/user_guide/how_to/custom_fonts.md new file mode 100644 index 000000000..863953dc1 --- /dev/null +++ b/docs/user_guide/how_to/custom_fonts.md @@ -0,0 +1,32 @@ +# Custom Fonts + +RenderCV automatically discovers custom fonts placed in a `fonts` directory next to your YAML input file. + +## How to Use Custom Fonts + +1. Create a `fonts` directory in the same location as your YAML file: + + ``` + Your_Name_CV.yaml + fonts/ + CustomFont-Regular.ttf + CustomFont-Bold.ttf + AnotherFont.otf + ``` + +2. In your YAML file, specify the font family name in the design section: + + ```yaml + design: + typography: + font_family: CustomFont + ``` + +## Supported Font Formats + +- `.ttf` (TrueType Font) +- `.otf` (OpenType Font) + +## Font Family Names + +Use the font family name exactly as defined in the font file's metadata. For most fonts, this is the name you see when you install the font on your system. diff --git a/docs/user_guide/how_to/override_default_templates.md b/docs/user_guide/how_to/override_default_templates.md new file mode 100644 index 000000000..efe3c8cf8 --- /dev/null +++ b/docs/user_guide/how_to/override_default_templates.md @@ -0,0 +1,138 @@ +# Override Default Templates + +When design options don't provide enough control, you can override the default Typst templates to fully customize your CV's appearance. + +## When to Override Templates + +Use template overriding when you need to: + +- Change the fundamental layout structure +- Add custom Typst functions or packages +- Modify how entries are rendered beyond what `design.templates` allows +- Create completely custom designs not achievable through design options + +For simpler customizations, try these first: + +- [`design`](../yaml_input_structure/design.md) for colors, fonts, spacing +- [`design.templates`](../yaml_input_structure/design.md) for changing entry text layout + +## Two Methods + +### Method 1: Quick Template Customization + +Use this when you want to tweak an existing theme's templates without creating a full custom theme. + +1. Create templates alongside your CV: + + ```bash + rendercv new "Your Name" --create-typst-templates + ``` + + This creates: + ``` + Your_Name_CV.yaml + classic/ + Preamble.j2.typ + Header.j2.typ + SectionBeginning.j2.typ + entries/ + NormalEntry.j2.typ + ... + ``` + +2. Modify any template file in the `classic/` folder. + +3. Render as usual: + + ```bash + rendercv render Your_Name_CV.yaml + ``` + +RenderCV automatically uses your local templates instead of the built-in ones. + +### Method 2: Create a Custom Theme + +Use this when building a reusable theme with its own design options. + +1. Create a custom theme: + + ```bash + rendercv create-theme mytheme + ``` + + This creates: + ``` + mytheme/ + __init__.py + Preamble.j2.typ + Header.j2.typ + SectionBeginning.j2.typ + entries/ + NormalEntry.j2.typ + ... + ``` + +2. Modify template files and optionally add custom design options in `__init__.py`. + +3. Use your theme in the YAML file: + + ```yaml + design: + theme: mytheme + # Your custom design options work here + ``` + +4. Render: + + ```bash + rendercv render Your_Name_CV.yaml + ``` + +## Template Structure + +Templates use [Jinja2](https://jinja.palletsprojects.com/) syntax with Typst code: + +```typst +// Example: entries/NormalEntry.j2.typ +#regular-entry( + [ +{% for line in entry.main_column.splitlines() %} + {{ line }} +{% endfor %} + ], + [ +{% for line in entry.date_and_location_column.splitlines() %} + {{ line }} +{% endfor %} + ], +) +``` + +### Variables Available in Templates + +- `cv`: All CV data (name, sections, etc.) +- `design`: All design options +- `locale`: Locale strings (month names, translations) +- `entry`: Current entry data (in entry templates) + +Example accessing design options: + +```typst +// In Preamble.j2.typ +#show: rendercv.with( + page-size: "{{ design.page.size }}", + colors-body: {{ design.colors.body.as_rgb() }}, + typography-font-family-body: "{{ design.typography.font_family.body }}", + // ... +) +``` + +## Markdown Templates + +Both methods also support Markdown template customization with `--create-markdown-templates`. The process is identical to Typst templates. + +## Tips + +- Start by copying templates and making small changes +- Templates are Jinja2 + Typst, not pure Typst +- Delete template files you don't need to customize (RenderCV falls back to built-in versions) diff --git a/docs/user_guide/how_to/set_up_vs_code_for_rendercv.md b/docs/user_guide/how_to/set_up_vs_code_for_rendercv.md new file mode 100644 index 000000000..e605c1330 --- /dev/null +++ b/docs/user_guide/how_to/set_up_vs_code_for_rendercv.md @@ -0,0 +1,80 @@ +# Set Up VS Code for RenderCV + +Visual Studio Code can be configured to provide a live preview environment for writing your CV with RenderCV. This setup enables you to see your changes reflected in the PDF instantly as you type, making the CV editing process smooth and interactive. + +## Required Extensions + +Install these two VS Code extensions: + +1. [**YAML Extension** by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml): Provides YAML language support with autocompletion and validation. + +2. [**PDFMore** by wytheglobal](https://marketplace.visualstudio.com/items?itemName=wytheglobal.pdfmore): Allows you to view PDF files directly within VS Code. + +## Configure Auto-Save + +To enable automatic rendering as you type, you need to configure VS Code to auto-save your files. + +1. Open the VS Code settings JSON file: + + === "macOS" + + Press `Cmd+Shift+P` to open the Command Palette, then type: + + ``` + Preferences: Open User Settings (JSON) + ``` + + === "Windows/Linux" + + Press `Ctrl+Shift+P` to open the Command Palette, then type: + + ``` + Preferences: Open User Settings (JSON) + ``` + +2. Add the following lines to your `settings.json` file: + + ```json + { + "files.autoSave": "afterDelay", + "files.autoSaveDelay": 10 + } + ``` + +These settings will automatically save your YAML file 10 milliseconds after you stop typing. + +## Start Writing Your CV with Live Preview + +Once the extensions are installed and auto-save is configured, follow these steps to start the live preview: + +1. **Open your YAML input file** (e.g. `John_Doe_CV.yaml`) in VS Code + +2. **Run `rendercv render` with the watch mode**: + + ```bash + rendercv render --watch John_Doe_CV.yaml + ``` + + The `--watch` flag tells RenderCV to monitor the YAML file for changes and automatically re-render the PDF whenever the file is saved. + +3. **Arrange your workspace**: + + - Place your YAML file on the left side of the editor + - Open the generated PDF (from `rendercv_output/`) on the right side + +4. **Start editing**: As you make changes to the YAML file, they will be automatically saved, triggering RenderCV to regenerate the PDF. The PDF viewer will update to show your changes in real-time. + +![Live Preview Demonstration](../../assets/images/design_options.gif) + +!!! tip + You can split your editor vertically by right-clicking on the PDF file tab and selecting "Split Right" or using the keyboard shortcut `Cmd+\` (macOS) or `Ctrl+\` (Windows/Linux). + +## Troubleshooting + +If the live preview isn't working: + +- Make sure auto-save is enabled and the delay is set +- Verify that `rendercv render --watch` is running in the terminal without errors +- Try closing and reopening the PDF file in VS Code + +With this setup, you'll have a productive environment for creating and refining your CV with instant visual feedback. diff --git a/docs/user_guide/how_to/use_the_ai_agent_skill.md b/docs/user_guide/how_to/use_the_ai_agent_skill.md new file mode 100644 index 000000000..f23dbcb14 --- /dev/null +++ b/docs/user_guide/how_to/use_the_ai_agent_skill.md @@ -0,0 +1,76 @@ +# Use the AI Agent Skill + +RenderCV provides an AI agent skill that teaches AI coding assistants how to create, edit, and render CVs. Once installed, your agent gains full knowledge of RenderCV's YAML schema, CLI commands, themes, locales, and design options. + +## Supported Agents + +The skill works with any AI agent that supports the [skills standard](https://skills.sh), including Claude Code, Claude Desktop, Cursor, Codex, Copilot, Windsurf, and Gemini CLI. + +## Install the Skill + +=== "Vercel Skills CLI" + + ```bash + npx skills add rendercv/rendercv-skill + ``` + + You can also target a specific agent: + + ```bash + npx skills add rendercv/rendercv-skill -a claude-code + npx skills add rendercv/rendercv-skill -a cursor + npx skills add rendercv/rendercv-skill -a codex + ``` + +=== "OpenSkills" + + ```bash + npx openskills install rendercv/rendercv-skill + ``` + +=== "Claude Desktop" + + 1. Download [`rendercv_skill.zip`](../../assets/rendercv_skill.zip). + 2. In Claude Desktop, click **Customize** (top left), then select **Skills**. + 3. Click **"+"** and select **"Upload a skill"**. + 4. Upload the downloaded ZIP file. + + The skill will appear in your Skills list and Claude will automatically use it when working with your CV. See [Claude's official guide](https://support.claude.com/en/articles/12512180-use-skills-in-claude) for detailed screenshots. + +=== "Manual" + + Copy the content of [`skills/rendercv/SKILL.md`](https://github.com/rendercv/rendercv-skill/blob/main/skills/rendercv/SKILL.md) into your agent's skill directory. For example, for Claude Code: + + ```bash + git clone https://github.com/rendercv/rendercv-skill.git + cp -r rendercv-skill/skills/rendercv ~/.claude/skills/ + ``` + +## Auto-Generated and Evaluated + +The skill is auto-generated from RenderCV's source code. A [build script](https://github.com/rendercv/rendercv/blob/main/scripts/rendercv_skill/generate.py) parses the Pydantic models via AST, strips them down to schema-relevant fields, generates sample CVs and design configs, and renders everything into a single SKILL.md through a Jinja2 template. This keeps the skill always in sync with the latest RenderCV version. + +We maintain a [promptfoo eval suite](https://github.com/rendercv/rendercv/tree/main/scripts/rendercv_skill/evals) that validates the skill's quality. The evals cover CV generation (software engineers, academics, fresh graduates, non-English locales), design customization, CLI workflows, and parsing messy CV text into clean YAML. Each generated YAML is validated through RenderCV's own Pydantic pipeline, the same validation `rendercv render` uses, so schema violations are caught deterministically, not just with LLM-as-judge. + +## What the Skill Provides + +The skill gives your AI agent: + +- Full knowledge of RenderCV's YAML input structure +- All 6 built-in themes and their design options +- Complete locale and language support (20 built-in languages) +- CLI commands and their options +- Pydantic model schemas for precise field types and defaults +- A complete sample CV as a reference + +## Usage + +After installing the skill, simply ask your AI agent to work with your CV: + +- "Create a new CV for me using the classic theme" +- "Switch my CV to the engineeringresumes theme" +- "Add a new experience entry to my CV" +- "Change the font to Source Sans 3 and make the margins smaller" +- "Translate my CV to German" + +The agent will use its knowledge from the skill to produce correct YAML and run the right `rendercv` commands. diff --git a/docs/user_guide/index.md b/docs/user_guide/index.md index 7d09d1d2e..f8e9491be 100644 --- a/docs/user_guide/index.md +++ b/docs/user_guide/index.md @@ -1,79 +1,76 @@ -# User Guide - -This page provides everything you need to know about the usage of RenderCV. +# Get Started ## Installation -1. Install [Python](https://www.python.org/downloads/) (3.10 or newer). +1. Install [Python](https://www.python.org/downloads/) (3.12 or newer). 2. Run the command below to install RenderCV. -```bash -pip install "rendercv[full]" -``` - -## Getting started + === "pip" -To get started, navigate to the directory where you want to create your CV and run the command below to create the input files. + ``` + pip install "rendercv[full]" + ``` -```bash -rendercv new "Your Full Name" -``` -This command will create the following files: + === "pipx" -- A YAML input file called `Your_Name_CV.yaml`. + ``` + pipx install "rendercv[full]" + ``` - This file contains the content and design options of your CV. A detailed explanation of the structure of the YAML input file is provided [here](structure_of_the_yaml_input_file.md). + === "uv" -- A directory called `classic`. + ``` + uv tool install "rendercv[full]" + ``` - This directory contains the Typst templates of RenderCV's default built-in theme, `classic`. You can update its contents to tweak the appearance of the output PDF file. + === "Docker" -- A directory called `markdown`. + Docker image is available at [ghcr.io/rendercv/rendercv](https://github.com/rendercv/rendercv/pkgs/container/rendercv). - This directory contains the templates of RenderCV's default Markdown template. You can update its contents to tweak the Markdown and HTML output of the CV. + ```bash + docker run --rm -v "$PWD":/work -u $(id -u):$(id -g) -e HOME=/tmp -w /work ghcr.io/rendercv/rendercv new "Your Name" + ``` -!!! note "A note about `classic` and `markdown` directories" - It's optional to have the `classic` and `markdown` directories. If you don't have them, RenderCV will use the built-in theme and Markdown templates. +## Quick Start -!!! info - Refer to the [here](cli.md#rendercv-new-command) for the complete list of CLI options available for the `new` command. +1. Create a new CV YAML input file -Then, open the `Your_Name_CV.yaml` file in your favorite text editor and fill it with your information. See the [structure of the YAML input file](structure_of_the_yaml_input_file.md) for more information about the YAML input file. + ```bash + rendercv new "Your Name" + ``` -Finally, render the YAML input file to generate your CV. + This creates a YAML input file called `Your_Name_CV.yaml`. This file contains the content, design options, translations and settings for RenderCV. See [YAML Input Structure](yaml_input_structure/index.md) for the full reference. -```bash -rendercv render "Your_Name_CV.yaml" -``` + See the [CLI Reference](cli_reference.md#rendercv-new) for the complete list of options available for the `new` command. -This command will generate a directory called `rendercv_output`, which contains the following files: + !!! tip + To get started with another language or theme, you can use the `--locale` and `--theme` options: -- The CV in PDF format, `Your_Name_CV.pdf`. -- Typst source code of the PDF file, `Your_Name_CV.typ`. -- Images of each page of the PDF file in PNG format, `Your_Name_CV_1.png`, `Your_Name_CV_page_2.png`, etc. -- The CV in Markdown format, `Your_Name_CV.md`. -- The CV in HTML format, `Your_Name_CV.html`. You can open this file in a web browser and copy-paste the content to Grammarly for proofreading. + ```bash + rendercv new "Your Name" --locale "turkish" --theme "engineeringresumes" + ``` -To have RenderCV run automatically whenever the YAML input file is updated, use the `--watch` option. -```bash -rendercv render --watch "Your_Name_CV.yaml" -``` +2. Render the YAML input file with -!!! info - Refer to the [here](cli.md#rendercv-render-command) for the complete list of CLI options available for the `render` command. + ```bash + rendercv render "Your_Name_CV.yaml" + ``` -## Overriding built-in themes + This generates a `rendercv_output/` directory containing: -If the theme and Markdown templates are found in the directory, they will override the default built-in theme and Markdown templates. You don't need to provide all the files; you can just provide the ones you want to override. + - `John_Doe_CV.pdf`: Your CV as PDF + - `John_Doe_CV.typ`: [Typst](https://typst.app) source code of the PDF + - `John_Doe_CV_1.png`, `..._2.png`, ...: PNG images of each page of the PDF + - `John_Doe_CV.md`: Your CV as Markdown + - `John_Doe_CV.html`: Your CV as HTML (generated from the Markdown) -For example, `ExperienceEntry` of the `classic` theme can be modified as shown below. + See the [CLI Reference](cli_reference.md#rendercv-render) for the complete list of options available for the `render` command. -``` { .sh .no-copy } -├── classic -│ └── ExperienceEntry.j2.typ # (1)! -└── Your_Full_Name_CV.yaml -``` + !!! tip + To re-render automatically whenever you save changes, use the `--watch` option: -1. This file will override the built-in `ExperienceEntry.j2.typ` template of the `classic` theme. + ```bash + rendercv render --watch "Your_Name_CV.yaml" + ``` diff --git a/docs/user_guide/structure_of_the_yaml_input_file.md b/docs/user_guide/structure_of_the_yaml_input_file.md deleted file mode 100644 index 39688c011..000000000 --- a/docs/user_guide/structure_of_the_yaml_input_file.md +++ /dev/null @@ -1,483 +0,0 @@ -# Structure of the YAML Input File - -RenderCV's input file consists of four parts: `cv`, `design`, `locale` and `rendercv_settings`. - -```yaml title="Your_Name_CV.yaml" -cv: - ... - YOUR CONTENT - ... -design: - ... - YOUR DESIGN - ... -locale: - ... - TRANSLATIONS TO YOUR LANGUAGE - ... -rendercv_settings: - ... - RENDERCV SETTINGS - ... -``` - -- The `cv` field is mandatory. It contains the **content of the CV**. -- The `design` field is optional. It contains the **design options of the CV**. If you don't provide a `design` field, RenderCV will use the default design options with the `classic` theme. -- The `locale` field is optional. It contains all the strings that define the CV's language (like month names, etc.). If you don't provide a `locale` field, the default English strings will be used. -- The `rendercv_settings` field is optional. It contains the settings of RenderCV (output paths, keywords to make bold, etc.). If you don't provide a `rendercv_settings` field, the default settings will be used. - -!!! tip "Tip: JSON Schema" - To maximize your productivity while editing the input YAML file, set up RenderCV's JSON Schema in your IDE. It will validate your inputs on the fly and give auto-complete suggestions. - - === "Visual Studio Code" - - 1. Install [YAML language support](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) extension. - 2. Then the Schema will be automatically set up because the file ends with `_CV.yaml`. - 3. Press `Ctrl + Space` to see the auto-complete suggestions. - - === "Other" - - 4. Ensure your editor of choice has support for JSON Schema. - 5. Add the following line at the top of `Your_Name_CV.yaml`: - - ``` yaml - # yaml-language-server: $schema=https://github.com/rendercv/rendercv/blob/main/schema.json?raw=true - ``` - 6. Press `Ctrl + Space` to see the auto-complete suggestions. - -## "`cv`" field - -The `cv` field of the YAML input starts with generic information, as shown below. - -```yaml -cv: - name: John Doe - location: Your Location - email: youremail@yourdomain.com - phone: +905419999999 # (1)! - website: https://example.com/ - social_networks: - - network: LinkedIn # (2)! - username: yourusername - - network: GitHub - username: yourusername - ... -``` - -1. If you want to change the phone number formatting in the output, see the `locale` field's `phone_number_format` key. -2. The available social networks are: {{available_social_networks}}. - -None of the values above are required. You can omit any or all of them, and RenderCV will adapt to your input. These generic fields are used in the header of the CV. - -The main content of your CV is stored in a field called `sections`. - -```yaml hl_lines="12 13 14 15" -cv: - name: John Doe - location: Your Location - email: youremail@yourdomain.com - phone: +905419999999 - website: https://yourwebsite.com/ - social_networks: - - network: LinkedIn - username: yourusername - - network: GitHub - username: yourusername - sections: - ... - YOUR CONTENT - ... -``` - -### "`cv.sections`" field - -The `cv.sections` field is a dictionary where the keys are the section titles, and the values are lists. Each item of the list is an entry for that section. - -Here is an example: - -```yaml hl_lines="3 7" -cv: - sections: - this_is_a_section_title: # (1)! - - This is a TextEntry. # (2)! - - This is another TextEntry under the same section. - - This is another another TextEntry under the same section. - this_is_another_section_title: - - company: This time it's an ExperienceEntry. # (3)! - position: Your position - start_date: 2019-01-01 - end_date: 2020-01 - location: TX, USA - highlights: - - This is a highlight (a bullet point). - - This is another highlight. - - company: Another ExperienceEntry. - position: Your position - start_date: 2019-01-01 - end_date: 2020-01-10 - location: TX, USA - highlights: - - This is a highlight (a bullet point). - - This is another highlight. -``` - -1. The section titles can be anything you want. They are the keys of the `sections` dictionary. -2. Each section is a list of entries. This section has three `TextEntry`s. -3. There are seven different entry types in RenderCV. Any of them can be used in the sections. This section has two `ExperienceEntry`s. - -There are seven different entry types in RenderCV. Different types of entries cannot be mixed under the same section, so for each section, you can only use one type of entry. - -The available entry types are: [`EducationEntry`](#educationentry), [`ExperienceEntry`](#experienceentry), [`PublicationEntry`](#publicationentry), [`NormalEntry`](#normalentry), [`OneLineEntry`](#onelineentry), [`BulletEntry`](#bulletentry), and [`TextEntry`](#textentry). - -Each entry type is a different object (a dictionary). Below, you can find all the entry types along with their optional/mandatory fields and how they appear in each built-in theme. - -{% for entry_name, entry in sample_entries.items() %} -#### {{ entry_name }} - -{% if entry_name == "EducationEntry" %} - -**Mandatory Fields:** - -- `institution`: The name of the institution. -- `area`: The area of study. - -**Optional Fields:** - -- `degree`: The type of degree (e.g., BS, MS, PhD) -- `location`: The location -- `start_date`: The start date in `YYYY-MM-DD`, `YYYY-MM`, or `YYYY` format -- `end_date`: The end date in `YYYY-MM-DD`, `YYYY-MM`, or `YYYY` format or "present" -- `date`: The date as a custom string or in `YYYY-MM-DD`, `YYYY-MM`, or `YYYY` format. This will override `start_date` and `end_date`. -- `summary`: The summary -- `highlights`: The list of bullet points - -{% elif entry_name == "ExperienceEntry" %} - -**Mandatory Fields:** - -- `company`: The name of the company -- `position`: The position - -**Optional Fields:** - -- `location`: The location -- `start_date`: The start date in `YYYY-MM-DD`, `YYYY-MM`, or `YYYY` format -- `end_date`: The end date in `YYYY-MM-DD`, `YYYY-MM`, or `YYYY` format or "present" -- `date`: The date as a custom string or in `YYYY-MM-DD`, `YYYY-MM`, or `YYYY` format. This will override `start_date` and `end_date`. -- `summary`: The summary -- `highlights`: The list of bullet points - -{% elif entry_name == "PublicationEntry" %} - -**Mandatory Fields:** - -- `title`: The title of the publication -- `authors`: The authors of the publication - -**Optional Fields:** - -- `doi`: The DOI of the publication -- `url`: The URL of the publication -- `journal`: The journal of the publication -- `date`: The date as a custom string or in `YYYY-MM-DD`, `YYYY-MM`, or `YYYY` format - -{% elif entry_name == "NormalEntry" %} - - -**Mandatory Fields:** - -- `name`: The name of the entry - -**Optional Fields:** - -- `location`: The location -- `start_date`: The start date in `YYYY-MM-DD`, `YYYY-MM`, or `YYYY` format -- `end_date`: The end date in `YYYY-MM-DD`, `YYYY-MM`, or `YYYY` format or "present" -- `date`: The date as a custom string or in `YYYY-MM-DD`, `YYYY-MM`, or `YYYY` format. This will override `start_date` and `end_date`. -- `summary`: The summary -- `highlights`: The list of bullet points - -{% elif entry_name == "OneLineEntry" %} - -**Mandatory Fields:** - -- `label`: The label of the entry -- `details`: The details of the entry - -{% elif entry_name == "BulletEntry" %} - -**Mandatory Fields:** - -- `bullet`: The bullet point - -{% elif entry_name == "NumberedEntry" %} - -**Mandatory Fields:** - -- `number`: The content of the numbered entry - -{% elif entry_name == "ReversedNumberedEntry" %} - -The `ReversedNumberedEntry` displays entries in descending numerical order. - -**Mandatory Fields:** - -- `reversed_number`: The content of the reversed numbered entry - -{% elif entry_name == "TextEntry" %} - -**Mandatory Fields:** - -- The text itself - -{% endif %} - -```yaml -{{ entry["yaml"] }} -``` - {% for figure in entry["figures"] %} -=== "`{{ figure["theme"] }}` theme" - ![figure["alt_text"]]({{ figure["path"] }}) - {% endfor %} -{% endfor %} - -#### Markdown Syntax - -All the fields in the entries support Markdown syntax. - -You can make anything bold by surrounding it with `**`, italic with `*`, and links with `[]()`, as shown below. - -```yaml -company: "**This will be bold**, *this will be italic*, - and [this will be a link](https://example.com)." -... -``` - -### Using arbitrary keys - -RenderCV allows the usage of any number of extra keys in the entries. For instance, the following is an `ExperienceEntry` containing an additional key, `an_arbitrary_key`. - -```yaml hl_lines="6" -company: Some Company -location: TX, USA -position: Software Engineer -start_date: 2020-07 -end_date: '2021-08-12' -an_arbitrary_key: Developed an [IOS application](https://example.com). -highlights: - - Received more than **100,000 downloads**. - - Managed a team of **5** engineers. -``` - -By default, the `an_arbitrary_key` key will not affect the output as the default design options do not use it. However, you can use the `an_arbitrary_key` key in your own design options (see `design.entry_types` field). - -## "`design`" field - -The `design` field contains your theme selection and its options. Currently, the available themes are: {{available_themes}}. The only difference between the themes are the `design` options. Their Typst templates are the same. Any theme can be obtained by playing with the `design` options. Custom themes can also be created (see [here](faq.md#how-to-create-a-custom-theme)). - -```yaml -design: - theme: classic - ... -``` - -Use an IDE that supports JSON schema to avoid missing any available options for the theme (see [above](#structure-of-the-yaml-input-file)). - -An example `design` field for a `classic` theme is shown below: - -```yaml -design: - theme: classic # (1)! - page: - size: us-letter # (2)! - top_margin: 2cm - bottom_margin: 2cm - left_margin: 2cm - right_margin: 2cm - show_page_numbering: true - show_last_updated_date: true - colors: - text: black - name: '#004f90' - connections: '#004f90' - section_titles: '#004f90' - links: '#004f90' - last_updated_date_and_page_numbering: grey - text: - font_family: Source Sans 3 # (3)! - font_size: 10pt - leading: 0.6em - alignment: justified # (4)! - date_and_location_column_alignment: right # (5)! - links: - underline: false - use_external_link_icon: true - header: - name_font_family: Source Sans 3 - name_font_size: 30pt - name_bold: true - small_caps_for_name: false - photo_width: 3.5cm - vertical_space_between_name_and_connections: 0.7cm - vertical_space_between_connections_and_first_section: 0.7cm - horizontal_space_between_connections: 0.5cm - connections_font_family: Source Sans 3 - separator_between_connections: '' - use_icons_for_connections: true - alignment: center # (6)! - section_titles: - type: with-partial-line # (7)! - font_family: Source Sans 3 - font_size: 1.4em - bold: true - small_caps: false - line_thickness: 0.5pt - vertical_space_above: 0.5cm - vertical_space_below: 0.3cm - entries: - date_and_location_width: 4.15cm - left_and_right_margin: 0.2cm - horizontal_space_between_columns: 0.1cm - vertical_space_between_entries: 1.2em - allow_page_break_in_sections: true - allow_page_break_in_entries: true - short_second_row: false - show_time_spans_in: [] - highlights: - bullet: • # (8)! - top_margin: 0.25cm - left_margin: 0.4cm - vertical_space_between_highlights: 0.25cm - horizontal_space_between_bullet_and_highlight: 0.5em - summary_left_margin: 0cm - entry_types: - one_line_entry: - template: '**LABEL:** DETAILS' - education_entry: - main_column_first_row_template: '**INSTITUTION**, AREA' - degree_column_template: '**DEGREE**' - degree_column_width: 1cm - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: |- - LOCATION - DATE - normal_entry: - main_column_first_row_template: '**NAME**' - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: |- - LOCATION - DATE - experience_entry: - main_column_first_row_template: '**COMPANY**, POSITION' - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: |- - LOCATION - DATE - publication_entry: - main_column_first_row_template: '**TITLE**' - main_column_second_row_template: |- - AUTHORS - URL (JOURNAL) - main_column_second_row_without_journal_template: |- - AUTHORS - URL - main_column_second_row_without_url_template: |- - AUTHORS - JOURNAL - date_and_location_column_template: DATE -``` - -1. The `design.theme` field only changes the default values of all the other fields in the `design` field. Therefore, if you don't change any of the other fields, the output will be the same for all the themes. You can remove all the other fields and just keep the `design.theme` field to use the default values of that theme. - - The available themes are: {{available_themes}}. - -2. The available page sizes are: {{available_page_sizes}}. -3. The available font families are: {{available_font_families}}. -4. The available text alignments are: {{available_text_alignments}}. -5. The available date and location column alignments are: {{available_header_alignments}}. -6. The available header alignments are: {{available_header_alignments}}. -7. The available section title types are: {{available_section_title_types}}. -8. The available bullet types are: {{available_bullets}}. - -## "`locale`" field - -This field is what makes RenderCV a multilingual tool. RenderCV uses some English strings to render PDFs. For example, it takes the dates in ISO format (`2020-01-01`) and converts them into human-friendly strings (`"Jan 2020"`). However, you can override these strings for your own language or needs with the `locale` field. - -Here is an example: - -```yaml -locale: - language: en - phone_number_format: national # (1)! - page_numbering_template: NAME - Page PAGE_NUMBER of TOTAL_PAGES # (4)! - last_updated_date_template: Last updated in TODAY # (3)! - date_template: MONTH_ABBREVIATION YEAR # (2)! - month: month - months: months - year: year - years: years - present: present - to: – - abbreviations_for_months: - - Jan - - Feb - - Mar - - Apr - - May - - June - - July - - Aug - - Sept - - Oct - - Nov - - Dec - full_names_of_months: - - January - - February - - March - - April - - May - - June - - July - - August - - September - - October - - November - - December -``` - -1. The available phone number formats are: `national`, `international`, and `E164`. -2. The `MONTH_ABBREVIATION` and `YEAR` are placeholders. The available placeholders are: `FULL_MONTH_NAME`, `MONTH_ABBREVIATION`, `MONTH`, `MONTH_IN_TWO_DIGITS`, `YEAR`, and `YEAR_IN_TWO_DIGITS`. -3. The available placeholders are: `TODAY`, which prints the today's date with `locale.date_template`. -4. The available placeholders are: `NAME`, `PAGE_NUMBER`, `TOTAL_PAGES`, and `TODAY`. - -## "`rendercv_settings`" field - -The `rendercv_settings` field contains RenderCV settings. - -```yaml -rendercv_settings: - date: "2025-01-06" # (1)! - bold_keywords: - - Python # (2)! - render_command: - output_folder_name: rendercv_output - pdf_path: NAME_IN_SNAKE_CASE_CV.pdf # (3)! - typst_path: NAME_IN_LOWER_SNAKE_CASE_cv.typ - html_path: NAME_IN_KEBAB_CASE_CV.html - markdown_path: NAME.md - dont_generate_html: false - dont_generate_markdown: false - dont_generate_pdf: false - dont_generate_png: false -``` - -1. This field is used for time span calculations and last updated date text. -2. The words in the list will be bolded in the output automatically. -3. `NAME_IN_SNAKE_CASE` is a placeholder. The available placeholders are: `NAME_IN_SNAKE_CASE`, `NAME_IN_LOWER_SNAKE_CASE`, `NAME_IN_UPPER_SNAKE_CASE`, `NAME_IN_KEBAB_CASE`, `NAME_IN_LOWER_KEBAB_CASE`, `NAME_IN_UPPER_KEBAB_CASE`, `NAME`, `FULL_MONTH_NAME`, `MONTH_ABBREVIATION`, `MONTH`, `MONTH_IN_TWO_DIGITS`, `YEAR`, and `YEAR_IN_TWO_DIGITS`. diff --git a/docs/user_guide/yaml_input_structure/cv.md b/docs/user_guide/yaml_input_structure/cv.md new file mode 100644 index 000000000..8beb55219 --- /dev/null +++ b/docs/user_guide/yaml_input_structure/cv.md @@ -0,0 +1,252 @@ +# `cv` Field + +## Header Information + +The `cv` field begins with your personal information. All fields are optional. RenderCV adapts to whatever you provide. + +```yaml +cv: + name: John Doe + headline: Machine Learning Engineer + location: San Francisco, CA + email: john@example.com # (1)! + phone: "+14155551234" # (2)! + website: https://johndoe.dev # (3)! + photo: photo.jpg + social_networks: + - network: LinkedIn # (4)! + username: johndoe + - network: GitHub + username: johndoe + custom_connections: + - placeholder: Book a call # (5)! + url: https://cal.com/johndoe + fontawesome_icon: calendar-days +``` + +1. Multiple emails can be provided as a list. +2. Multiple phone numbers can be provided as a list. The display format (national, international, or E164) can be controlled with [`design.header.connections.phone_number_format`](design.md). +3. Multiple websites can be provided as a list. +4. Available social networks: << available_social_networks >> +5. Custom connections let you add any extra link (or plain text if `url` is omitted) with your own display text (`placeholder`) and a Font Awesome icon name (e.g., `calendar-days`, `envelope`). For the list of available icons, see [fontawesome.com/search](https://fontawesome.com/search). + +## Sections + +The `sections` field holds the main content of your CV. It's a dictionary where: + +- **Keys** are section titles (displayed as headings). Section titles can be anything. +- **Values** are lists of entries + +```yaml +cv: + name: John Doe + + sections: + summary: + - Software engineer with 10 years of experience in distributed systems. + + experience: + - company: Acme Corp + position: Senior Engineer + start_date: 2020-01 + end_date: present + highlights: + - Led migration to microservices architecture + - Reduced deployment time by 80% + + education: + - institution: MIT + area: Computer Science + degree: BS + start_date: 2012-09 + end_date: 2016-05 + + skills: + - label: Languages + details: Python, Go, Rust, TypeScript + - label: Infrastructure + details: Kubernetes, Terraform, AWS +``` + + +**Section names are just titles.** You can use any of the << entry_count >> entry types in any section. Choose what works best for your content. + +For example, `experience` section could use `NormalEntry`: + +```yaml +sections: + experience: + - name: Acme Corp — Senior Engineer + start_date: 2020-01 + end_date: present + highlights: + - Led migration to microservices architecture +``` + +Or `BulletEntry` for a minimal style: + +```yaml +sections: + experience: + - bullet: "**Acme Corp** — Senior Engineer (2020–present)" + - bullet: "**StartupXYZ** — Founding Engineer (2018–2020)" +``` + + +!!! warning "One entry type per section" + Each section must contain only one type of entry. For example, you cannot mix `ExperienceEntry` and `EducationEntry` in the same section. + +## Entry Types + +RenderCV provides << entry_count >> entry types: +{$ for entry_name in entry_names $} +<< loop.index >>. << entry_name >> +{$ endfor $} +each rendered differently on the PDF. + +{$ for entry_name, entry in sample_entries.items() $} +### << entry_name >> + +{$ if entry_name == "EducationEntry" $} +For academic credentials. + +| Field | Required | Description | +| ------------- | -------- | ---------------------------------------- | +| `institution` | Yes | School or university name | +| `area` | Yes | Field of study | +| `degree` | No | Degree type (BS, MS, PhD, etc.) | +| `date` | No | Custom date string (overrides start/end) | +| `start_date` | No | Start date | +| `end_date` | No | End date (or `present`) | +| `location` | No | Institution location | +| `summary` | No | Brief description | +| `highlights` | No | List of bullet points | + +{$ elif entry_name == "ExperienceEntry" $} +For work history and professional roles. + +| Field | Required | Description | +| ------------ | -------- | ---------------------------------------- | +| `company` | Yes | Employer name | +| `position` | Yes | Job title | +| `date` | No | Custom date string (overrides start/end) | +| `start_date` | No | Start date | +| `end_date` | No | End date (or `present`) | +| `location` | No | Office location | +| `summary` | No | Role description | +| `highlights` | No | List of accomplishments | + +{$ elif entry_name == "PublicationEntry" $} +For papers, articles, and other publications. + +| Field | Required | Description | +| --------- | -------- | ------------------------------------------------ | +| `title` | Yes | Publication title | +| `authors` | Yes | List of author names (use `*Name*` for emphasis) | +| `doi` | No | Digital Object Identifier | +| `url` | No | Link to the publication | +| `journal` | No | Journal, conference, or venue name | +| `date` | No | Publication date | + +{$ elif entry_name == "NormalEntry" $} +A flexible entry for projects, awards, certifications, or anything else. + +| Field | Required | Description | +| ------------ | -------- | ---------------------------------------- | +| `name` | Yes | Entry title | +| `date` | No | Custom date string (overrides start/end) | +| `start_date` | No | Start date | +| `end_date` | No | End date (or `present`) | +| `location` | No | Associated location | +| `summary` | No | Brief description | +| `highlights` | No | List of bullet points | + +{$ elif entry_name == "OneLineEntry" $} +For compact key-value pairs, ideal for skills or technical proficiencies. + +| Field | Required | Description | +| --------- | -------- | ------------------ | +| `label` | Yes | Category name | +| `details` | Yes | Associated details | + +{$ elif entry_name == "BulletEntry" $} +A single bullet point. Use for simple lists. + +| Field | Required | Description | +| -------- | -------- | --------------- | +| `bullet` | Yes | The bullet text | + +{$ elif entry_name == "NumberedEntry" $} +An automatically numbered entry. + +| Field | Required | Description | +| -------- | -------- | ----------------- | +| `number` | Yes | The entry content | + +{$ elif entry_name == "ReversedNumberedEntry" $} +A numbered entry that counts down (useful for publication lists where recent items come first). + +| Field | Required | Description | +| ----------------- | -------- | ----------------- | +| `reversed_number` | Yes | The entry content | + +{$ elif entry_name == "TextEntry" $} +Plain text without structure. Just write a string. + +{$ endif $} + +```yaml +<< entry["yaml"] >> +``` + +{$ for figure in entry["figures"] $} +=== "`<< figure["theme"] >>` theme" + ![<< figure["alt_text"] >>](<< figure["path"] >>) +{$ endfor $} + +{$ endfor $} + +## Text Formatting & Features + +### Using Markdown + +All text fields support basic Markdown: + +```yaml +highlights: + - Increased revenue by **$2M** annually + - Developed [open-source tool](https://github.com/example) with *500+ stars* +``` + +- `**text**` → **bold** +- `*text*` → *italic* +- `[text](url)` → hyperlink +- `` `code` `` → `code` + +### Using Typst + +All text fields support + +- Typst math (surround with `$$` like `$$f(x) = x^2$$`) +- Typst commands (like `#emph[emphasized]`). + +```yaml +highlights: + - Showed that $$f(x) = x^2$$ is a parabola + - "This is an #emph[emphasized] text" +``` + +### Arbitrary Keys + +You can add arbitrary keys to any entry. By default, they're ignored, but you can reference them in `design.templates` field. See [Arbitrary Keys in Entries](../how_to/arbitrary_keys_in_entries.md) for more information. + +```yaml hl_lines="6" +experience: + - company: Startup Inc + position: Founder + start_date: 2020-01 + end_date: present + revenue: $5M ARR # Custom field + highlights: + - Built product from zero to profitability +``` diff --git a/docs/user_guide/yaml_input_structure/design.md b/docs/user_guide/yaml_input_structure/design.md new file mode 100644 index 000000000..be4f2754b --- /dev/null +++ b/docs/user_guide/yaml_input_structure/design.md @@ -0,0 +1,190 @@ +# `design` Field + +The `design` field controls every visual aspect of your CV: colors, fonts, spacing, and layout. + +## Built-in Themes + +RenderCV includes << theme_count >> built-in themes. To use one, simply specify the theme: + +```yaml +design: + theme: classic +``` + +Available themes: << available_themes >>. + +All themes are identical except for their default values. If you specify a setting explicitly, it overrides the theme's default. This means: + +- If you specify all design options in your YAML, changing the theme has no effect +- If you leave settings unspecified, changing the theme completely changes the design (because it uses different defaults) +- You can start with any theme and customize only what you want to change + +## Customizing Design + +You can override any field to fine-tune the theme: + +```yaml +design: + theme: classic + colors: + name: rgb(255, 0, 0) # Override just the name color +``` + +Or specify all options for complete control: + +```yaml +design: + theme: classic + + page: + size: us-letter # (1)! + top_margin: 0.7in + bottom_margin: 0.7in + left_margin: 0.7in + right_margin: 0.7in + show_footer: true + show_top_note: true + + colors: + body: rgb(0, 0, 0) + name: rgb(0, 79, 144) + headline: rgb(0, 79, 144) + connections: rgb(0, 79, 144) + section_titles: rgb(0, 79, 144) + links: rgb(0, 79, 144) + footer: rgb(128, 128, 128) + top_note: rgb(128, 128, 128) + + typography: + line_spacing: 0.6em + alignment: justified # (2)! + date_and_location_column_alignment: right + font_family: # (11)! + body: Source Sans 3 # (9)! + name: Source Sans 3 + headline: Source Sans 3 + connections: Source Sans 3 + section_titles: Source Sans 3 + font_size: + body: 10pt + name: 30pt + headline: 10pt + connections: 10pt + section_titles: 1.4em + small_caps: + name: false + headline: false + connections: false + section_titles: false + bold: + name: true + headline: false + connections: false + section_titles: true + + links: + underline: false + show_external_link_icon: false + + header: + alignment: center # (3)! + photo_width: 3.5cm + photo_position: left # (8)! + photo_space_left: 0.4cm + photo_space_right: 0.4cm + space_below_name: 0.7cm + space_below_headline: 0.7cm + space_below_connections: 0.7cm + connections: + phone_number_format: national # (7)! + hyperlink: true + show_icons: true + display_urls_instead_of_usernames: false + separator: '' + space_between_connections: 0.5cm + + section_titles: + type: with_partial_line # (4)! + line_thickness: 0.5pt + space_above: 0.5cm + space_below: 0.3cm + + sections: + allow_page_break: true + space_between_regular_entries: 1.2em + space_between_text_based_entries: 0.3em + show_time_spans_in: # (5)! + - experience + + entries: + date_and_location_width: 4.15cm + side_space: 0.2cm + space_between_columns: 0.1cm + allow_page_break: false + short_second_row: true + summary: + space_above: 0cm + space_left: 0cm + highlights: + bullet: • # (6)! + nested_bullet: • + space_left: 0.15cm + space_above: 0cm + space_between_items: 0cm + space_between_bullet_and_text: 0.5em + + templates: # (10)! + footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' + top_note: '*LAST_UPDATED CURRENT_DATE*' + single_date: MONTH_ABBREVIATION YEAR + date_range: START_DATE – END_DATE + time_span: HOW_MANY_YEARS YEARS HOW_MANY_MONTHS MONTHS + one_line_entry: + main_column: '**LABEL:** DETAILS' + education_entry: + main_column: |- + **INSTITUTION**, AREA + SUMMARY + HIGHLIGHTS + degree_column: '**DEGREE**' + date_and_location_column: |- + LOCATION + DATE + normal_entry: + main_column: |- + **NAME** + SUMMARY + HIGHLIGHTS + date_and_location_column: |- + LOCATION + DATE + experience_entry: + main_column: |- + **COMPANY**, POSITION + SUMMARY + HIGHLIGHTS + date_and_location_column: |- + LOCATION + DATE + publication_entry: + main_column: |- + **TITLE** + AUTHORS + URL (JOURNAL) + date_and_location_column: DATE +``` + +1. **Page size options:** << available_page_sizes >> +2. **Body text alignment:** << available_body_alignments >> + `justified` spreads text across the full width, `justified-with-no-hyphenation` does the same without breaking words +3. **Header alignment:** << available_alignments >> +4. **Section title styles:** << available_section_title_types >> + `with_partial_line` adds a line next to the title, `with_full_line` spans the page, `without_line` has no line, `moderncv` uses ModernCV style, `centered_without_line` centers the title with no line, `centered_with_partial_line` centers with baseline partial lines on both sides, `centered_with_centered_partial_line` centers with middle-aligned lines on both sides, `centered_with_full_line` centers with a full line underneath +5. **Show time spans:** Specify which sections should display duration calculations (e.g., "2 years 3 months") +6. **Bullet characters:** << available_bullets >> +7. **Phone number formats:** << available_phone_number_formats >> + `national` formats for domestic use, `international` includes country code, `E164` is the standard international format +8. **Photo position:** `left` or `right` of the header text +9. **Available fonts:** << available_font_families >>. Also, any system font can be used. Custom fonts can be used as well. See [Custom Fonts](../how_to/custom_fonts.md) for more information. +10. **Templates:** Advanced customization - define how each entry type is rendered using placeholders like NAME, COMPANY, DATE, etc. +11. **Font family shorthand:** You can specify `font_family: "Latin Modern Roman"` directly instead of using nested options to apply the same font everywhere diff --git a/docs/user_guide/yaml_input_structure/index.md b/docs/user_guide/yaml_input_structure/index.md new file mode 100644 index 000000000..e80dd5f76 --- /dev/null +++ b/docs/user_guide/yaml_input_structure/index.md @@ -0,0 +1,52 @@ +# The YAML Input File + +RenderCV uses a single YAML file to generate your CV. This file has four top-level fields: + +```yaml title="Your_Name_CV.yaml" +cv: + ... + # Your content (name, sections, entries) + ... +design: + ... + # Visual styling (theme, colors, fonts, spacing) + ... +locale: + ... + # Language strings (month names, "present", etc.) + ... +settings: + ... + # RenderCV behavior (current date, bold keywords) + ... +``` + +Only `cv` is required. The others have sensible defaults. + + +Explore the detailed documentation for each field: + +- [`cv` Field](cv.md) +- [`design` Field](design.md) +- [`locale` Field](locale.md) +- [`settings` Field](settings.md) + +## JSON Schema + +To maximize your productivity while editing the input YAML file, set up RenderCV's JSON Schema in your IDE. It will validate your inputs on the fly and give auto-complete suggestions. + +![JSON Schema in action](../../assets/images/json_schema.gif) + +=== "Visual Studio Code" + + 1. Install the [YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml). + 2. Name your file ending with `_CV.yaml`. The schema activates automatically. + 3. Press `Ctrl + Space` for suggestions. + +=== "Other Editors" + + 1. Add this line at the top of your file: + ```yaml + # yaml-language-server: $schema=https://github.com/rendercv/rendercv/blob/main/schema.json?raw=true + ``` + 2. Press `Ctrl + Space` for suggestions (if your editor supports JSON Schema). diff --git a/docs/user_guide/yaml_input_structure/locale.md b/docs/user_guide/yaml_input_structure/locale.md new file mode 100644 index 000000000..de77a3d71 --- /dev/null +++ b/docs/user_guide/yaml_input_structure/locale.md @@ -0,0 +1,63 @@ +# `locale` Field + +The `locale` field lets you create a CV in any language by customizing month names, date formatting, and other language-specific text. + +## Built-in Locales + +RenderCV includes translations for 12 languages. To use one, simply specify the language: + +```yaml +locale: + language: german +``` + +Available languages: << available_locales >>. + +## Customizing Locale + +You can override any field to fine-tune the translations: + +```yaml +locale: + language: german + present: jetzt # Override just this field +``` + +Or create a completely custom locale: + +```yaml +locale: + language: english + last_updated: Last updated in + month: month + months: months + year: year + years: years + present: present + month_abbreviations: + - Jan + - Feb + - Mar + - Apr + - May + - June + - July + - Aug + - Sept + - Oct + - Nov + - Dec + month_names: + - January + - February + - March + - April + - May + - June + - July + - August + - September + - October + - November + - December +``` diff --git a/docs/user_guide/yaml_input_structure/settings.md b/docs/user_guide/yaml_input_structure/settings.md new file mode 100644 index 000000000..16bcdb12a --- /dev/null +++ b/docs/user_guide/yaml_input_structure/settings.md @@ -0,0 +1,31 @@ +# `settings` Field + +The `settings` field configures RenderCV's behavior, including output paths, file generation, and text formatting. + +```yaml +settings: + current_date: '2025-12-03' # (5)! + render_command: + design: path/to/design.yaml # (1)! + locale: path/to/locale.yaml # (2)! + typst_path: rendercv_output/NAME_IN_SNAKE_CASE_CV.typ # (3)! + pdf_path: rendercv_output/NAME_IN_SNAKE_CASE_CV.pdf + markdown_path: rendercv_output/NAME_IN_SNAKE_CASE_CV.md + html_path: rendercv_output/NAME_IN_SNAKE_CASE_CV.html + png_path: rendercv_output/NAME_IN_SNAKE_CASE_CV.png + dont_generate_markdown: false + dont_generate_html: false + dont_generate_typst: false + dont_generate_pdf: false + dont_generate_png: false + bold_keywords: # (4)! + - AWS + - Python +``` + + +1. You can optionally split your YAML into multiple files. This file contains the `design` field. +2. You can optionally split your YAML into multiple files. This file contains the `locale` field. +3. Available placeholders are: `NAME`, `NAME_IN_SNAKE_CASE`, `NAME_IN_LOWER_SNAKE_CASE`, `NAME_IN_UPPER_SNAKE_CASE`, `NAME_IN_KEBAB_CASE`, `NAME_IN_LOWER_KEBAB_CASE`, `NAME_IN_UPPER_KEBAB_CASE`, `MONTH_NAME`, `MONTH_ABBREVIATION`, `MONTH`, `MONTH_IN_TWO_DIGITS`, `YEAR`, `YEAR_IN_TWO_DIGITS`. +4. These keywords will be bolded wherever they appear in your CV text (highlights, summaries, etc.). +5. Date used for file naming (when using date placeholders), the "last updated" text in the top note, and time span calculations for ongoing events (entries with `end_date: present`) diff --git a/examples/John_Doe_ClassicTheme_CV.pdf b/examples/John_Doe_ClassicTheme_CV.pdf index 2a844e252..857d4098d 100644 Binary files a/examples/John_Doe_ClassicTheme_CV.pdf and b/examples/John_Doe_ClassicTheme_CV.pdf differ diff --git a/examples/John_Doe_ClassicTheme_CV.yaml b/examples/John_Doe_ClassicTheme_CV.yaml index 25077f286..6d481a627 100644 --- a/examples/John_Doe_ClassicTheme_CV.yaml +++ b/examples/John_Doe_ClassicTheme_CV.yaml @@ -1,261 +1,369 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.8/schema.json cv: name: John Doe - location: Location - email: john.doe@example.com - phone: +1-609-999-9995 - website: + headline: + location: San Francisco, CA + email: john.doe@email.com + photo: + phone: + website: https://rendercv.com/ social_networks: - network: LinkedIn - username: john.doe + username: rendercv - network: GitHub - username: john.doe + username: rendercv + custom_connections: sections: - welcome_to_RenderCV!: - - '[RenderCV](https://rendercv.com) is a Typst-based CV framework designed for academics and engineers, with Markdown syntax support.' - - Each section title is arbitrary. Each section contains a list of entries, and there are 7 different entry types to choose from. + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + - Each section title is arbitrary. + - You can choose any of the 9 entry types for each section. + - Markdown syntax is supported everywhere. This is **bold**, *italic*, and [link](https://example.com). education: - - institution: Stanford University + - institution: Princeton University area: Computer Science degree: PhD date: - start_date: 2023-09 - end_date: present - location: Stanford, CA, USA + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ summary: highlights: - - Working on the optimization of autonomous vehicles in urban environments + - 'Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment' + - 'Advisor: Prof. Sanjeev Arora' + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) - institution: Boğaziçi University area: Computer Engineering degree: BS date: - start_date: 2018-09 - end_date: 2022-06 + start_date: 2014-09 + end_date: 2018-06 location: Istanbul, Türkiye summary: highlights: - - 'GPA: 3.9/4.0, ranked 1st out of 100 students' - - 'Awards: Best Senior Project, High Honor' + - 'GPA: 3.97/4.00, Valedictorian' + - Fulbright Scholarship recipient for Graduate Studies experience: - - company: Company C - position: Summer Intern + - company: Nexus AI + position: Co-Founder & CTO date: - start_date: 2024-06 - end_date: 2024-09 - location: Livingston, LA, USA + start_date: 2023-06 + end_date: present + location: San Francisco, CA summary: highlights: - - Developed deep learning models for the detection of gravitational waves in LIGO data - - Published [3 peer-reviewed research papers](https://example.com) about the project and results - - company: Company B - position: Summer Intern + - Built foundation model infrastructure serving 2M+ monthly API requests with 99.97% uptime + - Raised $18M Series A led by Sequoia Capital, with participation from a16z and Founders Fund + - Scaled engineering team from 3 to 28 across ML research, platform, and applied AI divisions + - Developed proprietary inference optimization reducing latency by 73% compared to baseline + - company: NVIDIA Research + position: Research Intern date: - start_date: 2023-06 - end_date: 2023-09 - location: Ankara, Türkiye + start_date: 2022-05 + end_date: 2022-08 + location: Santa Clara, CA summary: highlights: - - Optimized the production line by 15% by implementing a new scheduling algorithm - - company: Company A - position: Summer Intern + - Designed sparse attention mechanism reducing transformer memory footprint by 4.2x + - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top 5% of submissions) + - company: Google DeepMind + position: Research Intern date: - start_date: 2022-06 - end_date: 2022-09 - location: Istanbul, Türkiye + start_date: 2021-05 + end_date: 2021-08 + location: London, UK summary: highlights: - - Designed an inventory management web application for a warehouse + - Developed reinforcement learning algorithms for multi-agent coordination + - Published research at top-tier venues with significant academic impact + - ICML 2022 main conference paper, cited 340+ times within two years + - NeurIPS 2022 workshop paper on emergent communication protocols + - Invited journal extension in JMLR (2023) + - company: Apple ML Research + position: Research Intern + date: + start_date: 2020-05 + end_date: 2020-08 + location: Cupertino, CA + summary: + highlights: + - Created on-device neural network compression pipeline deployed across 50M+ devices + - Filed 2 patents on efficient model quantization techniques for edge inference + - company: Microsoft Research + position: Research Intern + date: + start_date: 2019-05 + end_date: 2019-08 + location: Redmond, WA + summary: + highlights: + - Implemented novel self-supervised learning framework for low-resource language modeling + - Research integrated into Azure Cognitive Services, reducing training data requirements by 60% projects: - - name: '[Example Project](https://example.com)' + - name: '[FlashInfer](https://github.com/)' date: - start_date: 2024-05 + start_date: 2023-01 end_date: present location: - summary: A web application for writing essays + summary: Open-source library for high-performance LLM inference kernels highlights: - - Launched an [iOS app](https://example.com) in 09/2024 that currently has 10k+ monthly active users - - The app is made open-source (3,000+ stars [on GitHub](https://github.com)) - - name: '[Teaching on Udemy](https://example.com)' - date: Fall 2023 + - Achieved 2.8x speedup over baseline attention implementations on A100 GPUs + - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors + - name: '[NeuralPrune](https://github.com/)' + date: '2021' start_date: end_date: location: - summary: + summary: Automated neural network pruning toolkit with differentiable masks highlights: - - Instructed the "Statistics" course on Udemy (60,000+ students, 200,000+ hours watched) - skills: - - label: Programming - details: Proficient with Python, C++, and Git; good understanding of Web, app development, and DevOps - - label: Mathematics - details: Good understanding of differential equations, calculus, and linear algebra - - label: Languages - details: 'English (fluent, TOEFL: 118/120), Turkish (native)' + - Reduced model size by 90% with less than 1% accuracy degradation on ImageNet + - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars publications: - - title: 3D Finite Element Analysis of No-Insulation Coils + - title: 'Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter Models' + authors: + - '*John Doe*' + - Sarah Williams + - David Park + summary: + doi: 10.1234/neurips.2023.1234 + url: + journal: NeurIPS 2023 + date: 2023-07 + - title: Neural Architecture Search via Differentiable Pruning + authors: + - James Liu + - '*John Doe*' + summary: + doi: 10.1234/neurips.2022.5678 + url: + journal: NeurIPS 2022, Spotlight + date: 2022-12 + - title: Multi-Agent Reinforcement Learning with Emergent Communication authors: - - Frodo Baggins - - '***John Doe***' - - Samwise Gamgee - doi: 10.1109/TASC.2023.3340648 + - Maria Garcia + - '*John Doe*' + - Tom Anderson + summary: + doi: 10.1234/icml.2022.9012 url: - journal: - date: 2004-01 - extracurricular_activities: - - bullet: 'There are 7 unique entry types in RenderCV: *BulletEntry*, *TextEntry*, *EducationEntry*, *ExperienceEntry*, *NormalEntry*, *PublicationEntry*, and *OneLineEntry*.' - - bullet: Each entry type has a different structure and layout. This document demonstrates all of them. - numbered_entries: - - number: This is a numbered entry. - - number: This is another numbered entry. - - number: This is the third numbered entry. - reversed_numbered_entries: - - reversed_number: This is a reversed numbered entry. - - reversed_number: This is another reversed numbered entry. - - reversed_number: This is the third reversed numbered entry. + journal: ICML 2022 + date: 2022-07 + - title: On-Device Model Compression via Learned Quantization + authors: + - '*John Doe*' + - Kevin Wu + summary: + doi: 10.1234/iclr.2021.3456 + url: + journal: ICLR 2021, Best Paper Award + date: 2021-05 + selected_honors: + - bullet: MIT Technology Review 35 Under 35 Innovators (2024) + - bullet: Forbes 30 Under 30 in Enterprise Technology (2024) + - bullet: ACM Doctoral Dissertation Award Honorable Mention (2023) + - bullet: Google PhD Fellowship in Machine Learning (2020 – 2023) + - bullet: Fulbright Scholarship for Graduate Studies (2018) + skills: + - label: Languages + details: Python, C++, CUDA, Rust, Julia + - label: ML Frameworks + details: PyTorch, JAX, TensorFlow, Triton, ONNX + - label: Infrastructure + details: Kubernetes, Ray, distributed training, AWS, GCP + - label: Research Areas + details: Neural architecture search, model compression, efficient inference, multi-agent RL + patents: + - number: Adaptive Quantization for Neural Network Inference on Edge Devices (US Patent 11,234,567) + - number: Dynamic Sparsity Patterns for Efficient Transformer Attention (US Patent 11,345,678) + - number: Hardware-Aware Neural Architecture Search Method (US Patent 11,456,789) + invited_talks: + - reversed_number: Scaling Laws for Efficient Inference — Stanford HAI Symposium (2024) + - reversed_number: Building AI Infrastructure for the Next Decade — TechCrunch Disrupt (2024) + - reversed_number: 'From Research to Production: Lessons in ML Systems — NeurIPS Workshop (2023)' + - reversed_number: "Efficient Deep Learning: A Practitioner's Perspective — Google Tech Talk (2022)" design: theme: classic - page: - size: us-letter - top_margin: 2cm - bottom_margin: 2cm - left_margin: 2cm - right_margin: 2cm - show_page_numbering: true - show_last_updated_date: true - colors: - text: rgb(0, 0, 0) - name: rgb(0, 79, 144) - connections: rgb(0, 79, 144) - section_titles: rgb(0, 79, 144) - links: rgb(0, 79, 144) - last_updated_date_and_page_numbering: rgb(128, 128, 128) - text: - font_family: Source Sans 3 - font_size: 10pt - leading: 0.6em - alignment: justified - date_and_location_column_alignment: right - links: - underline: false - use_external_link_icon: true - header: - name_font_family: Source Sans 3 - name_font_size: 30pt - name_bold: true - small_caps_for_name: false - photo_width: 3.5cm - vertical_space_between_name_and_connections: 0.7cm - vertical_space_between_connections_and_first_section: 0.7cm - horizontal_space_between_connections: 0.5cm - connections_font_family: Source Sans 3 - separator_between_connections: '' - use_icons_for_connections: true - use_urls_as_placeholders_for_connections: false - make_connections_links: true - alignment: center - section_titles: - type: with-partial-line - font_family: Source Sans 3 - font_size: 1.4em - bold: true - small_caps: false - line_thickness: 0.5pt - vertical_space_above: 0.5cm - vertical_space_below: 0.3cm - entries: - date_and_location_width: 4.15cm - left_and_right_margin: 0.2cm - horizontal_space_between_columns: 0.1cm - vertical_space_between_entries: 1.2em - allow_page_break_in_sections: true - allow_page_break_in_entries: true - short_second_row: false - show_time_spans_in: [] - highlights: - bullet: • - nested_bullet: '-' - top_margin: 0.25cm - left_margin: 0.4cm - vertical_space_between_highlights: 0.25cm - horizontal_space_between_bullet_and_highlight: 0.5em - summary_left_margin: 0cm - entry_types: - one_line_entry: - template: '**LABEL:** DETAILS' - education_entry: - main_column_first_row_template: '**INSTITUTION**, AREA' - degree_column_template: '**DEGREE**' - degree_column_width: 1cm - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: |- - LOCATION - DATE - normal_entry: - main_column_first_row_template: '**NAME**' - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: |- - LOCATION - DATE - experience_entry: - main_column_first_row_template: '**COMPANY**, POSITION' - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: |- - LOCATION - DATE - publication_entry: - main_column_first_row_template: '**TITLE**' - main_column_second_row_template: |- - AUTHORS - URL (JOURNAL) - main_column_second_row_without_journal_template: |- - AUTHORS - URL - main_column_second_row_without_url_template: |- - AUTHORS - JOURNAL - date_and_location_column_template: DATE + # page: + # size: us-letter + # top_margin: 0.7in + # bottom_margin: 0.7in + # left_margin: 0.7in + # right_margin: 0.7in + # show_footer: true + # show_top_note: true + # colors: + # body: rgb(0, 0, 0) + # name: rgb(0, 79, 144) + # headline: rgb(0, 79, 144) + # connections: rgb(0, 79, 144) + # section_titles: rgb(0, 79, 144) + # links: rgb(0, 79, 144) + # footer: rgb(128, 128, 128) + # top_note: rgb(128, 128, 128) + # typography: + # line_spacing: 0.6em + # alignment: justified + # date_and_location_column_alignment: right + # font_family: + # body: Source Sans 3 + # name: Source Sans 3 + # headline: Source Sans 3 + # connections: Source Sans 3 + # section_titles: Source Sans 3 + # font_size: + # body: 10pt + # name: 30pt + # headline: 10pt + # connections: 10pt + # section_titles: 1.4em + # small_caps: + # name: false + # headline: false + # connections: false + # section_titles: false + # bold: + # name: true + # headline: false + # connections: false + # section_titles: true + # links: + # underline: false + # show_external_link_icon: false + # header: + # alignment: center + # photo_width: 3.5cm + # photo_position: left + # photo_space_left: 0.4cm + # photo_space_right: 0.4cm + # space_below_name: 0.7cm + # space_below_headline: 0.7cm + # space_below_connections: 0.7cm + # connections: + # phone_number_format: national + # hyperlink: true + # show_icons: true + # display_urls_instead_of_usernames: false + # separator: '' + # space_between_connections: 0.5cm + # section_titles: + # type: with_partial_line + # line_thickness: 0.5pt + # space_above: 0.5cm + # space_below: 0.3cm + # sections: + # allow_page_break: true + # space_between_regular_entries: 1.2em + # space_between_text_based_entries: 0.3em + # show_time_spans_in: + # - experience + # entries: + # date_and_location_width: 4.15cm + # side_space: 0.2cm + # space_between_columns: 0.1cm + # allow_page_break: false + # short_second_row: true + # degree_width: 1cm + # summary: + # space_above: 0cm + # space_left: 0cm + # highlights: + # bullet: • + # nested_bullet: • + # space_left: 0.15cm + # space_above: 0cm + # space_between_items: 0cm + # space_between_bullet_and_text: 0.5em + # templates: + # footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' + # top_note: '*LAST_UPDATED CURRENT_DATE*' + # single_date: MONTH_ABBREVIATION YEAR + # date_range: START_DATE – END_DATE + # time_span: HOW_MANY_YEARS YEARS HOW_MANY_MONTHS MONTHS + # one_line_entry: + # main_column: '**LABEL:** DETAILS' + # education_entry: + # main_column: |- + # **INSTITUTION**, AREA + # SUMMARY + # HIGHLIGHTS + # degree_column: '**DEGREE**' + # date_and_location_column: |- + # LOCATION + # DATE + # normal_entry: + # main_column: |- + # **NAME** + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: |- + # LOCATION + # DATE + # experience_entry: + # main_column: |- + # **COMPANY**, POSITION + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: |- + # LOCATION + # DATE + # publication_entry: + # main_column: |- + # **TITLE** + # SUMMARY + # AUTHORS + # URL (JOURNAL) + # date_and_location_column: DATE locale: - language: en - phone_number_format: national - page_numbering_template: NAME - Page PAGE_NUMBER of TOTAL_PAGES - last_updated_date_template: Last updated in TODAY - date_template: MONTH_ABBREVIATION YEAR - month: month - months: months - year: year - years: years - present: present - to: – - abbreviations_for_months: - - Jan - - Feb - - Mar - - Apr - - May - - June - - July - - Aug - - Sept - - Oct - - Nov - - Dec - full_names_of_months: - - January - - February - - March - - April - - May - - June - - July - - August - - September - - October - - November - - December -rendercv_settings: - date: '2025-03-01' + language: english + # last_updated: Last updated in + # month: month + # months: months + # year: year + # years: years + # present: present + # phrases: + # degree_with_area: DEGREE in AREA + # month_abbreviations: + # - Jan + # - Feb + # - Mar + # - Apr + # - May + # - June + # - July + # - Aug + # - Sept + # - Oct + # - Nov + # - Dec + # month_names: + # - January + # - February + # - March + # - April + # - May + # - June + # - July + # - August + # - September + # - October + # - November + # - December +settings: + current_date: today + render_command: + output_folder: rendercv_output + design: + locale: + typst_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.typ + pdf_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.pdf + markdown_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.md + html_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.html + png_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.png + dont_generate_markdown: false + dont_generate_html: false + dont_generate_typst: false + dont_generate_pdf: false + dont_generate_png: false bold_keywords: [] + pdf_title: NAME - CV diff --git a/examples/John_Doe_EmberTheme_CV.pdf b/examples/John_Doe_EmberTheme_CV.pdf new file mode 100644 index 000000000..e13397429 Binary files /dev/null and b/examples/John_Doe_EmberTheme_CV.pdf differ diff --git a/examples/John_Doe_EmberTheme_CV.yaml b/examples/John_Doe_EmberTheme_CV.yaml new file mode 100644 index 000000000..2f3abdd80 --- /dev/null +++ b/examples/John_Doe_EmberTheme_CV.yaml @@ -0,0 +1,364 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.8/schema.json +cv: + name: John Doe + headline: + location: San Francisco, CA + email: john.doe@email.com + photo: + phone: + website: https://rendercv.com/ + social_networks: + - network: LinkedIn + username: rendercv + - network: GitHub + username: rendercv + custom_connections: + sections: + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + - Each section title is arbitrary. + - You can choose any of the 9 entry types for each section. + - Markdown syntax is supported everywhere. This is **bold**, *italic*, and [link](https://example.com). + education: + - institution: Princeton University + area: Computer Science + degree: PhD + date: + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ + summary: + highlights: + - 'Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment' + - 'Advisor: Prof. Sanjeev Arora' + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) + - institution: Boğaziçi University + area: Computer Engineering + degree: BS + date: + start_date: 2014-09 + end_date: 2018-06 + location: Istanbul, Türkiye + summary: + highlights: + - 'GPA: 3.97/4.00, Valedictorian' + - Fulbright Scholarship recipient for Graduate Studies + experience: + - company: Nexus AI + position: Co-Founder & CTO + date: + start_date: 2023-06 + end_date: present + location: San Francisco, CA + summary: + highlights: + - Built foundation model infrastructure serving 2M+ monthly API requests with 99.97% uptime + - Raised $18M Series A led by Sequoia Capital, with participation from a16z and Founders Fund + - Scaled engineering team from 3 to 28 across ML research, platform, and applied AI divisions + - Developed proprietary inference optimization reducing latency by 73% compared to baseline + - company: NVIDIA Research + position: Research Intern + date: + start_date: 2022-05 + end_date: 2022-08 + location: Santa Clara, CA + summary: + highlights: + - Designed sparse attention mechanism reducing transformer memory footprint by 4.2x + - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top 5% of submissions) + - company: Google DeepMind + position: Research Intern + date: + start_date: 2021-05 + end_date: 2021-08 + location: London, UK + summary: + highlights: + - Developed reinforcement learning algorithms for multi-agent coordination + - Published research at top-tier venues with significant academic impact + - ICML 2022 main conference paper, cited 340+ times within two years + - NeurIPS 2022 workshop paper on emergent communication protocols + - Invited journal extension in JMLR (2023) + - company: Apple ML Research + position: Research Intern + date: + start_date: 2020-05 + end_date: 2020-08 + location: Cupertino, CA + summary: + highlights: + - Created on-device neural network compression pipeline deployed across 50M+ devices + - Filed 2 patents on efficient model quantization techniques for edge inference + - company: Microsoft Research + position: Research Intern + date: + start_date: 2019-05 + end_date: 2019-08 + location: Redmond, WA + summary: + highlights: + - Implemented novel self-supervised learning framework for low-resource language modeling + - Research integrated into Azure Cognitive Services, reducing training data requirements by 60% + projects: + - name: '[FlashInfer](https://github.com/)' + date: + start_date: 2023-01 + end_date: present + location: + summary: Open-source library for high-performance LLM inference kernels + highlights: + - Achieved 2.8x speedup over baseline attention implementations on A100 GPUs + - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors + - name: '[NeuralPrune](https://github.com/)' + date: '2021' + start_date: + end_date: + location: + summary: Automated neural network pruning toolkit with differentiable masks + highlights: + - Reduced model size by 90% with less than 1% accuracy degradation on ImageNet + - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars + publications: + - title: 'Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter Models' + authors: + - '*John Doe*' + - Sarah Williams + - David Park + summary: + doi: 10.1234/neurips.2023.1234 + url: + journal: NeurIPS 2023 + date: 2023-07 + - title: Neural Architecture Search via Differentiable Pruning + authors: + - James Liu + - '*John Doe*' + summary: + doi: 10.1234/neurips.2022.5678 + url: + journal: NeurIPS 2022, Spotlight + date: 2022-12 + - title: Multi-Agent Reinforcement Learning with Emergent Communication + authors: + - Maria Garcia + - '*John Doe*' + - Tom Anderson + summary: + doi: 10.1234/icml.2022.9012 + url: + journal: ICML 2022 + date: 2022-07 + - title: On-Device Model Compression via Learned Quantization + authors: + - '*John Doe*' + - Kevin Wu + summary: + doi: 10.1234/iclr.2021.3456 + url: + journal: ICLR 2021, Best Paper Award + date: 2021-05 + selected_honors: + - bullet: MIT Technology Review 35 Under 35 Innovators (2024) + - bullet: Forbes 30 Under 30 in Enterprise Technology (2024) + - bullet: ACM Doctoral Dissertation Award Honorable Mention (2023) + - bullet: Google PhD Fellowship in Machine Learning (2020 – 2023) + - bullet: Fulbright Scholarship for Graduate Studies (2018) + skills: + - label: Languages + details: Python, C++, CUDA, Rust, Julia + - label: ML Frameworks + details: PyTorch, JAX, TensorFlow, Triton, ONNX + - label: Infrastructure + details: Kubernetes, Ray, distributed training, AWS, GCP + - label: Research Areas + details: Neural architecture search, model compression, efficient inference, multi-agent RL + patents: + - number: Adaptive Quantization for Neural Network Inference on Edge Devices (US Patent 11,234,567) + - number: Dynamic Sparsity Patterns for Efficient Transformer Attention (US Patent 11,345,678) + - number: Hardware-Aware Neural Architecture Search Method (US Patent 11,456,789) + invited_talks: + - reversed_number: Scaling Laws for Efficient Inference — Stanford HAI Symposium (2024) + - reversed_number: Building AI Infrastructure for the Next Decade — TechCrunch Disrupt (2024) + - reversed_number: 'From Research to Production: Lessons in ML Systems — NeurIPS Workshop (2023)' + - reversed_number: "Efficient Deep Learning: A Practitioner's Perspective — Google Tech Talk (2022)" +design: + theme: ember + # page: + # size: us-letter + # top_margin: 0.6in + # bottom_margin: 0.6in + # left_margin: 0.6in + # right_margin: 0.6in + # show_footer: true + # show_top_note: true + # colors: + # body: rgb(35, 31, 32) + # name: rgb(155, 35, 25) + # headline: rgb(90, 60, 55) + # connections: rgb(100, 75, 68) + # section_titles: rgb(155, 35, 25) + # links: rgb(155, 35, 25) + # footer: rgb(140, 125, 118) + # top_note: rgb(140, 125, 118) + # typography: + # line_spacing: 0.6em + # alignment: justified-with-no-hyphenation + # date_and_location_column_alignment: right + # font_family: + # body: Ubuntu + # name: Gentium Book Plus + # headline: Gentium Book Plus + # connections: Ubuntu + # section_titles: Ubuntu + # font_size: + # body: 10pt + # name: 30pt + # headline: 10.5pt + # connections: 9pt + # section_titles: 1.25em + # small_caps: + # name: false + # headline: true + # connections: false + # section_titles: true + # bold: + # name: true + # headline: false + # connections: false + # section_titles: false + # links: + # underline: true + # show_external_link_icon: false + # header: + # alignment: center + # photo_width: 3.5cm + # photo_position: left + # photo_space_left: 0.4cm + # photo_space_right: 0.4cm + # space_below_name: 0.5cm + # space_below_headline: 0.4cm + # space_below_connections: 0.6cm + # connections: + # phone_number_format: national + # hyperlink: true + # show_icons: false + # display_urls_instead_of_usernames: false + # separator: · + # space_between_connections: 0.5cm + # section_titles: + # type: centered_without_line + # line_thickness: 0.5pt + # space_above: 0.55cm + # space_below: 0.25cm + # sections: + # allow_page_break: true + # space_between_regular_entries: 1.1em + # space_between_text_based_entries: 0.3em + # show_time_spans_in: [] + # entries: + # date_and_location_width: 4.15cm + # side_space: 0.1cm + # space_between_columns: 0.15cm + # allow_page_break: false + # short_second_row: false + # degree_width: 1cm + # summary: + # space_above: 0.05cm + # space_left: 0cm + # highlights: + # bullet: ◆ + # nested_bullet: ◦ + # space_left: 0.15cm + # space_above: 0.05cm + # space_between_items: 0.04cm + # space_between_bullet_and_text: 0.5em + # templates: + # footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' + # top_note: '*LAST_UPDATED CURRENT_DATE*' + # single_date: MONTH_ABBREVIATION YEAR + # date_range: START_DATE – END_DATE + # time_span: HOW_MANY_YEARS YEARS HOW_MANY_MONTHS MONTHS + # one_line_entry: + # main_column: '**LABEL:** DETAILS' + # education_entry: + # main_column: |- + # **INSTITUTION** -- LOCATION + # *DEGREE_WITH_AREA* + # SUMMARY + # HIGHLIGHTS + # degree_column: + # date_and_location_column: DATE + # normal_entry: + # main_column: |- + # **NAME** -- LOCATION + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # experience_entry: + # main_column: |- + # **COMPANY** -- LOCATION + # *POSITION* + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # publication_entry: + # main_column: |- + # **TITLE** + # SUMMARY + # AUTHORS + # URL (JOURNAL) + # date_and_location_column: DATE +locale: + language: english + # last_updated: Last updated in + # month: month + # months: months + # year: year + # years: years + # present: present + # phrases: + # degree_with_area: DEGREE in AREA + # month_abbreviations: + # - Jan + # - Feb + # - Mar + # - Apr + # - May + # - June + # - July + # - Aug + # - Sept + # - Oct + # - Nov + # - Dec + # month_names: + # - January + # - February + # - March + # - April + # - May + # - June + # - July + # - August + # - September + # - October + # - November + # - December +settings: + current_date: today + render_command: + output_folder: rendercv_output + design: + locale: + typst_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.typ + pdf_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.pdf + markdown_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.md + html_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.html + png_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.png + dont_generate_markdown: false + dont_generate_html: false + dont_generate_typst: false + dont_generate_pdf: false + dont_generate_png: false + bold_keywords: [] + pdf_title: NAME - CV diff --git a/examples/John_Doe_EngineeringclassicTheme_CV.pdf b/examples/John_Doe_EngineeringclassicTheme_CV.pdf index 4208756bc..b9fdcd0f1 100644 Binary files a/examples/John_Doe_EngineeringclassicTheme_CV.pdf and b/examples/John_Doe_EngineeringclassicTheme_CV.pdf differ diff --git a/examples/John_Doe_EngineeringclassicTheme_CV.yaml b/examples/John_Doe_EngineeringclassicTheme_CV.yaml index 9ce3d3d4b..fbe4863f2 100644 --- a/examples/John_Doe_EngineeringclassicTheme_CV.yaml +++ b/examples/John_Doe_EngineeringclassicTheme_CV.yaml @@ -1,255 +1,362 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.8/schema.json cv: name: John Doe - location: Location - email: john.doe@example.com - phone: +1-609-999-9995 - website: + headline: + location: San Francisco, CA + email: john.doe@email.com + photo: + phone: + website: https://rendercv.com/ social_networks: - network: LinkedIn - username: john.doe + username: rendercv - network: GitHub - username: john.doe + username: rendercv + custom_connections: sections: - welcome_to_RenderCV!: - - '[RenderCV](https://rendercv.com) is a Typst-based CV framework designed for academics and engineers, with Markdown syntax support.' - - Each section title is arbitrary. Each section contains a list of entries, and there are 7 different entry types to choose from. + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + - Each section title is arbitrary. + - You can choose any of the 9 entry types for each section. + - Markdown syntax is supported everywhere. This is **bold**, *italic*, and [link](https://example.com). education: - - institution: Stanford University + - institution: Princeton University area: Computer Science degree: PhD date: - start_date: 2023-09 - end_date: present - location: Stanford, CA, USA + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ summary: highlights: - - Working on the optimization of autonomous vehicles in urban environments + - 'Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment' + - 'Advisor: Prof. Sanjeev Arora' + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) - institution: Boğaziçi University area: Computer Engineering degree: BS date: - start_date: 2018-09 - end_date: 2022-06 + start_date: 2014-09 + end_date: 2018-06 location: Istanbul, Türkiye summary: highlights: - - 'GPA: 3.9/4.0, ranked 1st out of 100 students' - - 'Awards: Best Senior Project, High Honor' + - 'GPA: 3.97/4.00, Valedictorian' + - Fulbright Scholarship recipient for Graduate Studies experience: - - company: Company C - position: Summer Intern + - company: Nexus AI + position: Co-Founder & CTO date: - start_date: 2024-06 - end_date: 2024-09 - location: Livingston, LA, USA + start_date: 2023-06 + end_date: present + location: San Francisco, CA summary: highlights: - - Developed deep learning models for the detection of gravitational waves in LIGO data - - Published [3 peer-reviewed research papers](https://example.com) about the project and results - - company: Company B - position: Summer Intern + - Built foundation model infrastructure serving 2M+ monthly API requests with 99.97% uptime + - Raised $18M Series A led by Sequoia Capital, with participation from a16z and Founders Fund + - Scaled engineering team from 3 to 28 across ML research, platform, and applied AI divisions + - Developed proprietary inference optimization reducing latency by 73% compared to baseline + - company: NVIDIA Research + position: Research Intern date: - start_date: 2023-06 - end_date: 2023-09 - location: Ankara, Türkiye + start_date: 2022-05 + end_date: 2022-08 + location: Santa Clara, CA summary: highlights: - - Optimized the production line by 15% by implementing a new scheduling algorithm - - company: Company A - position: Summer Intern + - Designed sparse attention mechanism reducing transformer memory footprint by 4.2x + - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top 5% of submissions) + - company: Google DeepMind + position: Research Intern date: - start_date: 2022-06 - end_date: 2022-09 - location: Istanbul, Türkiye + start_date: 2021-05 + end_date: 2021-08 + location: London, UK summary: highlights: - - Designed an inventory management web application for a warehouse + - Developed reinforcement learning algorithms for multi-agent coordination + - Published research at top-tier venues with significant academic impact + - ICML 2022 main conference paper, cited 340+ times within two years + - NeurIPS 2022 workshop paper on emergent communication protocols + - Invited journal extension in JMLR (2023) + - company: Apple ML Research + position: Research Intern + date: + start_date: 2020-05 + end_date: 2020-08 + location: Cupertino, CA + summary: + highlights: + - Created on-device neural network compression pipeline deployed across 50M+ devices + - Filed 2 patents on efficient model quantization techniques for edge inference + - company: Microsoft Research + position: Research Intern + date: + start_date: 2019-05 + end_date: 2019-08 + location: Redmond, WA + summary: + highlights: + - Implemented novel self-supervised learning framework for low-resource language modeling + - Research integrated into Azure Cognitive Services, reducing training data requirements by 60% projects: - - name: '[Example Project](https://example.com)' + - name: '[FlashInfer](https://github.com/)' date: - start_date: 2024-05 + start_date: 2023-01 end_date: present location: - summary: A web application for writing essays + summary: Open-source library for high-performance LLM inference kernels highlights: - - Launched an [iOS app](https://example.com) in 09/2024 that currently has 10k+ monthly active users - - The app is made open-source (3,000+ stars [on GitHub](https://github.com)) - - name: '[Teaching on Udemy](https://example.com)' - date: Fall 2023 + - Achieved 2.8x speedup over baseline attention implementations on A100 GPUs + - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors + - name: '[NeuralPrune](https://github.com/)' + date: '2021' start_date: end_date: location: - summary: + summary: Automated neural network pruning toolkit with differentiable masks highlights: - - Instructed the "Statistics" course on Udemy (60,000+ students, 200,000+ hours watched) - skills: - - label: Programming - details: Proficient with Python, C++, and Git; good understanding of Web, app development, and DevOps - - label: Mathematics - details: Good understanding of differential equations, calculus, and linear algebra - - label: Languages - details: 'English (fluent, TOEFL: 118/120), Turkish (native)' + - Reduced model size by 90% with less than 1% accuracy degradation on ImageNet + - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars publications: - - title: 3D Finite Element Analysis of No-Insulation Coils + - title: 'Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter Models' + authors: + - '*John Doe*' + - Sarah Williams + - David Park + summary: + doi: 10.1234/neurips.2023.1234 + url: + journal: NeurIPS 2023 + date: 2023-07 + - title: Neural Architecture Search via Differentiable Pruning + authors: + - James Liu + - '*John Doe*' + summary: + doi: 10.1234/neurips.2022.5678 + url: + journal: NeurIPS 2022, Spotlight + date: 2022-12 + - title: Multi-Agent Reinforcement Learning with Emergent Communication authors: - - Frodo Baggins - - '***John Doe***' - - Samwise Gamgee - doi: 10.1109/TASC.2023.3340648 + - Maria Garcia + - '*John Doe*' + - Tom Anderson + summary: + doi: 10.1234/icml.2022.9012 url: - journal: - date: 2004-01 - extracurricular_activities: - - bullet: 'There are 7 unique entry types in RenderCV: *BulletEntry*, *TextEntry*, *EducationEntry*, *ExperienceEntry*, *NormalEntry*, *PublicationEntry*, and *OneLineEntry*.' - - bullet: Each entry type has a different structure and layout. This document demonstrates all of them. - numbered_entries: - - number: This is a numbered entry. - - number: This is another numbered entry. - - number: This is the third numbered entry. - reversed_numbered_entries: - - reversed_number: This is a reversed numbered entry. - - reversed_number: This is another reversed numbered entry. - - reversed_number: This is the third reversed numbered entry. + journal: ICML 2022 + date: 2022-07 + - title: On-Device Model Compression via Learned Quantization + authors: + - '*John Doe*' + - Kevin Wu + summary: + doi: 10.1234/iclr.2021.3456 + url: + journal: ICLR 2021, Best Paper Award + date: 2021-05 + selected_honors: + - bullet: MIT Technology Review 35 Under 35 Innovators (2024) + - bullet: Forbes 30 Under 30 in Enterprise Technology (2024) + - bullet: ACM Doctoral Dissertation Award Honorable Mention (2023) + - bullet: Google PhD Fellowship in Machine Learning (2020 – 2023) + - bullet: Fulbright Scholarship for Graduate Studies (2018) + skills: + - label: Languages + details: Python, C++, CUDA, Rust, Julia + - label: ML Frameworks + details: PyTorch, JAX, TensorFlow, Triton, ONNX + - label: Infrastructure + details: Kubernetes, Ray, distributed training, AWS, GCP + - label: Research Areas + details: Neural architecture search, model compression, efficient inference, multi-agent RL + patents: + - number: Adaptive Quantization for Neural Network Inference on Edge Devices (US Patent 11,234,567) + - number: Dynamic Sparsity Patterns for Efficient Transformer Attention (US Patent 11,345,678) + - number: Hardware-Aware Neural Architecture Search Method (US Patent 11,456,789) + invited_talks: + - reversed_number: Scaling Laws for Efficient Inference — Stanford HAI Symposium (2024) + - reversed_number: Building AI Infrastructure for the Next Decade — TechCrunch Disrupt (2024) + - reversed_number: 'From Research to Production: Lessons in ML Systems — NeurIPS Workshop (2023)' + - reversed_number: "Efficient Deep Learning: A Practitioner's Perspective — Google Tech Talk (2022)" design: theme: engineeringclassic - page: - size: us-letter - top_margin: 2cm - bottom_margin: 2cm - left_margin: 2cm - right_margin: 2cm - show_page_numbering: false - show_last_updated_date: true - colors: - text: rgb(0, 0, 0) - name: rgb(0, 79, 144) - connections: rgb(0, 79, 144) - section_titles: rgb(0, 79, 144) - links: rgb(0, 79, 144) - last_updated_date_and_page_numbering: rgb(128, 128, 128) - text: - font_family: Raleway - font_size: 10pt - leading: 0.6em - alignment: justified - date_and_location_column_alignment: right - links: - underline: false - use_external_link_icon: false - header: - name_font_family: Raleway - name_font_size: 30pt - name_bold: false - small_caps_for_name: false - photo_width: 3.5cm - vertical_space_between_name_and_connections: 0.7cm - vertical_space_between_connections_and_first_section: 0.7cm - horizontal_space_between_connections: 0.5cm - connections_font_family: Raleway - separator_between_connections: '' - use_icons_for_connections: true - use_urls_as_placeholders_for_connections: false - make_connections_links: true - alignment: left - section_titles: - type: with-partial-line - font_family: Raleway - font_size: 1.4em - bold: false - small_caps: false - line_thickness: 0.5pt - vertical_space_above: 0.5cm - vertical_space_below: 0.3cm - entries: - date_and_location_width: 4.15cm - left_and_right_margin: 0.2cm - horizontal_space_between_columns: 0.1cm - vertical_space_between_entries: 1.2em - allow_page_break_in_sections: true - allow_page_break_in_entries: true - short_second_row: false - show_time_spans_in: [] - highlights: - bullet: • - nested_bullet: '-' - top_margin: 0.25cm - left_margin: 0cm - vertical_space_between_highlights: 0.25cm - horizontal_space_between_bullet_and_highlight: 0.5em - summary_left_margin: 0cm - entry_types: - one_line_entry: - template: '**LABEL:** DETAILS' - education_entry: - main_column_first_row_template: '**INSTITUTION**, AREA -- LOCATION' - degree_column_template: '**DEGREE**' - degree_column_width: 1cm - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: DATE - normal_entry: - main_column_first_row_template: '**NAME** -- **LOCATION**' - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: DATE - experience_entry: - main_column_first_row_template: '**POSITION**, COMPANY -- LOCATION' - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: DATE - publication_entry: - main_column_first_row_template: '**TITLE**' - main_column_second_row_template: |- - AUTHORS - URL (JOURNAL) - main_column_second_row_without_journal_template: |- - AUTHORS - URL - main_column_second_row_without_url_template: |- - AUTHORS - JOURNAL - date_and_location_column_template: DATE + # page: + # size: us-letter + # top_margin: 0.7in + # bottom_margin: 0.7in + # left_margin: 0.7in + # right_margin: 0.7in + # show_footer: true + # show_top_note: true + # colors: + # body: rgb(0, 0, 0) + # name: rgb(0, 79, 144) + # headline: rgb(0, 79, 144) + # connections: rgb(0, 79, 144) + # section_titles: rgb(0, 79, 144) + # links: rgb(0, 79, 144) + # footer: rgb(128, 128, 128) + # top_note: rgb(128, 128, 128) + # typography: + # line_spacing: 0.6em + # alignment: justified + # date_and_location_column_alignment: right + # font_family: + # body: Raleway + # name: Raleway + # headline: Raleway + # connections: Raleway + # section_titles: Raleway + # font_size: + # body: 10pt + # name: 30pt + # headline: 10pt + # connections: 10pt + # section_titles: 1.4em + # small_caps: + # name: false + # headline: false + # connections: false + # section_titles: false + # bold: + # name: false + # headline: false + # connections: false + # section_titles: false + # links: + # underline: false + # show_external_link_icon: false + # header: + # alignment: left + # photo_width: 3.5cm + # photo_position: left + # photo_space_left: 0.4cm + # photo_space_right: 0.4cm + # space_below_name: 0.7cm + # space_below_headline: 0.7cm + # space_below_connections: 0.7cm + # connections: + # phone_number_format: national + # hyperlink: true + # show_icons: true + # display_urls_instead_of_usernames: false + # separator: '' + # space_between_connections: 0.5cm + # section_titles: + # type: with_full_line + # line_thickness: 0.5pt + # space_above: 0.5cm + # space_below: 0.3cm + # sections: + # allow_page_break: true + # space_between_regular_entries: 1.2em + # space_between_text_based_entries: 0.3em + # show_time_spans_in: [] + # entries: + # date_and_location_width: 4.15cm + # side_space: 0.2cm + # space_between_columns: 0.1cm + # allow_page_break: false + # short_second_row: false + # degree_width: 1cm + # summary: + # space_above: 0.12cm + # space_left: 0cm + # highlights: + # bullet: • + # nested_bullet: • + # space_left: 0cm + # space_above: 0.12cm + # space_between_items: 0.12cm + # space_between_bullet_and_text: 0.5em + # templates: + # footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' + # top_note: '*LAST_UPDATED CURRENT_DATE*' + # single_date: MONTH_ABBREVIATION YEAR + # date_range: START_DATE – END_DATE + # time_span: HOW_MANY_YEARS YEARS HOW_MANY_MONTHS MONTHS + # one_line_entry: + # main_column: '**LABEL:** DETAILS' + # education_entry: + # main_column: |- + # **INSTITUTION**, DEGREE_WITH_AREA -- LOCATION + # SUMMARY + # HIGHLIGHTS + # degree_column: + # date_and_location_column: DATE + # normal_entry: + # main_column: |- + # **NAME** -- **LOCATION** + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # experience_entry: + # main_column: |- + # **POSITION**, COMPANY -- LOCATION + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # publication_entry: + # main_column: |- + # **TITLE** + # SUMMARY + # AUTHORS + # URL (JOURNAL) + # date_and_location_column: DATE locale: - language: en - phone_number_format: national - page_numbering_template: NAME - Page PAGE_NUMBER of TOTAL_PAGES - last_updated_date_template: Last updated in TODAY - date_template: MONTH_ABBREVIATION YEAR - month: month - months: months - year: year - years: years - present: present - to: – - abbreviations_for_months: - - Jan - - Feb - - Mar - - Apr - - May - - June - - July - - Aug - - Sept - - Oct - - Nov - - Dec - full_names_of_months: - - January - - February - - March - - April - - May - - June - - July - - August - - September - - October - - November - - December -rendercv_settings: - date: '2025-03-01' + language: english + # last_updated: Last updated in + # month: month + # months: months + # year: year + # years: years + # present: present + # phrases: + # degree_with_area: DEGREE in AREA + # month_abbreviations: + # - Jan + # - Feb + # - Mar + # - Apr + # - May + # - June + # - July + # - Aug + # - Sept + # - Oct + # - Nov + # - Dec + # month_names: + # - January + # - February + # - March + # - April + # - May + # - June + # - July + # - August + # - September + # - October + # - November + # - December +settings: + current_date: today + render_command: + output_folder: rendercv_output + design: + locale: + typst_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.typ + pdf_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.pdf + markdown_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.md + html_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.html + png_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.png + dont_generate_markdown: false + dont_generate_html: false + dont_generate_typst: false + dont_generate_pdf: false + dont_generate_png: false bold_keywords: [] + pdf_title: NAME - CV diff --git a/examples/John_Doe_EngineeringresumesTheme_CV.pdf b/examples/John_Doe_EngineeringresumesTheme_CV.pdf index a0abf6fe2..e490ff49a 100644 Binary files a/examples/John_Doe_EngineeringresumesTheme_CV.pdf and b/examples/John_Doe_EngineeringresumesTheme_CV.pdf differ diff --git a/examples/John_Doe_EngineeringresumesTheme_CV.yaml b/examples/John_Doe_EngineeringresumesTheme_CV.yaml index 62ea95d0f..f3ab0f257 100644 --- a/examples/John_Doe_EngineeringresumesTheme_CV.yaml +++ b/examples/John_Doe_EngineeringresumesTheme_CV.yaml @@ -1,255 +1,362 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.8/schema.json cv: name: John Doe - location: Location - email: john.doe@example.com - phone: +1-609-999-9995 - website: + headline: + location: San Francisco, CA + email: john.doe@email.com + photo: + phone: + website: https://rendercv.com/ social_networks: - network: LinkedIn - username: john.doe + username: rendercv - network: GitHub - username: john.doe + username: rendercv + custom_connections: sections: - welcome_to_RenderCV!: - - '[RenderCV](https://rendercv.com) is a Typst-based CV framework designed for academics and engineers, with Markdown syntax support.' - - Each section title is arbitrary. Each section contains a list of entries, and there are 7 different entry types to choose from. + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + - Each section title is arbitrary. + - You can choose any of the 9 entry types for each section. + - Markdown syntax is supported everywhere. This is **bold**, *italic*, and [link](https://example.com). education: - - institution: Stanford University + - institution: Princeton University area: Computer Science degree: PhD date: - start_date: 2023-09 - end_date: present - location: Stanford, CA, USA + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ summary: highlights: - - Working on the optimization of autonomous vehicles in urban environments + - 'Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment' + - 'Advisor: Prof. Sanjeev Arora' + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) - institution: Boğaziçi University area: Computer Engineering degree: BS date: - start_date: 2018-09 - end_date: 2022-06 + start_date: 2014-09 + end_date: 2018-06 location: Istanbul, Türkiye summary: highlights: - - 'GPA: 3.9/4.0, ranked 1st out of 100 students' - - 'Awards: Best Senior Project, High Honor' + - 'GPA: 3.97/4.00, Valedictorian' + - Fulbright Scholarship recipient for Graduate Studies experience: - - company: Company C - position: Summer Intern + - company: Nexus AI + position: Co-Founder & CTO date: - start_date: 2024-06 - end_date: 2024-09 - location: Livingston, LA, USA + start_date: 2023-06 + end_date: present + location: San Francisco, CA summary: highlights: - - Developed deep learning models for the detection of gravitational waves in LIGO data - - Published [3 peer-reviewed research papers](https://example.com) about the project and results - - company: Company B - position: Summer Intern + - Built foundation model infrastructure serving 2M+ monthly API requests with 99.97% uptime + - Raised $18M Series A led by Sequoia Capital, with participation from a16z and Founders Fund + - Scaled engineering team from 3 to 28 across ML research, platform, and applied AI divisions + - Developed proprietary inference optimization reducing latency by 73% compared to baseline + - company: NVIDIA Research + position: Research Intern date: - start_date: 2023-06 - end_date: 2023-09 - location: Ankara, Türkiye + start_date: 2022-05 + end_date: 2022-08 + location: Santa Clara, CA summary: highlights: - - Optimized the production line by 15% by implementing a new scheduling algorithm - - company: Company A - position: Summer Intern + - Designed sparse attention mechanism reducing transformer memory footprint by 4.2x + - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top 5% of submissions) + - company: Google DeepMind + position: Research Intern date: - start_date: 2022-06 - end_date: 2022-09 - location: Istanbul, Türkiye + start_date: 2021-05 + end_date: 2021-08 + location: London, UK summary: highlights: - - Designed an inventory management web application for a warehouse + - Developed reinforcement learning algorithms for multi-agent coordination + - Published research at top-tier venues with significant academic impact + - ICML 2022 main conference paper, cited 340+ times within two years + - NeurIPS 2022 workshop paper on emergent communication protocols + - Invited journal extension in JMLR (2023) + - company: Apple ML Research + position: Research Intern + date: + start_date: 2020-05 + end_date: 2020-08 + location: Cupertino, CA + summary: + highlights: + - Created on-device neural network compression pipeline deployed across 50M+ devices + - Filed 2 patents on efficient model quantization techniques for edge inference + - company: Microsoft Research + position: Research Intern + date: + start_date: 2019-05 + end_date: 2019-08 + location: Redmond, WA + summary: + highlights: + - Implemented novel self-supervised learning framework for low-resource language modeling + - Research integrated into Azure Cognitive Services, reducing training data requirements by 60% projects: - - name: '[Example Project](https://example.com)' + - name: '[FlashInfer](https://github.com/)' date: - start_date: 2024-05 + start_date: 2023-01 end_date: present location: - summary: A web application for writing essays + summary: Open-source library for high-performance LLM inference kernels highlights: - - Launched an [iOS app](https://example.com) in 09/2024 that currently has 10k+ monthly active users - - The app is made open-source (3,000+ stars [on GitHub](https://github.com)) - - name: '[Teaching on Udemy](https://example.com)' - date: Fall 2023 + - Achieved 2.8x speedup over baseline attention implementations on A100 GPUs + - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors + - name: '[NeuralPrune](https://github.com/)' + date: '2021' start_date: end_date: location: - summary: + summary: Automated neural network pruning toolkit with differentiable masks highlights: - - Instructed the "Statistics" course on Udemy (60,000+ students, 200,000+ hours watched) - skills: - - label: Programming - details: Proficient with Python, C++, and Git; good understanding of Web, app development, and DevOps - - label: Mathematics - details: Good understanding of differential equations, calculus, and linear algebra - - label: Languages - details: 'English (fluent, TOEFL: 118/120), Turkish (native)' + - Reduced model size by 90% with less than 1% accuracy degradation on ImageNet + - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars publications: - - title: 3D Finite Element Analysis of No-Insulation Coils + - title: 'Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter Models' + authors: + - '*John Doe*' + - Sarah Williams + - David Park + summary: + doi: 10.1234/neurips.2023.1234 + url: + journal: NeurIPS 2023 + date: 2023-07 + - title: Neural Architecture Search via Differentiable Pruning + authors: + - James Liu + - '*John Doe*' + summary: + doi: 10.1234/neurips.2022.5678 + url: + journal: NeurIPS 2022, Spotlight + date: 2022-12 + - title: Multi-Agent Reinforcement Learning with Emergent Communication authors: - - Frodo Baggins - - '***John Doe***' - - Samwise Gamgee - doi: 10.1109/TASC.2023.3340648 + - Maria Garcia + - '*John Doe*' + - Tom Anderson + summary: + doi: 10.1234/icml.2022.9012 url: - journal: - date: 2004-01 - extracurricular_activities: - - bullet: 'There are 7 unique entry types in RenderCV: *BulletEntry*, *TextEntry*, *EducationEntry*, *ExperienceEntry*, *NormalEntry*, *PublicationEntry*, and *OneLineEntry*.' - - bullet: Each entry type has a different structure and layout. This document demonstrates all of them. - numbered_entries: - - number: This is a numbered entry. - - number: This is another numbered entry. - - number: This is the third numbered entry. - reversed_numbered_entries: - - reversed_number: This is a reversed numbered entry. - - reversed_number: This is another reversed numbered entry. - - reversed_number: This is the third reversed numbered entry. + journal: ICML 2022 + date: 2022-07 + - title: On-Device Model Compression via Learned Quantization + authors: + - '*John Doe*' + - Kevin Wu + summary: + doi: 10.1234/iclr.2021.3456 + url: + journal: ICLR 2021, Best Paper Award + date: 2021-05 + selected_honors: + - bullet: MIT Technology Review 35 Under 35 Innovators (2024) + - bullet: Forbes 30 Under 30 in Enterprise Technology (2024) + - bullet: ACM Doctoral Dissertation Award Honorable Mention (2023) + - bullet: Google PhD Fellowship in Machine Learning (2020 – 2023) + - bullet: Fulbright Scholarship for Graduate Studies (2018) + skills: + - label: Languages + details: Python, C++, CUDA, Rust, Julia + - label: ML Frameworks + details: PyTorch, JAX, TensorFlow, Triton, ONNX + - label: Infrastructure + details: Kubernetes, Ray, distributed training, AWS, GCP + - label: Research Areas + details: Neural architecture search, model compression, efficient inference, multi-agent RL + patents: + - number: Adaptive Quantization for Neural Network Inference on Edge Devices (US Patent 11,234,567) + - number: Dynamic Sparsity Patterns for Efficient Transformer Attention (US Patent 11,345,678) + - number: Hardware-Aware Neural Architecture Search Method (US Patent 11,456,789) + invited_talks: + - reversed_number: Scaling Laws for Efficient Inference — Stanford HAI Symposium (2024) + - reversed_number: Building AI Infrastructure for the Next Decade — TechCrunch Disrupt (2024) + - reversed_number: 'From Research to Production: Lessons in ML Systems — NeurIPS Workshop (2023)' + - reversed_number: "Efficient Deep Learning: A Practitioner's Perspective — Google Tech Talk (2022)" design: theme: engineeringresumes - page: - size: us-letter - top_margin: 2cm - bottom_margin: 2cm - left_margin: 2cm - right_margin: 2cm - show_page_numbering: false - show_last_updated_date: true - colors: - text: rgb(0, 0, 0) - name: rgb(0, 0, 0) - connections: rgb(0, 0, 0) - section_titles: rgb(0, 0, 0) - links: rgb(0, 0, 0) - last_updated_date_and_page_numbering: rgb(128, 128, 128) - text: - font_family: XCharter - font_size: 10pt - leading: 0.6em - alignment: justified - date_and_location_column_alignment: right - links: - underline: true - use_external_link_icon: false - header: - name_font_family: XCharter - name_font_size: 25pt - name_bold: false - small_caps_for_name: false - photo_width: 3.5cm - vertical_space_between_name_and_connections: 0.7cm - vertical_space_between_connections_and_first_section: 0.7cm - horizontal_space_between_connections: 0.5cm - connections_font_family: XCharter - separator_between_connections: '|' - use_icons_for_connections: false - use_urls_as_placeholders_for_connections: true - make_connections_links: true - alignment: center - section_titles: - type: with-full-line - font_family: XCharter - font_size: 1.2em - bold: true - small_caps: false - line_thickness: 0.5pt - vertical_space_above: 0.55cm - vertical_space_below: 0.3cm - entries: - date_and_location_width: 4.15cm - left_and_right_margin: 0cm - horizontal_space_between_columns: 0.1cm - vertical_space_between_entries: 0.4cm - allow_page_break_in_sections: true - allow_page_break_in_entries: true - short_second_row: false - show_time_spans_in: [] - highlights: - bullet: • - nested_bullet: '-' - top_margin: 0.25cm - left_margin: 0cm - vertical_space_between_highlights: 0.19cm - horizontal_space_between_bullet_and_highlight: 0.3em - summary_left_margin: 0cm - entry_types: - one_line_entry: - template: '**LABEL:** DETAILS' - education_entry: - main_column_first_row_template: '**INSTITUTION**, DEGREE in AREA -- LOCATION' - degree_column_template: - degree_column_width: 1cm - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: DATE - normal_entry: - main_column_first_row_template: '**NAME** -- **LOCATION**' - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: DATE - experience_entry: - main_column_first_row_template: '**POSITION**, COMPANY -- LOCATION' - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: DATE - publication_entry: - main_column_first_row_template: '**TITLE**' - main_column_second_row_template: |- - AUTHORS - URL (JOURNAL) - main_column_second_row_without_journal_template: |- - AUTHORS - URL - main_column_second_row_without_url_template: |- - AUTHORS - JOURNAL - date_and_location_column_template: DATE + # page: + # size: us-letter + # top_margin: 0.7in + # bottom_margin: 0.7in + # left_margin: 0.7in + # right_margin: 0.7in + # show_footer: false + # show_top_note: true + # colors: + # body: rgb(0, 0, 0) + # name: rgb(0, 0, 0) + # headline: rgb(0, 0, 0) + # connections: rgb(0, 0, 0) + # section_titles: rgb(0, 0, 0) + # links: rgb(0, 0, 0) + # footer: rgb(128, 128, 128) + # top_note: rgb(128, 128, 128) + # typography: + # line_spacing: 0.6em + # alignment: justified + # date_and_location_column_alignment: right + # font_family: + # body: XCharter + # name: XCharter + # headline: XCharter + # connections: XCharter + # section_titles: XCharter + # font_size: + # body: 10pt + # name: 25pt + # headline: 10pt + # connections: 10pt + # section_titles: 1.2em + # small_caps: + # name: false + # headline: false + # connections: false + # section_titles: false + # bold: + # name: false + # headline: false + # connections: false + # section_titles: true + # links: + # underline: true + # show_external_link_icon: false + # header: + # alignment: center + # photo_width: 3.5cm + # photo_position: left + # photo_space_left: 0.4cm + # photo_space_right: 0.4cm + # space_below_name: 0.7cm + # space_below_headline: 0.7cm + # space_below_connections: 0.7cm + # connections: + # phone_number_format: national + # hyperlink: true + # show_icons: false + # display_urls_instead_of_usernames: true + # separator: '|' + # space_between_connections: 0.5cm + # section_titles: + # type: with_full_line + # line_thickness: 0.5pt + # space_above: 0.5cm + # space_below: 0.3cm + # sections: + # allow_page_break: true + # space_between_regular_entries: 0.42cm + # space_between_text_based_entries: 0.15cm + # show_time_spans_in: [] + # entries: + # date_and_location_width: 4.15cm + # side_space: 0cm + # space_between_columns: 0.1cm + # allow_page_break: false + # short_second_row: false + # degree_width: 1cm + # summary: + # space_above: 0.08cm + # space_left: 0cm + # highlights: + # bullet: ● + # nested_bullet: ● + # space_left: 0cm + # space_above: 0.08cm + # space_between_items: 0.08cm + # space_between_bullet_and_text: 0.3em + # templates: + # footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' + # top_note: '*LAST_UPDATED CURRENT_DATE*' + # single_date: MONTH_ABBREVIATION YEAR + # date_range: START_DATE – END_DATE + # time_span: HOW_MANY_YEARS YEARS HOW_MANY_MONTHS MONTHS + # one_line_entry: + # main_column: '**LABEL:** DETAILS' + # education_entry: + # main_column: |- + # **INSTITUTION**, DEGREE_WITH_AREA -- LOCATION + # SUMMARY + # HIGHLIGHTS + # degree_column: + # date_and_location_column: DATE + # normal_entry: + # main_column: |- + # **NAME** -- **LOCATION** + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # experience_entry: + # main_column: |- + # **POSITION**, COMPANY -- LOCATION + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # publication_entry: + # main_column: |- + # **TITLE** + # SUMMARY + # AUTHORS + # URL (JOURNAL) + # date_and_location_column: DATE locale: - language: en - phone_number_format: national - page_numbering_template: NAME - Page PAGE_NUMBER of TOTAL_PAGES - last_updated_date_template: Last updated in TODAY - date_template: MONTH_ABBREVIATION YEAR - month: month - months: months - year: year - years: years - present: present - to: – - abbreviations_for_months: - - Jan - - Feb - - Mar - - Apr - - May - - June - - July - - Aug - - Sept - - Oct - - Nov - - Dec - full_names_of_months: - - January - - February - - March - - April - - May - - June - - July - - August - - September - - October - - November - - December -rendercv_settings: - date: '2025-03-01' + language: english + # last_updated: Last updated in + # month: month + # months: months + # year: year + # years: years + # present: present + # phrases: + # degree_with_area: DEGREE in AREA + # month_abbreviations: + # - Jan + # - Feb + # - Mar + # - Apr + # - May + # - June + # - July + # - Aug + # - Sept + # - Oct + # - Nov + # - Dec + # month_names: + # - January + # - February + # - March + # - April + # - May + # - June + # - July + # - August + # - September + # - October + # - November + # - December +settings: + current_date: today + render_command: + output_folder: rendercv_output + design: + locale: + typst_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.typ + pdf_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.pdf + markdown_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.md + html_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.html + png_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.png + dont_generate_markdown: false + dont_generate_html: false + dont_generate_typst: false + dont_generate_pdf: false + dont_generate_png: false bold_keywords: [] + pdf_title: NAME - CV diff --git a/examples/John_Doe_HarvardTheme_CV.pdf b/examples/John_Doe_HarvardTheme_CV.pdf new file mode 100644 index 000000000..adcf9c14a Binary files /dev/null and b/examples/John_Doe_HarvardTheme_CV.pdf differ diff --git a/examples/John_Doe_HarvardTheme_CV.yaml b/examples/John_Doe_HarvardTheme_CV.yaml new file mode 100644 index 000000000..212aee981 --- /dev/null +++ b/examples/John_Doe_HarvardTheme_CV.yaml @@ -0,0 +1,362 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.8/schema.json +cv: + name: John Doe + headline: + location: San Francisco, CA + email: john.doe@email.com + photo: + phone: + website: https://rendercv.com/ + social_networks: + - network: LinkedIn + username: rendercv + - network: GitHub + username: rendercv + custom_connections: + sections: + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + - Each section title is arbitrary. + - You can choose any of the 9 entry types for each section. + - Markdown syntax is supported everywhere. This is **bold**, *italic*, and [link](https://example.com). + education: + - institution: Princeton University + area: Computer Science + degree: PhD + date: + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ + summary: + highlights: + - 'Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment' + - 'Advisor: Prof. Sanjeev Arora' + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) + - institution: Boğaziçi University + area: Computer Engineering + degree: BS + date: + start_date: 2014-09 + end_date: 2018-06 + location: Istanbul, Türkiye + summary: + highlights: + - 'GPA: 3.97/4.00, Valedictorian' + - Fulbright Scholarship recipient for Graduate Studies + experience: + - company: Nexus AI + position: Co-Founder & CTO + date: + start_date: 2023-06 + end_date: present + location: San Francisco, CA + summary: + highlights: + - Built foundation model infrastructure serving 2M+ monthly API requests with 99.97% uptime + - Raised $18M Series A led by Sequoia Capital, with participation from a16z and Founders Fund + - Scaled engineering team from 3 to 28 across ML research, platform, and applied AI divisions + - Developed proprietary inference optimization reducing latency by 73% compared to baseline + - company: NVIDIA Research + position: Research Intern + date: + start_date: 2022-05 + end_date: 2022-08 + location: Santa Clara, CA + summary: + highlights: + - Designed sparse attention mechanism reducing transformer memory footprint by 4.2x + - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top 5% of submissions) + - company: Google DeepMind + position: Research Intern + date: + start_date: 2021-05 + end_date: 2021-08 + location: London, UK + summary: + highlights: + - Developed reinforcement learning algorithms for multi-agent coordination + - Published research at top-tier venues with significant academic impact + - ICML 2022 main conference paper, cited 340+ times within two years + - NeurIPS 2022 workshop paper on emergent communication protocols + - Invited journal extension in JMLR (2023) + - company: Apple ML Research + position: Research Intern + date: + start_date: 2020-05 + end_date: 2020-08 + location: Cupertino, CA + summary: + highlights: + - Created on-device neural network compression pipeline deployed across 50M+ devices + - Filed 2 patents on efficient model quantization techniques for edge inference + - company: Microsoft Research + position: Research Intern + date: + start_date: 2019-05 + end_date: 2019-08 + location: Redmond, WA + summary: + highlights: + - Implemented novel self-supervised learning framework for low-resource language modeling + - Research integrated into Azure Cognitive Services, reducing training data requirements by 60% + projects: + - name: '[FlashInfer](https://github.com/)' + date: + start_date: 2023-01 + end_date: present + location: + summary: Open-source library for high-performance LLM inference kernels + highlights: + - Achieved 2.8x speedup over baseline attention implementations on A100 GPUs + - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors + - name: '[NeuralPrune](https://github.com/)' + date: '2021' + start_date: + end_date: + location: + summary: Automated neural network pruning toolkit with differentiable masks + highlights: + - Reduced model size by 90% with less than 1% accuracy degradation on ImageNet + - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars + publications: + - title: 'Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter Models' + authors: + - '*John Doe*' + - Sarah Williams + - David Park + summary: + doi: 10.1234/neurips.2023.1234 + url: + journal: NeurIPS 2023 + date: 2023-07 + - title: Neural Architecture Search via Differentiable Pruning + authors: + - James Liu + - '*John Doe*' + summary: + doi: 10.1234/neurips.2022.5678 + url: + journal: NeurIPS 2022, Spotlight + date: 2022-12 + - title: Multi-Agent Reinforcement Learning with Emergent Communication + authors: + - Maria Garcia + - '*John Doe*' + - Tom Anderson + summary: + doi: 10.1234/icml.2022.9012 + url: + journal: ICML 2022 + date: 2022-07 + - title: On-Device Model Compression via Learned Quantization + authors: + - '*John Doe*' + - Kevin Wu + summary: + doi: 10.1234/iclr.2021.3456 + url: + journal: ICLR 2021, Best Paper Award + date: 2021-05 + selected_honors: + - bullet: MIT Technology Review 35 Under 35 Innovators (2024) + - bullet: Forbes 30 Under 30 in Enterprise Technology (2024) + - bullet: ACM Doctoral Dissertation Award Honorable Mention (2023) + - bullet: Google PhD Fellowship in Machine Learning (2020 – 2023) + - bullet: Fulbright Scholarship for Graduate Studies (2018) + skills: + - label: Languages + details: Python, C++, CUDA, Rust, Julia + - label: ML Frameworks + details: PyTorch, JAX, TensorFlow, Triton, ONNX + - label: Infrastructure + details: Kubernetes, Ray, distributed training, AWS, GCP + - label: Research Areas + details: Neural architecture search, model compression, efficient inference, multi-agent RL + patents: + - number: Adaptive Quantization for Neural Network Inference on Edge Devices (US Patent 11,234,567) + - number: Dynamic Sparsity Patterns for Efficient Transformer Attention (US Patent 11,345,678) + - number: Hardware-Aware Neural Architecture Search Method (US Patent 11,456,789) + invited_talks: + - reversed_number: Scaling Laws for Efficient Inference — Stanford HAI Symposium (2024) + - reversed_number: Building AI Infrastructure for the Next Decade — TechCrunch Disrupt (2024) + - reversed_number: 'From Research to Production: Lessons in ML Systems — NeurIPS Workshop (2023)' + - reversed_number: "Efficient Deep Learning: A Practitioner's Perspective — Google Tech Talk (2022)" +design: + theme: harvard + # page: + # size: us-letter + # top_margin: 0.5in + # bottom_margin: 0.5in + # left_margin: 0.5in + # right_margin: 0.5in + # show_footer: true + # show_top_note: false + # colors: + # body: rgb(0, 0, 0) + # name: rgb(0, 0, 0) + # headline: rgb(0, 0, 0) + # connections: rgb(0, 0, 0) + # section_titles: rgb(0, 0, 0) + # links: rgb(0, 0, 0) + # footer: rgb(128, 128, 128) + # top_note: rgb(128, 128, 128) + # typography: + # line_spacing: 0.6em + # alignment: justified + # date_and_location_column_alignment: right + # font_family: + # body: XCharter + # name: XCharter + # headline: XCharter + # connections: XCharter + # section_titles: XCharter + # font_size: + # body: 10pt + # name: 25pt + # headline: 10pt + # connections: 9pt + # section_titles: 1.3em + # small_caps: + # name: false + # headline: false + # connections: false + # section_titles: false + # bold: + # name: true + # headline: false + # connections: false + # section_titles: true + # links: + # underline: false + # show_external_link_icon: false + # header: + # alignment: center + # photo_width: 3.5cm + # photo_position: left + # photo_space_left: 0.4cm + # photo_space_right: 0.4cm + # space_below_name: 0.5cm + # space_below_headline: 0.5cm + # space_below_connections: 0.5cm + # connections: + # phone_number_format: national + # hyperlink: true + # show_icons: false + # display_urls_instead_of_usernames: false + # separator: • + # space_between_connections: 0.4cm + # section_titles: + # type: centered_with_centered_partial_line + # line_thickness: 0.5pt + # space_above: 0.5cm + # space_below: 0.2cm + # sections: + # allow_page_break: true + # space_between_regular_entries: 1em + # space_between_text_based_entries: 0.3em + # show_time_spans_in: [] + # entries: + # date_and_location_width: 4.15cm + # side_space: 0.2cm + # space_between_columns: 0.1cm + # allow_page_break: false + # short_second_row: false + # degree_width: 1cm + # summary: + # space_above: 0cm + # space_left: 0cm + # highlights: + # bullet: • + # nested_bullet: • + # space_left: 0.15cm + # space_above: 0cm + # space_between_items: 0cm + # space_between_bullet_and_text: 0.5em + # templates: + # footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' + # top_note: '*LAST_UPDATED CURRENT_DATE*' + # single_date: MONTH_ABBREVIATION YEAR + # date_range: START_DATE – END_DATE + # time_span: '' + # one_line_entry: + # main_column: '**LABEL:** DETAILS' + # education_entry: + # main_column: |- + # **INSTITUTION**, DEGREE_WITH_AREA -- LOCATION + # SUMMARY + # HIGHLIGHTS + # degree_column: '**DEGREE**' + # date_and_location_column: DATE + # normal_entry: + # main_column: |- + # **NAME** -- **LOCATION** + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # experience_entry: + # main_column: |- + # **COMPANY**, POSITION -- LOCATION + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # publication_entry: + # main_column: |- + # **TITLE** + # SUMMARY + # AUTHORS + # URL (JOURNAL) + # date_and_location_column: DATE +locale: + language: english + # last_updated: Last updated in + # month: month + # months: months + # year: year + # years: years + # present: present + # phrases: + # degree_with_area: DEGREE in AREA + # month_abbreviations: + # - Jan + # - Feb + # - Mar + # - Apr + # - May + # - June + # - July + # - Aug + # - Sept + # - Oct + # - Nov + # - Dec + # month_names: + # - January + # - February + # - March + # - April + # - May + # - June + # - July + # - August + # - September + # - October + # - November + # - December +settings: + current_date: today + render_command: + output_folder: rendercv_output + design: + locale: + typst_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.typ + pdf_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.pdf + markdown_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.md + html_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.html + png_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.png + dont_generate_markdown: false + dont_generate_html: false + dont_generate_typst: false + dont_generate_pdf: false + dont_generate_png: false + bold_keywords: [] + pdf_title: NAME - CV diff --git a/examples/John_Doe_InkTheme_CV.pdf b/examples/John_Doe_InkTheme_CV.pdf new file mode 100644 index 000000000..131580b13 Binary files /dev/null and b/examples/John_Doe_InkTheme_CV.pdf differ diff --git a/examples/John_Doe_InkTheme_CV.yaml b/examples/John_Doe_InkTheme_CV.yaml new file mode 100644 index 000000000..a35229a01 --- /dev/null +++ b/examples/John_Doe_InkTheme_CV.yaml @@ -0,0 +1,364 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.8/schema.json +cv: + name: John Doe + headline: + location: San Francisco, CA + email: john.doe@email.com + photo: + phone: + website: https://rendercv.com/ + social_networks: + - network: LinkedIn + username: rendercv + - network: GitHub + username: rendercv + custom_connections: + sections: + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + - Each section title is arbitrary. + - You can choose any of the 9 entry types for each section. + - Markdown syntax is supported everywhere. This is **bold**, *italic*, and [link](https://example.com). + education: + - institution: Princeton University + area: Computer Science + degree: PhD + date: + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ + summary: + highlights: + - 'Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment' + - 'Advisor: Prof. Sanjeev Arora' + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) + - institution: Boğaziçi University + area: Computer Engineering + degree: BS + date: + start_date: 2014-09 + end_date: 2018-06 + location: Istanbul, Türkiye + summary: + highlights: + - 'GPA: 3.97/4.00, Valedictorian' + - Fulbright Scholarship recipient for Graduate Studies + experience: + - company: Nexus AI + position: Co-Founder & CTO + date: + start_date: 2023-06 + end_date: present + location: San Francisco, CA + summary: + highlights: + - Built foundation model infrastructure serving 2M+ monthly API requests with 99.97% uptime + - Raised $18M Series A led by Sequoia Capital, with participation from a16z and Founders Fund + - Scaled engineering team from 3 to 28 across ML research, platform, and applied AI divisions + - Developed proprietary inference optimization reducing latency by 73% compared to baseline + - company: NVIDIA Research + position: Research Intern + date: + start_date: 2022-05 + end_date: 2022-08 + location: Santa Clara, CA + summary: + highlights: + - Designed sparse attention mechanism reducing transformer memory footprint by 4.2x + - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top 5% of submissions) + - company: Google DeepMind + position: Research Intern + date: + start_date: 2021-05 + end_date: 2021-08 + location: London, UK + summary: + highlights: + - Developed reinforcement learning algorithms for multi-agent coordination + - Published research at top-tier venues with significant academic impact + - ICML 2022 main conference paper, cited 340+ times within two years + - NeurIPS 2022 workshop paper on emergent communication protocols + - Invited journal extension in JMLR (2023) + - company: Apple ML Research + position: Research Intern + date: + start_date: 2020-05 + end_date: 2020-08 + location: Cupertino, CA + summary: + highlights: + - Created on-device neural network compression pipeline deployed across 50M+ devices + - Filed 2 patents on efficient model quantization techniques for edge inference + - company: Microsoft Research + position: Research Intern + date: + start_date: 2019-05 + end_date: 2019-08 + location: Redmond, WA + summary: + highlights: + - Implemented novel self-supervised learning framework for low-resource language modeling + - Research integrated into Azure Cognitive Services, reducing training data requirements by 60% + projects: + - name: '[FlashInfer](https://github.com/)' + date: + start_date: 2023-01 + end_date: present + location: + summary: Open-source library for high-performance LLM inference kernels + highlights: + - Achieved 2.8x speedup over baseline attention implementations on A100 GPUs + - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors + - name: '[NeuralPrune](https://github.com/)' + date: '2021' + start_date: + end_date: + location: + summary: Automated neural network pruning toolkit with differentiable masks + highlights: + - Reduced model size by 90% with less than 1% accuracy degradation on ImageNet + - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars + publications: + - title: 'Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter Models' + authors: + - '*John Doe*' + - Sarah Williams + - David Park + summary: + doi: 10.1234/neurips.2023.1234 + url: + journal: NeurIPS 2023 + date: 2023-07 + - title: Neural Architecture Search via Differentiable Pruning + authors: + - James Liu + - '*John Doe*' + summary: + doi: 10.1234/neurips.2022.5678 + url: + journal: NeurIPS 2022, Spotlight + date: 2022-12 + - title: Multi-Agent Reinforcement Learning with Emergent Communication + authors: + - Maria Garcia + - '*John Doe*' + - Tom Anderson + summary: + doi: 10.1234/icml.2022.9012 + url: + journal: ICML 2022 + date: 2022-07 + - title: On-Device Model Compression via Learned Quantization + authors: + - '*John Doe*' + - Kevin Wu + summary: + doi: 10.1234/iclr.2021.3456 + url: + journal: ICLR 2021, Best Paper Award + date: 2021-05 + selected_honors: + - bullet: MIT Technology Review 35 Under 35 Innovators (2024) + - bullet: Forbes 30 Under 30 in Enterprise Technology (2024) + - bullet: ACM Doctoral Dissertation Award Honorable Mention (2023) + - bullet: Google PhD Fellowship in Machine Learning (2020 – 2023) + - bullet: Fulbright Scholarship for Graduate Studies (2018) + skills: + - label: Languages + details: Python, C++, CUDA, Rust, Julia + - label: ML Frameworks + details: PyTorch, JAX, TensorFlow, Triton, ONNX + - label: Infrastructure + details: Kubernetes, Ray, distributed training, AWS, GCP + - label: Research Areas + details: Neural architecture search, model compression, efficient inference, multi-agent RL + patents: + - number: Adaptive Quantization for Neural Network Inference on Edge Devices (US Patent 11,234,567) + - number: Dynamic Sparsity Patterns for Efficient Transformer Attention (US Patent 11,345,678) + - number: Hardware-Aware Neural Architecture Search Method (US Patent 11,456,789) + invited_talks: + - reversed_number: Scaling Laws for Efficient Inference — Stanford HAI Symposium (2024) + - reversed_number: Building AI Infrastructure for the Next Decade — TechCrunch Disrupt (2024) + - reversed_number: 'From Research to Production: Lessons in ML Systems — NeurIPS Workshop (2023)' + - reversed_number: "Efficient Deep Learning: A Practitioner's Perspective — Google Tech Talk (2022)" +design: + theme: ink + # page: + # size: us-letter + # top_margin: 0.6in + # bottom_margin: 0.6in + # left_margin: 0.6in + # right_margin: 0.6in + # show_footer: true + # show_top_note: true + # colors: + # body: rgb(0, 0, 0) + # name: rgb(42, 24, 82) + # headline: rgb(42, 24, 82) + # connections: rgb(70, 50, 110) + # section_titles: rgb(42, 24, 82) + # links: rgb(42, 24, 82) + # footer: rgb(120, 100, 140) + # top_note: rgb(120, 100, 140) + # typography: + # line_spacing: 0.55em + # alignment: justified + # date_and_location_column_alignment: right + # font_family: + # body: EB Garamond + # name: EB Garamond + # headline: EB Garamond + # connections: EB Garamond + # section_titles: EB Garamond + # font_size: + # body: 10pt + # name: 32pt + # headline: 11pt + # connections: 10pt + # section_titles: 1.4em + # small_caps: + # name: false + # headline: false + # connections: false + # section_titles: true + # bold: + # name: true + # headline: false + # connections: false + # section_titles: true + # links: + # underline: true + # show_external_link_icon: false + # header: + # alignment: left + # photo_width: 3.5cm + # photo_position: left + # photo_space_left: 0.4cm + # photo_space_right: 0.4cm + # space_below_name: 0.5cm + # space_below_headline: 0.4cm + # space_below_connections: 0.5cm + # connections: + # phone_number_format: national + # hyperlink: true + # show_icons: false + # display_urls_instead_of_usernames: true + # separator: '|' + # space_between_connections: 0.4cm + # section_titles: + # type: without_line + # line_thickness: 0.5pt + # space_above: 0.5cm + # space_below: 0.2cm + # sections: + # allow_page_break: true + # space_between_regular_entries: 1em + # space_between_text_based_entries: 0.2em + # show_time_spans_in: [] + # entries: + # date_and_location_width: 4.15cm + # side_space: 0cm + # space_between_columns: 0.2cm + # allow_page_break: false + # short_second_row: false + # degree_width: 1cm + # summary: + # space_above: 0.06cm + # space_left: 0cm + # highlights: + # bullet: • + # nested_bullet: • + # space_left: 0cm + # space_above: 0.06cm + # space_between_items: 0.06cm + # space_between_bullet_and_text: 0.4em + # templates: + # footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' + # top_note: '*LAST_UPDATED CURRENT_DATE*' + # single_date: MONTH_ABBREVIATION YEAR + # date_range: START_DATE – END_DATE + # time_span: HOW_MANY_YEARS YEARS HOW_MANY_MONTHS MONTHS + # one_line_entry: + # main_column: '**LABEL:** DETAILS' + # education_entry: + # main_column: |- + # **INSTITUTION** -- LOCATION + # *DEGREE_WITH_AREA* + # SUMMARY + # HIGHLIGHTS + # degree_column: + # date_and_location_column: DATE + # normal_entry: + # main_column: |- + # **NAME** -- **LOCATION** + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # experience_entry: + # main_column: |- + # **COMPANY** -- LOCATION + # *POSITION* + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # publication_entry: + # main_column: |- + # **TITLE** + # SUMMARY + # AUTHORS + # URL (JOURNAL) + # date_and_location_column: DATE +locale: + language: english + # last_updated: Last updated in + # month: month + # months: months + # year: year + # years: years + # present: present + # phrases: + # degree_with_area: DEGREE in AREA + # month_abbreviations: + # - Jan + # - Feb + # - Mar + # - Apr + # - May + # - June + # - July + # - Aug + # - Sept + # - Oct + # - Nov + # - Dec + # month_names: + # - January + # - February + # - March + # - April + # - May + # - June + # - July + # - August + # - September + # - October + # - November + # - December +settings: + current_date: today + render_command: + output_folder: rendercv_output + design: + locale: + typst_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.typ + pdf_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.pdf + markdown_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.md + html_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.html + png_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.png + dont_generate_markdown: false + dont_generate_html: false + dont_generate_typst: false + dont_generate_pdf: false + dont_generate_png: false + bold_keywords: [] + pdf_title: NAME - CV diff --git a/examples/John_Doe_ModerncvTheme_CV.pdf b/examples/John_Doe_ModerncvTheme_CV.pdf index 67816402b..c12594fd6 100644 Binary files a/examples/John_Doe_ModerncvTheme_CV.pdf and b/examples/John_Doe_ModerncvTheme_CV.pdf differ diff --git a/examples/John_Doe_ModerncvTheme_CV.yaml b/examples/John_Doe_ModerncvTheme_CV.yaml index 9b68c5069..6cbac1873 100644 --- a/examples/John_Doe_ModerncvTheme_CV.yaml +++ b/examples/John_Doe_ModerncvTheme_CV.yaml @@ -1,255 +1,362 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.8/schema.json cv: name: John Doe - location: Location - email: john.doe@example.com - phone: +1-609-999-9995 - website: + headline: + location: San Francisco, CA + email: john.doe@email.com + photo: + phone: + website: https://rendercv.com/ social_networks: - network: LinkedIn - username: john.doe + username: rendercv - network: GitHub - username: john.doe + username: rendercv + custom_connections: sections: - welcome_to_RenderCV!: - - '[RenderCV](https://rendercv.com) is a Typst-based CV framework designed for academics and engineers, with Markdown syntax support.' - - Each section title is arbitrary. Each section contains a list of entries, and there are 7 different entry types to choose from. + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + - Each section title is arbitrary. + - You can choose any of the 9 entry types for each section. + - Markdown syntax is supported everywhere. This is **bold**, *italic*, and [link](https://example.com). education: - - institution: Stanford University + - institution: Princeton University area: Computer Science degree: PhD date: - start_date: 2023-09 - end_date: present - location: Stanford, CA, USA + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ summary: highlights: - - Working on the optimization of autonomous vehicles in urban environments + - 'Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment' + - 'Advisor: Prof. Sanjeev Arora' + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) - institution: Boğaziçi University area: Computer Engineering degree: BS date: - start_date: 2018-09 - end_date: 2022-06 + start_date: 2014-09 + end_date: 2018-06 location: Istanbul, Türkiye summary: highlights: - - 'GPA: 3.9/4.0, ranked 1st out of 100 students' - - 'Awards: Best Senior Project, High Honor' + - 'GPA: 3.97/4.00, Valedictorian' + - Fulbright Scholarship recipient for Graduate Studies experience: - - company: Company C - position: Summer Intern + - company: Nexus AI + position: Co-Founder & CTO date: - start_date: 2024-06 - end_date: 2024-09 - location: Livingston, LA, USA + start_date: 2023-06 + end_date: present + location: San Francisco, CA summary: highlights: - - Developed deep learning models for the detection of gravitational waves in LIGO data - - Published [3 peer-reviewed research papers](https://example.com) about the project and results - - company: Company B - position: Summer Intern + - Built foundation model infrastructure serving 2M+ monthly API requests with 99.97% uptime + - Raised $18M Series A led by Sequoia Capital, with participation from a16z and Founders Fund + - Scaled engineering team from 3 to 28 across ML research, platform, and applied AI divisions + - Developed proprietary inference optimization reducing latency by 73% compared to baseline + - company: NVIDIA Research + position: Research Intern date: - start_date: 2023-06 - end_date: 2023-09 - location: Ankara, Türkiye + start_date: 2022-05 + end_date: 2022-08 + location: Santa Clara, CA summary: highlights: - - Optimized the production line by 15% by implementing a new scheduling algorithm - - company: Company A - position: Summer Intern + - Designed sparse attention mechanism reducing transformer memory footprint by 4.2x + - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top 5% of submissions) + - company: Google DeepMind + position: Research Intern date: - start_date: 2022-06 - end_date: 2022-09 - location: Istanbul, Türkiye + start_date: 2021-05 + end_date: 2021-08 + location: London, UK summary: highlights: - - Designed an inventory management web application for a warehouse + - Developed reinforcement learning algorithms for multi-agent coordination + - Published research at top-tier venues with significant academic impact + - ICML 2022 main conference paper, cited 340+ times within two years + - NeurIPS 2022 workshop paper on emergent communication protocols + - Invited journal extension in JMLR (2023) + - company: Apple ML Research + position: Research Intern + date: + start_date: 2020-05 + end_date: 2020-08 + location: Cupertino, CA + summary: + highlights: + - Created on-device neural network compression pipeline deployed across 50M+ devices + - Filed 2 patents on efficient model quantization techniques for edge inference + - company: Microsoft Research + position: Research Intern + date: + start_date: 2019-05 + end_date: 2019-08 + location: Redmond, WA + summary: + highlights: + - Implemented novel self-supervised learning framework for low-resource language modeling + - Research integrated into Azure Cognitive Services, reducing training data requirements by 60% projects: - - name: '[Example Project](https://example.com)' + - name: '[FlashInfer](https://github.com/)' date: - start_date: 2024-05 + start_date: 2023-01 end_date: present location: - summary: A web application for writing essays + summary: Open-source library for high-performance LLM inference kernels highlights: - - Launched an [iOS app](https://example.com) in 09/2024 that currently has 10k+ monthly active users - - The app is made open-source (3,000+ stars [on GitHub](https://github.com)) - - name: '[Teaching on Udemy](https://example.com)' - date: Fall 2023 + - Achieved 2.8x speedup over baseline attention implementations on A100 GPUs + - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors + - name: '[NeuralPrune](https://github.com/)' + date: '2021' start_date: end_date: location: - summary: + summary: Automated neural network pruning toolkit with differentiable masks highlights: - - Instructed the "Statistics" course on Udemy (60,000+ students, 200,000+ hours watched) - skills: - - label: Programming - details: Proficient with Python, C++, and Git; good understanding of Web, app development, and DevOps - - label: Mathematics - details: Good understanding of differential equations, calculus, and linear algebra - - label: Languages - details: 'English (fluent, TOEFL: 118/120), Turkish (native)' + - Reduced model size by 90% with less than 1% accuracy degradation on ImageNet + - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars publications: - - title: 3D Finite Element Analysis of No-Insulation Coils + - title: 'Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter Models' + authors: + - '*John Doe*' + - Sarah Williams + - David Park + summary: + doi: 10.1234/neurips.2023.1234 + url: + journal: NeurIPS 2023 + date: 2023-07 + - title: Neural Architecture Search via Differentiable Pruning + authors: + - James Liu + - '*John Doe*' + summary: + doi: 10.1234/neurips.2022.5678 + url: + journal: NeurIPS 2022, Spotlight + date: 2022-12 + - title: Multi-Agent Reinforcement Learning with Emergent Communication authors: - - Frodo Baggins - - '***John Doe***' - - Samwise Gamgee - doi: 10.1109/TASC.2023.3340648 + - Maria Garcia + - '*John Doe*' + - Tom Anderson + summary: + doi: 10.1234/icml.2022.9012 url: - journal: - date: 2004-01 - extracurricular_activities: - - bullet: 'There are 7 unique entry types in RenderCV: *BulletEntry*, *TextEntry*, *EducationEntry*, *ExperienceEntry*, *NormalEntry*, *PublicationEntry*, and *OneLineEntry*.' - - bullet: Each entry type has a different structure and layout. This document demonstrates all of them. - numbered_entries: - - number: This is a numbered entry. - - number: This is another numbered entry. - - number: This is the third numbered entry. - reversed_numbered_entries: - - reversed_number: This is a reversed numbered entry. - - reversed_number: This is another reversed numbered entry. - - reversed_number: This is the third reversed numbered entry. + journal: ICML 2022 + date: 2022-07 + - title: On-Device Model Compression via Learned Quantization + authors: + - '*John Doe*' + - Kevin Wu + summary: + doi: 10.1234/iclr.2021.3456 + url: + journal: ICLR 2021, Best Paper Award + date: 2021-05 + selected_honors: + - bullet: MIT Technology Review 35 Under 35 Innovators (2024) + - bullet: Forbes 30 Under 30 in Enterprise Technology (2024) + - bullet: ACM Doctoral Dissertation Award Honorable Mention (2023) + - bullet: Google PhD Fellowship in Machine Learning (2020 – 2023) + - bullet: Fulbright Scholarship for Graduate Studies (2018) + skills: + - label: Languages + details: Python, C++, CUDA, Rust, Julia + - label: ML Frameworks + details: PyTorch, JAX, TensorFlow, Triton, ONNX + - label: Infrastructure + details: Kubernetes, Ray, distributed training, AWS, GCP + - label: Research Areas + details: Neural architecture search, model compression, efficient inference, multi-agent RL + patents: + - number: Adaptive Quantization for Neural Network Inference on Edge Devices (US Patent 11,234,567) + - number: Dynamic Sparsity Patterns for Efficient Transformer Attention (US Patent 11,345,678) + - number: Hardware-Aware Neural Architecture Search Method (US Patent 11,456,789) + invited_talks: + - reversed_number: Scaling Laws for Efficient Inference — Stanford HAI Symposium (2024) + - reversed_number: Building AI Infrastructure for the Next Decade — TechCrunch Disrupt (2024) + - reversed_number: 'From Research to Production: Lessons in ML Systems — NeurIPS Workshop (2023)' + - reversed_number: "Efficient Deep Learning: A Practitioner's Perspective — Google Tech Talk (2022)" design: theme: moderncv - page: - size: us-letter - top_margin: 2cm - bottom_margin: 2cm - left_margin: 2cm - right_margin: 2cm - show_page_numbering: true - show_last_updated_date: true - colors: - text: rgb(0, 0, 0) - name: rgb(0, 79, 144) - connections: rgb(0, 79, 144) - section_titles: rgb(0, 79, 144) - links: rgb(0, 79, 144) - last_updated_date_and_page_numbering: rgb(128, 128, 128) - text: - font_family: Fontin - font_size: 10pt - leading: 0.6em - alignment: justified - date_and_location_column_alignment: right - links: - underline: true - use_external_link_icon: false - header: - name_font_family: Fontin - name_font_size: 25pt - name_bold: false - small_caps_for_name: false - photo_width: 3.5cm - vertical_space_between_name_and_connections: 0.7cm - vertical_space_between_connections_and_first_section: 0.7cm - horizontal_space_between_connections: 0.5cm - connections_font_family: Fontin - separator_between_connections: '' - use_icons_for_connections: true - use_urls_as_placeholders_for_connections: false - make_connections_links: true - alignment: left - section_titles: - type: moderncv - font_family: Fontin - font_size: 1.4em - bold: false - small_caps: false - line_thickness: 0.15cm - vertical_space_above: 0.55cm - vertical_space_below: 0.3cm - entries: - date_and_location_width: 4.15cm - left_and_right_margin: 0cm - horizontal_space_between_columns: 0.4cm - vertical_space_between_entries: 0.4cm - allow_page_break_in_sections: true - allow_page_break_in_entries: true - short_second_row: false - show_time_spans_in: [] - highlights: - bullet: • - nested_bullet: '-' - top_margin: 0.25cm - left_margin: 0cm - vertical_space_between_highlights: 0.19cm - horizontal_space_between_bullet_and_highlight: 0.3em - summary_left_margin: 0cm - entry_types: - one_line_entry: - template: '**LABEL:** DETAILS' - education_entry: - main_column_first_row_template: '**INSTITUTION**, DEGREE in AREA -- LOCATION' - degree_column_template: - degree_column_width: 1cm - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: DATE - normal_entry: - main_column_first_row_template: '**NAME** -- **LOCATION**' - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: DATE - experience_entry: - main_column_first_row_template: '**POSITION**, COMPANY -- LOCATION' - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: DATE - publication_entry: - main_column_first_row_template: '**TITLE**' - main_column_second_row_template: |- - AUTHORS - URL (JOURNAL) - main_column_second_row_without_journal_template: |- - AUTHORS - URL - main_column_second_row_without_url_template: |- - AUTHORS - JOURNAL - date_and_location_column_template: DATE + # page: + # size: us-letter + # top_margin: 0.7in + # bottom_margin: 0.7in + # left_margin: 0.7in + # right_margin: 0.7in + # show_footer: true + # show_top_note: true + # colors: + # body: rgb(0, 0, 0) + # name: rgb(0, 79, 144) + # headline: rgb(0, 79, 144) + # connections: rgb(0, 79, 144) + # section_titles: rgb(0, 79, 144) + # links: rgb(0, 79, 144) + # footer: rgb(128, 128, 128) + # top_note: rgb(128, 128, 128) + # typography: + # line_spacing: 0.6em + # alignment: justified + # date_and_location_column_alignment: right + # font_family: + # body: Fontin + # name: Fontin + # headline: Fontin + # connections: Fontin + # section_titles: Fontin + # font_size: + # body: 10pt + # name: 25pt + # headline: 10pt + # connections: 10pt + # section_titles: 1.4em + # small_caps: + # name: false + # headline: false + # connections: false + # section_titles: false + # bold: + # name: false + # headline: false + # connections: false + # section_titles: false + # links: + # underline: true + # show_external_link_icon: false + # header: + # alignment: left + # photo_width: 4.15cm + # photo_position: left + # photo_space_left: 0cm + # photo_space_right: 0.3cm + # space_below_name: 0.7cm + # space_below_headline: 0.7cm + # space_below_connections: 0.7cm + # connections: + # phone_number_format: national + # hyperlink: true + # show_icons: true + # display_urls_instead_of_usernames: false + # separator: '' + # space_between_connections: 0.5cm + # section_titles: + # type: moderncv + # line_thickness: 0.15cm + # space_above: 0.55cm + # space_below: 0.3cm + # sections: + # allow_page_break: true + # space_between_regular_entries: 1.2em + # space_between_text_based_entries: 0.3em + # show_time_spans_in: [] + # entries: + # date_and_location_width: 4.15cm + # side_space: 0cm + # space_between_columns: 0.3cm + # allow_page_break: false + # short_second_row: false + # degree_width: 1cm + # summary: + # space_above: 0.1cm + # space_left: 0cm + # highlights: + # bullet: • + # nested_bullet: • + # space_left: 0cm + # space_above: 0.15cm + # space_between_items: 0.1cm + # space_between_bullet_and_text: 0.3em + # templates: + # footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' + # top_note: '*LAST_UPDATED CURRENT_DATE*' + # single_date: MONTH_ABBREVIATION YEAR + # date_range: START_DATE – END_DATE + # time_span: HOW_MANY_YEARS YEARS HOW_MANY_MONTHS MONTHS + # one_line_entry: + # main_column: '**LABEL:** DETAILS' + # education_entry: + # main_column: |- + # **INSTITUTION**, DEGREE_WITH_AREA -- LOCATION + # SUMMARY + # HIGHLIGHTS + # degree_column: + # date_and_location_column: DATE + # normal_entry: + # main_column: |- + # **NAME** -- **LOCATION** + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # experience_entry: + # main_column: |- + # **POSITION**, COMPANY -- LOCATION + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # publication_entry: + # main_column: |- + # **TITLE** + # SUMMARY + # AUTHORS + # URL (JOURNAL) + # date_and_location_column: DATE locale: - language: en - phone_number_format: national - page_numbering_template: NAME - Page PAGE_NUMBER of TOTAL_PAGES - last_updated_date_template: Last updated in TODAY - date_template: MONTH_ABBREVIATION YEAR - month: month - months: months - year: year - years: years - present: present - to: – - abbreviations_for_months: - - Jan - - Feb - - Mar - - Apr - - May - - June - - July - - Aug - - Sept - - Oct - - Nov - - Dec - full_names_of_months: - - January - - February - - March - - April - - May - - June - - July - - August - - September - - October - - November - - December -rendercv_settings: - date: '2025-03-01' + language: english + # last_updated: Last updated in + # month: month + # months: months + # year: year + # years: years + # present: present + # phrases: + # degree_with_area: DEGREE in AREA + # month_abbreviations: + # - Jan + # - Feb + # - Mar + # - Apr + # - May + # - June + # - July + # - Aug + # - Sept + # - Oct + # - Nov + # - Dec + # month_names: + # - January + # - February + # - March + # - April + # - May + # - June + # - July + # - August + # - September + # - October + # - November + # - December +settings: + current_date: today + render_command: + output_folder: rendercv_output + design: + locale: + typst_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.typ + pdf_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.pdf + markdown_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.md + html_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.html + png_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.png + dont_generate_markdown: false + dont_generate_html: false + dont_generate_typst: false + dont_generate_pdf: false + dont_generate_png: false bold_keywords: [] + pdf_title: NAME - CV diff --git a/examples/John_Doe_OpalTheme_CV.pdf b/examples/John_Doe_OpalTheme_CV.pdf new file mode 100644 index 000000000..127886981 Binary files /dev/null and b/examples/John_Doe_OpalTheme_CV.pdf differ diff --git a/examples/John_Doe_OpalTheme_CV.yaml b/examples/John_Doe_OpalTheme_CV.yaml new file mode 100644 index 000000000..d661a1e75 --- /dev/null +++ b/examples/John_Doe_OpalTheme_CV.yaml @@ -0,0 +1,363 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.8/schema.json +cv: + name: John Doe + headline: + location: San Francisco, CA + email: john.doe@email.com + photo: + phone: + website: https://rendercv.com/ + social_networks: + - network: LinkedIn + username: rendercv + - network: GitHub + username: rendercv + custom_connections: + sections: + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + - Each section title is arbitrary. + - You can choose any of the 9 entry types for each section. + - Markdown syntax is supported everywhere. This is **bold**, *italic*, and [link](https://example.com). + education: + - institution: Princeton University + area: Computer Science + degree: PhD + date: + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ + summary: + highlights: + - 'Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment' + - 'Advisor: Prof. Sanjeev Arora' + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) + - institution: Boğaziçi University + area: Computer Engineering + degree: BS + date: + start_date: 2014-09 + end_date: 2018-06 + location: Istanbul, Türkiye + summary: + highlights: + - 'GPA: 3.97/4.00, Valedictorian' + - Fulbright Scholarship recipient for Graduate Studies + experience: + - company: Nexus AI + position: Co-Founder & CTO + date: + start_date: 2023-06 + end_date: present + location: San Francisco, CA + summary: + highlights: + - Built foundation model infrastructure serving 2M+ monthly API requests with 99.97% uptime + - Raised $18M Series A led by Sequoia Capital, with participation from a16z and Founders Fund + - Scaled engineering team from 3 to 28 across ML research, platform, and applied AI divisions + - Developed proprietary inference optimization reducing latency by 73% compared to baseline + - company: NVIDIA Research + position: Research Intern + date: + start_date: 2022-05 + end_date: 2022-08 + location: Santa Clara, CA + summary: + highlights: + - Designed sparse attention mechanism reducing transformer memory footprint by 4.2x + - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top 5% of submissions) + - company: Google DeepMind + position: Research Intern + date: + start_date: 2021-05 + end_date: 2021-08 + location: London, UK + summary: + highlights: + - Developed reinforcement learning algorithms for multi-agent coordination + - Published research at top-tier venues with significant academic impact + - ICML 2022 main conference paper, cited 340+ times within two years + - NeurIPS 2022 workshop paper on emergent communication protocols + - Invited journal extension in JMLR (2023) + - company: Apple ML Research + position: Research Intern + date: + start_date: 2020-05 + end_date: 2020-08 + location: Cupertino, CA + summary: + highlights: + - Created on-device neural network compression pipeline deployed across 50M+ devices + - Filed 2 patents on efficient model quantization techniques for edge inference + - company: Microsoft Research + position: Research Intern + date: + start_date: 2019-05 + end_date: 2019-08 + location: Redmond, WA + summary: + highlights: + - Implemented novel self-supervised learning framework for low-resource language modeling + - Research integrated into Azure Cognitive Services, reducing training data requirements by 60% + projects: + - name: '[FlashInfer](https://github.com/)' + date: + start_date: 2023-01 + end_date: present + location: + summary: Open-source library for high-performance LLM inference kernels + highlights: + - Achieved 2.8x speedup over baseline attention implementations on A100 GPUs + - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors + - name: '[NeuralPrune](https://github.com/)' + date: '2021' + start_date: + end_date: + location: + summary: Automated neural network pruning toolkit with differentiable masks + highlights: + - Reduced model size by 90% with less than 1% accuracy degradation on ImageNet + - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars + publications: + - title: 'Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter Models' + authors: + - '*John Doe*' + - Sarah Williams + - David Park + summary: + doi: 10.1234/neurips.2023.1234 + url: + journal: NeurIPS 2023 + date: 2023-07 + - title: Neural Architecture Search via Differentiable Pruning + authors: + - James Liu + - '*John Doe*' + summary: + doi: 10.1234/neurips.2022.5678 + url: + journal: NeurIPS 2022, Spotlight + date: 2022-12 + - title: Multi-Agent Reinforcement Learning with Emergent Communication + authors: + - Maria Garcia + - '*John Doe*' + - Tom Anderson + summary: + doi: 10.1234/icml.2022.9012 + url: + journal: ICML 2022 + date: 2022-07 + - title: On-Device Model Compression via Learned Quantization + authors: + - '*John Doe*' + - Kevin Wu + summary: + doi: 10.1234/iclr.2021.3456 + url: + journal: ICLR 2021, Best Paper Award + date: 2021-05 + selected_honors: + - bullet: MIT Technology Review 35 Under 35 Innovators (2024) + - bullet: Forbes 30 Under 30 in Enterprise Technology (2024) + - bullet: ACM Doctoral Dissertation Award Honorable Mention (2023) + - bullet: Google PhD Fellowship in Machine Learning (2020 – 2023) + - bullet: Fulbright Scholarship for Graduate Studies (2018) + skills: + - label: Languages + details: Python, C++, CUDA, Rust, Julia + - label: ML Frameworks + details: PyTorch, JAX, TensorFlow, Triton, ONNX + - label: Infrastructure + details: Kubernetes, Ray, distributed training, AWS, GCP + - label: Research Areas + details: Neural architecture search, model compression, efficient inference, multi-agent RL + patents: + - number: Adaptive Quantization for Neural Network Inference on Edge Devices (US Patent 11,234,567) + - number: Dynamic Sparsity Patterns for Efficient Transformer Attention (US Patent 11,345,678) + - number: Hardware-Aware Neural Architecture Search Method (US Patent 11,456,789) + invited_talks: + - reversed_number: Scaling Laws for Efficient Inference — Stanford HAI Symposium (2024) + - reversed_number: Building AI Infrastructure for the Next Decade — TechCrunch Disrupt (2024) + - reversed_number: 'From Research to Production: Lessons in ML Systems — NeurIPS Workshop (2023)' + - reversed_number: "Efficient Deep Learning: A Practitioner's Perspective — Google Tech Talk (2022)" +design: + theme: opal + # page: + # size: us-letter + # top_margin: 0.65in + # bottom_margin: 0.65in + # left_margin: 0.65in + # right_margin: 0.65in + # show_footer: false + # show_top_note: false + # colors: + # body: rgb(0, 0, 0) + # name: rgb(0, 100, 90) + # headline: rgb(0, 80, 72) + # connections: rgb(0, 80, 72) + # section_titles: rgb(0, 100, 90) + # links: rgb(0, 100, 90) + # footer: rgb(100, 140, 135) + # top_note: rgb(100, 140, 135) + # typography: + # line_spacing: 0.6em + # alignment: left + # date_and_location_column_alignment: right + # font_family: + # body: Lato + # name: Lato + # headline: Lato + # connections: Lato + # section_titles: Lato + # font_size: + # body: 10pt + # name: 26pt + # headline: 10pt + # connections: 9pt + # section_titles: 1.2em + # small_caps: + # name: false + # headline: true + # connections: false + # section_titles: true + # bold: + # name: true + # headline: false + # connections: false + # section_titles: false + # links: + # underline: false + # show_external_link_icon: false + # header: + # alignment: center + # photo_width: 3.5cm + # photo_position: left + # photo_space_left: 0.4cm + # photo_space_right: 0.4cm + # space_below_name: 0.3cm + # space_below_headline: 0.3cm + # space_below_connections: 0.6cm + # connections: + # phone_number_format: national + # hyperlink: true + # show_icons: true + # display_urls_instead_of_usernames: false + # separator: • + # space_between_connections: 0.5cm + # section_titles: + # type: centered_without_line + # line_thickness: 0.4pt + # space_above: 0.55cm + # space_below: 0.25cm + # sections: + # allow_page_break: true + # space_between_regular_entries: 1.1em + # space_between_text_based_entries: 0.3em + # show_time_spans_in: [] + # entries: + # date_and_location_width: 4.15cm + # side_space: 0.15cm + # space_between_columns: 0.1cm + # allow_page_break: false + # short_second_row: true + # degree_width: 1cm + # summary: + # space_above: 0.04cm + # space_left: 0cm + # highlights: + # bullet: ◦ + # nested_bullet: ◦ + # space_left: 0.15cm + # space_above: 0.04cm + # space_between_items: 0.04cm + # space_between_bullet_and_text: 0.5em + # templates: + # footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' + # top_note: '*LAST_UPDATED CURRENT_DATE*' + # single_date: MONTH_ABBREVIATION YEAR + # date_range: START_DATE – END_DATE + # time_span: HOW_MANY_YEARS YEARS HOW_MANY_MONTHS MONTHS + # one_line_entry: + # main_column: '**LABEL:** DETAILS' + # education_entry: + # main_column: |- + # **INSTITUTION** -- LOCATION + # DEGREE_WITH_AREA + # SUMMARY + # HIGHLIGHTS + # degree_column: + # date_and_location_column: DATE + # normal_entry: + # main_column: |- + # **NAME** -- LOCATION + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # experience_entry: + # main_column: |- + # **COMPANY**, *POSITION* -- LOCATION + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: DATE + # publication_entry: + # main_column: |- + # **TITLE** + # SUMMARY + # AUTHORS + # URL (JOURNAL) + # date_and_location_column: DATE +locale: + language: english + # last_updated: Last updated in + # month: month + # months: months + # year: year + # years: years + # present: present + # phrases: + # degree_with_area: DEGREE in AREA + # month_abbreviations: + # - Jan + # - Feb + # - Mar + # - Apr + # - May + # - June + # - July + # - Aug + # - Sept + # - Oct + # - Nov + # - Dec + # month_names: + # - January + # - February + # - March + # - April + # - May + # - June + # - July + # - August + # - September + # - October + # - November + # - December +settings: + current_date: today + render_command: + output_folder: rendercv_output + design: + locale: + typst_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.typ + pdf_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.pdf + markdown_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.md + html_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.html + png_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.png + dont_generate_markdown: false + dont_generate_html: false + dont_generate_typst: false + dont_generate_pdf: false + dont_generate_png: false + bold_keywords: [] + pdf_title: NAME - CV diff --git a/examples/John_Doe_Sb2novTheme_CV.pdf b/examples/John_Doe_Sb2novTheme_CV.pdf index 4d902a4f1..f5c22ab83 100644 Binary files a/examples/John_Doe_Sb2novTheme_CV.pdf and b/examples/John_Doe_Sb2novTheme_CV.pdf differ diff --git a/examples/John_Doe_Sb2novTheme_CV.yaml b/examples/John_Doe_Sb2novTheme_CV.yaml index e15dc2ec5..d13263c99 100644 --- a/examples/John_Doe_Sb2novTheme_CV.yaml +++ b/examples/John_Doe_Sb2novTheme_CV.yaml @@ -1,265 +1,370 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.8/schema.json cv: name: John Doe - location: Location - email: john.doe@example.com - phone: +1-609-999-9995 - website: + headline: + location: San Francisco, CA + email: john.doe@email.com + photo: + phone: + website: https://rendercv.com/ social_networks: - network: LinkedIn - username: john.doe + username: rendercv - network: GitHub - username: john.doe + username: rendercv + custom_connections: sections: - welcome_to_RenderCV!: - - '[RenderCV](https://rendercv.com) is a Typst-based CV framework designed for academics and engineers, with Markdown syntax support.' - - Each section title is arbitrary. Each section contains a list of entries, and there are 7 different entry types to choose from. + Welcome to RenderCV: + - RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + - Each section title is arbitrary. + - You can choose any of the 9 entry types for each section. + - Markdown syntax is supported everywhere. This is **bold**, *italic*, and [link](https://example.com). education: - - institution: Stanford University + - institution: Princeton University area: Computer Science degree: PhD date: - start_date: 2023-09 - end_date: present - location: Stanford, CA, USA + start_date: 2018-09 + end_date: 2023-05 + location: Princeton, NJ summary: highlights: - - Working on the optimization of autonomous vehicles in urban environments + - 'Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment' + - 'Advisor: Prof. Sanjeev Arora' + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) - institution: Boğaziçi University area: Computer Engineering degree: BS date: - start_date: 2018-09 - end_date: 2022-06 + start_date: 2014-09 + end_date: 2018-06 location: Istanbul, Türkiye summary: highlights: - - 'GPA: 3.9/4.0, ranked 1st out of 100 students' - - 'Awards: Best Senior Project, High Honor' + - 'GPA: 3.97/4.00, Valedictorian' + - Fulbright Scholarship recipient for Graduate Studies experience: - - company: Company C - position: Summer Intern + - company: Nexus AI + position: Co-Founder & CTO date: - start_date: 2024-06 - end_date: 2024-09 - location: Livingston, LA, USA + start_date: 2023-06 + end_date: present + location: San Francisco, CA summary: highlights: - - Developed deep learning models for the detection of gravitational waves in LIGO data - - Published [3 peer-reviewed research papers](https://example.com) about the project and results - - company: Company B - position: Summer Intern + - Built foundation model infrastructure serving 2M+ monthly API requests with 99.97% uptime + - Raised $18M Series A led by Sequoia Capital, with participation from a16z and Founders Fund + - Scaled engineering team from 3 to 28 across ML research, platform, and applied AI divisions + - Developed proprietary inference optimization reducing latency by 73% compared to baseline + - company: NVIDIA Research + position: Research Intern date: - start_date: 2023-06 - end_date: 2023-09 - location: Ankara, Türkiye + start_date: 2022-05 + end_date: 2022-08 + location: Santa Clara, CA summary: highlights: - - Optimized the production line by 15% by implementing a new scheduling algorithm - - company: Company A - position: Summer Intern + - Designed sparse attention mechanism reducing transformer memory footprint by 4.2x + - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top 5% of submissions) + - company: Google DeepMind + position: Research Intern date: - start_date: 2022-06 - end_date: 2022-09 - location: Istanbul, Türkiye + start_date: 2021-05 + end_date: 2021-08 + location: London, UK summary: highlights: - - Designed an inventory management web application for a warehouse + - Developed reinforcement learning algorithms for multi-agent coordination + - Published research at top-tier venues with significant academic impact + - ICML 2022 main conference paper, cited 340+ times within two years + - NeurIPS 2022 workshop paper on emergent communication protocols + - Invited journal extension in JMLR (2023) + - company: Apple ML Research + position: Research Intern + date: + start_date: 2020-05 + end_date: 2020-08 + location: Cupertino, CA + summary: + highlights: + - Created on-device neural network compression pipeline deployed across 50M+ devices + - Filed 2 patents on efficient model quantization techniques for edge inference + - company: Microsoft Research + position: Research Intern + date: + start_date: 2019-05 + end_date: 2019-08 + location: Redmond, WA + summary: + highlights: + - Implemented novel self-supervised learning framework for low-resource language modeling + - Research integrated into Azure Cognitive Services, reducing training data requirements by 60% projects: - - name: '[Example Project](https://example.com)' + - name: '[FlashInfer](https://github.com/)' date: - start_date: 2024-05 + start_date: 2023-01 end_date: present location: - summary: A web application for writing essays + summary: Open-source library for high-performance LLM inference kernels highlights: - - Launched an [iOS app](https://example.com) in 09/2024 that currently has 10k+ monthly active users - - The app is made open-source (3,000+ stars [on GitHub](https://github.com)) - - name: '[Teaching on Udemy](https://example.com)' - date: Fall 2023 + - Achieved 2.8x speedup over baseline attention implementations on A100 GPUs + - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors + - name: '[NeuralPrune](https://github.com/)' + date: '2021' start_date: end_date: location: - summary: + summary: Automated neural network pruning toolkit with differentiable masks highlights: - - Instructed the "Statistics" course on Udemy (60,000+ students, 200,000+ hours watched) - skills: - - label: Programming - details: Proficient with Python, C++, and Git; good understanding of Web, app development, and DevOps - - label: Mathematics - details: Good understanding of differential equations, calculus, and linear algebra - - label: Languages - details: 'English (fluent, TOEFL: 118/120), Turkish (native)' + - Reduced model size by 90% with less than 1% accuracy degradation on ImageNet + - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars publications: - - title: 3D Finite Element Analysis of No-Insulation Coils + - title: 'Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter Models' + authors: + - '*John Doe*' + - Sarah Williams + - David Park + summary: + doi: 10.1234/neurips.2023.1234 + url: + journal: NeurIPS 2023 + date: 2023-07 + - title: Neural Architecture Search via Differentiable Pruning + authors: + - James Liu + - '*John Doe*' + summary: + doi: 10.1234/neurips.2022.5678 + url: + journal: NeurIPS 2022, Spotlight + date: 2022-12 + - title: Multi-Agent Reinforcement Learning with Emergent Communication authors: - - Frodo Baggins - - '***John Doe***' - - Samwise Gamgee - doi: 10.1109/TASC.2023.3340648 + - Maria Garcia + - '*John Doe*' + - Tom Anderson + summary: + doi: 10.1234/icml.2022.9012 url: - journal: - date: 2004-01 - extracurricular_activities: - - bullet: 'There are 7 unique entry types in RenderCV: *BulletEntry*, *TextEntry*, *EducationEntry*, *ExperienceEntry*, *NormalEntry*, *PublicationEntry*, and *OneLineEntry*.' - - bullet: Each entry type has a different structure and layout. This document demonstrates all of them. - numbered_entries: - - number: This is a numbered entry. - - number: This is another numbered entry. - - number: This is the third numbered entry. - reversed_numbered_entries: - - reversed_number: This is a reversed numbered entry. - - reversed_number: This is another reversed numbered entry. - - reversed_number: This is the third reversed numbered entry. + journal: ICML 2022 + date: 2022-07 + - title: On-Device Model Compression via Learned Quantization + authors: + - '*John Doe*' + - Kevin Wu + summary: + doi: 10.1234/iclr.2021.3456 + url: + journal: ICLR 2021, Best Paper Award + date: 2021-05 + selected_honors: + - bullet: MIT Technology Review 35 Under 35 Innovators (2024) + - bullet: Forbes 30 Under 30 in Enterprise Technology (2024) + - bullet: ACM Doctoral Dissertation Award Honorable Mention (2023) + - bullet: Google PhD Fellowship in Machine Learning (2020 – 2023) + - bullet: Fulbright Scholarship for Graduate Studies (2018) + skills: + - label: Languages + details: Python, C++, CUDA, Rust, Julia + - label: ML Frameworks + details: PyTorch, JAX, TensorFlow, Triton, ONNX + - label: Infrastructure + details: Kubernetes, Ray, distributed training, AWS, GCP + - label: Research Areas + details: Neural architecture search, model compression, efficient inference, multi-agent RL + patents: + - number: Adaptive Quantization for Neural Network Inference on Edge Devices (US Patent 11,234,567) + - number: Dynamic Sparsity Patterns for Efficient Transformer Attention (US Patent 11,345,678) + - number: Hardware-Aware Neural Architecture Search Method (US Patent 11,456,789) + invited_talks: + - reversed_number: Scaling Laws for Efficient Inference — Stanford HAI Symposium (2024) + - reversed_number: Building AI Infrastructure for the Next Decade — TechCrunch Disrupt (2024) + - reversed_number: 'From Research to Production: Lessons in ML Systems — NeurIPS Workshop (2023)' + - reversed_number: "Efficient Deep Learning: A Practitioner's Perspective — Google Tech Talk (2022)" design: theme: sb2nov - page: - size: us-letter - top_margin: 2cm - bottom_margin: 2cm - left_margin: 2cm - right_margin: 2cm - show_page_numbering: true - show_last_updated_date: true - colors: - text: rgb(0, 0, 0) - name: rgb(0, 0, 0) - connections: rgb(0, 0, 0) - section_titles: rgb(0, 0, 0) - links: rgb(0, 79, 144) - last_updated_date_and_page_numbering: rgb(128, 128, 128) - text: - font_family: New Computer Modern - font_size: 10pt - leading: 0.6em - alignment: justified - date_and_location_column_alignment: right - links: - underline: true - use_external_link_icon: false - header: - name_font_family: New Computer Modern - name_font_size: 30pt - name_bold: true - small_caps_for_name: false - photo_width: 3.5cm - vertical_space_between_name_and_connections: 0.7cm - vertical_space_between_connections_and_first_section: 0.7cm - horizontal_space_between_connections: 0.5cm - connections_font_family: New Computer Modern - separator_between_connections: '' - use_icons_for_connections: true - use_urls_as_placeholders_for_connections: false - make_connections_links: true - alignment: center - section_titles: - type: with-full-line - font_family: New Computer Modern - font_size: 1.4em - bold: true - small_caps: false - line_thickness: 0.5pt - vertical_space_above: 0.5cm - vertical_space_below: 0.3cm - entries: - date_and_location_width: 4.15cm - left_and_right_margin: 0.2cm - horizontal_space_between_columns: 0.1cm - vertical_space_between_entries: 1.2em - allow_page_break_in_sections: true - allow_page_break_in_entries: true - short_second_row: false - show_time_spans_in: [] - highlights: - bullet: ◦ - nested_bullet: '-' - top_margin: 0.25cm - left_margin: 0.4cm - vertical_space_between_highlights: 0.25cm - horizontal_space_between_bullet_and_highlight: 0.5em - summary_left_margin: 0cm - entry_types: - one_line_entry: - template: '**LABEL:** DETAILS' - education_entry: - main_column_first_row_template: |- - **INSTITUTION** - *DEGREE in AREA* - degree_column_template: - degree_column_width: 1cm - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: |- - *LOCATION* - *DATE* - normal_entry: - main_column_first_row_template: '**NAME**' - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: |- - *LOCATION* - *DATE* - experience_entry: - main_column_first_row_template: |- - **POSITION** - *COMPANY* - main_column_second_row_template: |- - SUMMARY - HIGHLIGHTS - date_and_location_column_template: |- - *LOCATION* - *DATE* - publication_entry: - main_column_first_row_template: '**TITLE**' - main_column_second_row_template: |- - AUTHORS - URL (JOURNAL) - main_column_second_row_without_journal_template: |- - AUTHORS - URL - main_column_second_row_without_url_template: |- - AUTHORS - JOURNAL - date_and_location_column_template: DATE + # page: + # size: us-letter + # top_margin: 0.7in + # bottom_margin: 0.7in + # left_margin: 0.7in + # right_margin: 0.7in + # show_footer: true + # show_top_note: true + # colors: + # body: rgb(0, 0, 0) + # name: rgb(0, 0, 0) + # headline: rgb(0, 0, 0) + # connections: rgb(0, 0, 0) + # section_titles: rgb(0, 0, 0) + # links: rgb(0, 0, 0) + # footer: rgb(128, 128, 128) + # top_note: rgb(128, 128, 128) + # typography: + # line_spacing: 0.6em + # alignment: justified + # date_and_location_column_alignment: right + # font_family: + # body: New Computer Modern + # name: New Computer Modern + # headline: New Computer Modern + # connections: New Computer Modern + # section_titles: New Computer Modern + # font_size: + # body: 10pt + # name: 30pt + # headline: 10pt + # connections: 10pt + # section_titles: 1.4em + # small_caps: + # name: false + # headline: false + # connections: false + # section_titles: false + # bold: + # name: true + # headline: false + # connections: false + # section_titles: true + # links: + # underline: true + # show_external_link_icon: false + # header: + # alignment: center + # photo_width: 3.5cm + # photo_position: left + # photo_space_left: 0.4cm + # photo_space_right: 0.4cm + # space_below_name: 0.7cm + # space_below_headline: 0.7cm + # space_below_connections: 0.7cm + # connections: + # phone_number_format: national + # hyperlink: true + # show_icons: false + # display_urls_instead_of_usernames: true + # separator: • + # space_between_connections: 0.5cm + # section_titles: + # type: with_full_line + # line_thickness: 0.5pt + # space_above: 0.5cm + # space_below: 0.3cm + # sections: + # allow_page_break: true + # space_between_regular_entries: 1.2em + # space_between_text_based_entries: 0.3em + # show_time_spans_in: [] + # entries: + # date_and_location_width: 4.15cm + # side_space: 0.2cm + # space_between_columns: 0.1cm + # allow_page_break: false + # short_second_row: false + # degree_width: 1cm + # summary: + # space_above: 0cm + # space_left: 0cm + # highlights: + # bullet: ◦ + # nested_bullet: ◦ + # space_left: 0.15cm + # space_above: 0cm + # space_between_items: 0cm + # space_between_bullet_and_text: 0.5em + # templates: + # footer: '*NAME -- PAGE_NUMBER/TOTAL_PAGES*' + # top_note: '*LAST_UPDATED CURRENT_DATE*' + # single_date: MONTH_ABBREVIATION YEAR + # date_range: START_DATE – END_DATE + # time_span: HOW_MANY_YEARS YEARS HOW_MANY_MONTHS MONTHS + # one_line_entry: + # main_column: '**LABEL:** DETAILS' + # education_entry: + # main_column: |- + # **INSTITUTION** + # *DEGREE* *in* *AREA* + # SUMMARY + # HIGHLIGHTS + # degree_column: + # date_and_location_column: |- + # *LOCATION* + # *DATE* + # normal_entry: + # main_column: |- + # **NAME** + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: |- + # *LOCATION* + # *DATE* + # experience_entry: + # main_column: |- + # **POSITION** + # *COMPANY* + # SUMMARY + # HIGHLIGHTS + # date_and_location_column: |- + # *LOCATION* + # *DATE* + # publication_entry: + # main_column: |- + # **TITLE** + # SUMMARY + # AUTHORS + # URL (JOURNAL) + # date_and_location_column: DATE locale: - language: en - phone_number_format: national - page_numbering_template: NAME - Page PAGE_NUMBER of TOTAL_PAGES - last_updated_date_template: Last updated in TODAY - date_template: MONTH_ABBREVIATION YEAR - month: month - months: months - year: year - years: years - present: present - to: – - abbreviations_for_months: - - Jan - - Feb - - Mar - - Apr - - May - - June - - July - - Aug - - Sept - - Oct - - Nov - - Dec - full_names_of_months: - - January - - February - - March - - April - - May - - June - - July - - August - - September - - October - - November - - December -rendercv_settings: - date: '2025-03-01' + language: english + # last_updated: Last updated in + # month: month + # months: months + # year: year + # years: years + # present: present + # phrases: + # degree_with_area: DEGREE in AREA + # month_abbreviations: + # - Jan + # - Feb + # - Mar + # - Apr + # - May + # - June + # - July + # - Aug + # - Sept + # - Oct + # - Nov + # - Dec + # month_names: + # - January + # - February + # - March + # - April + # - May + # - June + # - July + # - August + # - September + # - October + # - November + # - December +settings: + current_date: today + render_command: + output_folder: rendercv_output + design: + locale: + typst_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.typ + pdf_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.pdf + markdown_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.md + html_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.html + png_path: OUTPUT_FOLDER/NAME_IN_SNAKE_CASE_CV.png + dont_generate_markdown: false + dont_generate_html: false + dont_generate_typst: false + dont_generate_pdf: false + dont_generate_png: false bold_keywords: [] + pdf_title: NAME - CV diff --git a/examples/output_Opal/John_Doe_CV.pdf b/examples/output_Opal/John_Doe_CV.pdf new file mode 100644 index 000000000..127886981 Binary files /dev/null and b/examples/output_Opal/John_Doe_CV.pdf differ diff --git a/examples/output_Opal/John_Doe_CV.typ b/examples/output_Opal/John_Doe_CV.typ new file mode 100644 index 000000000..db83207cf --- /dev/null +++ b/examples/output_Opal/John_Doe_CV.typ @@ -0,0 +1,376 @@ +// Import the rendercv function and all the refactored components +#import "@preview/rendercv:0.3.0": * + +// Apply the rendercv template with custom configuration +#show: rendercv.with( + name: "John Doe", + title: "John Doe - CV", + footer: context { [#emph[John Doe -- #str(here().page())\/#str(counter(page).final().first())]] }, + top-note: [ #emph[Last updated in Mar 2026] ], + locale-catalog-language: "en", + text-direction: ltr, + page-size: "us-letter", + page-top-margin: 0.65in, + page-bottom-margin: 0.65in, + page-left-margin: 0.65in, + page-right-margin: 0.65in, + page-show-footer: false, + page-show-top-note: false, + colors-body: rgb(0, 0, 0), + colors-name: rgb(0, 100, 90), + colors-headline: rgb(0, 80, 72), + colors-connections: rgb(0, 80, 72), + colors-section-titles: rgb(0, 100, 90), + colors-links: rgb(0, 100, 90), + colors-footer: rgb(100, 140, 135), + colors-top-note: rgb(100, 140, 135), + typography-line-spacing: 0.6em, + typography-alignment: "left", + typography-date-and-location-column-alignment: right, + typography-font-family-body: "Lato", + typography-font-family-name: "Lato", + typography-font-family-headline: "Lato", + typography-font-family-connections: "Lato", + typography-font-family-section-titles: "Lato", + typography-font-size-body: 10pt, + typography-font-size-name: 26pt, + typography-font-size-headline: 10pt, + typography-font-size-connections: 9pt, + typography-font-size-section-titles: 1.2em, + typography-small-caps-name: false, + typography-small-caps-headline: true, + typography-small-caps-connections: false, + typography-small-caps-section-titles: true, + typography-bold-name: true, + typography-bold-headline: false, + typography-bold-connections: false, + typography-bold-section-titles: false, + links-underline: false, + links-show-external-link-icon: false, + header-alignment: center, + header-photo-width: 3.5cm, + header-space-below-name: 0.3cm, + header-space-below-headline: 0.3cm, + header-space-below-connections: 0.6cm, + header-connections-hyperlink: true, + header-connections-show-icons: true, + header-connections-display-urls-instead-of-usernames: false, + header-connections-separator: "•", + header-connections-space-between-connections: 0.5cm, + section-titles-type: "centered_without_line", + section-titles-line-thickness: 0.4pt, + section-titles-space-above: 0.55cm, + section-titles-space-below: 0.25cm, + sections-allow-page-break: true, + sections-space-between-text-based-entries: 0.3em, + sections-space-between-regular-entries: 1.1em, + entries-date-and-location-width: 4.15cm, + entries-side-space: 0.15cm, + entries-space-between-columns: 0.1cm, + entries-allow-page-break: false, + entries-short-second-row: true, + entries-degree-width: 1cm, + entries-summary-space-left: 0cm, + entries-summary-space-above: 0.04cm, + entries-highlights-bullet: "◦" , + entries-highlights-nested-bullet: "◦" , + entries-highlights-space-left: 0.15cm, + entries-highlights-space-above: 0.04cm, + entries-highlights-space-between-items: 0.04cm, + entries-highlights-space-between-bullet-and-text: 0.5em, + date: datetime( + year: 2026, + month: 3, + day: 20, + ), +) + + += John Doe + +#connections( + [#connection-with-icon("location-dot")[San Francisco, CA]], + [#link("mailto:john.doe@email.com", icon: false, if-underline: false, if-color: false)[#connection-with-icon("envelope")[john.doe\@email.com]]], + [#link("https://rendercv.com/", icon: false, if-underline: false, if-color: false)[#connection-with-icon("link")[rendercv.com]]], + [#link("https://linkedin.com/in/rendercv", icon: false, if-underline: false, if-color: false)[#connection-with-icon("linkedin")[rendercv]]], + [#link("https://github.com/rendercv", icon: false, if-underline: false, if-color: false)[#connection-with-icon("github")[rendercv]]], +) + + +== Welcome to RenderCV + +RenderCV reads a CV written in a YAML file, and generates a PDF with professional typography. + +Each section title is arbitrary. + +You can choose any of the 9 entry types for each section. + +Markdown syntax is supported everywhere. This is #strong[bold], #emph[italic], and #link("https://example.com")[link]. + +== Education + +#education-entry( + [ + #strong[Princeton University] -- Princeton, NJ + + PhD in Computer Science + + - Thesis: Efficient Neural Architecture Search for Resource-Constrained Deployment + + - Advisor: Prof. Sanjeev Arora + + - NSF Graduate Research Fellowship, Siebel Scholar (Class of 2022) + + ], + [ + Sept 2018 – May 2023 + + ], +) + +#education-entry( + [ + #strong[Boğaziçi University] -- Istanbul, Türkiye + + BS in Computer Engineering + + - GPA: 3.97\/4.00, Valedictorian + + - Fulbright Scholarship recipient for Graduate Studies + + ], + [ + Sept 2014 – June 2018 + + ], +) + +== Experience + +#regular-entry( + [ + #strong[Nexus AI], #emph[Co-Founder & CTO] -- San Francisco, CA + + - Built foundation model infrastructure serving 2M+ monthly API requests with 99.97\% uptime + + - Raised \$18M Series A led by Sequoia Capital, with participation from a16z and Founders Fund + + - Scaled engineering team from 3 to 28 across ML research, platform, and applied AI divisions + + - Developed proprietary inference optimization reducing latency by 73\% compared to baseline + + ], + [ + June 2023 – present + + ], +) + +#regular-entry( + [ + #strong[NVIDIA Research], #emph[Research Intern] -- Santa Clara, CA + + - Designed sparse attention mechanism reducing transformer memory footprint by 4.2x + + - Co-authored paper accepted at NeurIPS 2022 (spotlight presentation, top 5\% of submissions) + + ], + [ + May 2022 – Aug 2022 + + ], +) + +#regular-entry( + [ + #strong[Google DeepMind], #emph[Research Intern] -- London, UK + + - Developed reinforcement learning algorithms for multi-agent coordination + + - Published research at top-tier venues with significant academic impact + + - ICML 2022 main conference paper, cited 340+ times within two years + + - NeurIPS 2022 workshop paper on emergent communication protocols + + - Invited journal extension in JMLR (2023) + + ], + [ + May 2021 – Aug 2021 + + ], +) + +#regular-entry( + [ + #strong[Apple ML Research], #emph[Research Intern] -- Cupertino, CA + + - Created on-device neural network compression pipeline deployed across 50M+ devices + + - Filed 2 patents on efficient model quantization techniques for edge inference + + ], + [ + May 2020 – Aug 2020 + + ], +) + +#regular-entry( + [ + #strong[Microsoft Research], #emph[Research Intern] -- Redmond, WA + + - Implemented novel self-supervised learning framework for low-resource language modeling + + - Research integrated into Azure Cognitive Services, reducing training data requirements by 60\% + + ], + [ + May 2019 – Aug 2019 + + ], +) + +== Projects + +#regular-entry( + [ + #strong[#link("https://github.com/")[FlashInfer]] + + #summary[Open-source library for high-performance LLM inference kernels] + + - Achieved 2.8x speedup over baseline attention implementations on A100 GPUs + + - Adopted by 3 major AI labs, 8,500+ GitHub stars, 200+ contributors + + ], + [ + Jan 2023 – present + + ], +) + +#regular-entry( + [ + #strong[#link("https://github.com/")[NeuralPrune]] + + #summary[Automated neural network pruning toolkit with differentiable masks] + + - Reduced model size by 90\% with less than 1\% accuracy degradation on ImageNet + + - Featured in PyTorch ecosystem tools, 4,200+ GitHub stars + + ], + [ + Jan 2021 + + ], +) + +== Publications + +#regular-entry( + [ + #strong[Sparse Mixture-of-Experts at Scale: Efficient Routing for Trillion-Parameter Models] + + #emph[John Doe], Sarah Williams, David Park + + #link("https://doi.org/10.1234/neurips.2023.1234")[10.1234\/neurips.2023.1234] (NeurIPS 2023) + + ], + [ + July 2023 + + ], +) + +#regular-entry( + [ + #strong[Neural Architecture Search via Differentiable Pruning] + + James Liu, #emph[John Doe] + + #link("https://doi.org/10.1234/neurips.2022.5678")[10.1234\/neurips.2022.5678] (NeurIPS 2022, Spotlight) + + ], + [ + Dec 2022 + + ], +) + +#regular-entry( + [ + #strong[Multi-Agent Reinforcement Learning with Emergent Communication] + + Maria Garcia, #emph[John Doe], Tom Anderson + + #link("https://doi.org/10.1234/icml.2022.9012")[10.1234\/icml.2022.9012] (ICML 2022) + + ], + [ + July 2022 + + ], +) + +#regular-entry( + [ + #strong[On-Device Model Compression via Learned Quantization] + + #emph[John Doe], Kevin Wu + + #link("https://doi.org/10.1234/iclr.2021.3456")[10.1234\/iclr.2021.3456] (ICLR 2021, Best Paper Award) + + ], + [ + May 2021 + + ], +) + +== Selected Honors + +- MIT Technology Review 35 Under 35 Innovators (2024) + +- Forbes 30 Under 30 in Enterprise Technology (2024) + +- ACM Doctoral Dissertation Award Honorable Mention (2023) + +- Google PhD Fellowship in Machine Learning (2020 – 2023) + +- Fulbright Scholarship for Graduate Studies (2018) + +== Skills + +#strong[Languages:] Python, C++, CUDA, Rust, Julia + +#strong[ML Frameworks:] PyTorch, JAX, TensorFlow, Triton, ONNX + +#strong[Infrastructure:] Kubernetes, Ray, distributed training, AWS, GCP + +#strong[Research Areas:] Neural architecture search, model compression, efficient inference, multi-agent RL + +== Patents + ++ Adaptive Quantization for Neural Network Inference on Edge Devices (US Patent 11,234,567) + ++ Dynamic Sparsity Patterns for Efficient Transformer Attention (US Patent 11,345,678) + ++ Hardware-Aware Neural Architecture Search Method (US Patent 11,456,789) + +== Invited Talks + +#reversed-numbered-entries( + [ + ++ Scaling Laws for Efficient Inference — Stanford HAI Symposium (2024) + ++ Building AI Infrastructure for the Next Decade — TechCrunch Disrupt (2024) + ++ From Research to Production: Lessons in ML Systems — NeurIPS Workshop (2023) + ++ Efficient Deep Learning: A Practitioner's Perspective — Google Tech Talk (2022) + ], +) diff --git a/examples/output_Opal/John_Doe_CV_1.png b/examples/output_Opal/John_Doe_CV_1.png new file mode 100644 index 000000000..b42f39ddb Binary files /dev/null and b/examples/output_Opal/John_Doe_CV_1.png differ diff --git a/examples/output_Opal/John_Doe_CV_2.png b/examples/output_Opal/John_Doe_CV_2.png new file mode 100644 index 000000000..feacb447a Binary files /dev/null and b/examples/output_Opal/John_Doe_CV_2.png differ diff --git a/examples/output_Opal/John_Doe_CV_3.png b/examples/output_Opal/John_Doe_CV_3.png new file mode 100644 index 000000000..4f704533f Binary files /dev/null and b/examples/output_Opal/John_Doe_CV_3.png differ diff --git a/justfile b/justfile new file mode 100644 index 000000000..bcb179c59 --- /dev/null +++ b/justfile @@ -0,0 +1,58 @@ +# Development: +sync: + uv sync --frozen --all-extras + +lock: + uv lock + +format: + uv run --frozen --all-extras black src tests || true + uv run --frozen --all-extras ruff check --fix src tests || true + uv run --frozen --all-extras ruff format src tests + +format-file target: + uv run --frozen --all-extras black {{target}} || true + uv run --frozen --all-extras ruff check --fix {{target}} || true + uv run --frozen --all-extras ruff format {{target}} + +check: + uv run --frozen --all-extras ruff check src tests + uv run --frozen --all-extras ty check src tests + uv run --frozen --all-extras prek run --all-files + +# Testing: +test: + uv run --frozen --all-extras pytest + +update-testdata: + uv run --frozen --all-extras pytest --update-testdata + +test-coverage: + uv run --frozen --all-extras pytest --cov=src/rendercv --cov-report=term --cov-report=html --cov-report=markdown + +# Docs: +build-docs: + uv run --frozen --all-extras mkdocs build --clean --strict + +serve-docs: + uv run --frozen --all-extras mkdocs serve --watch-theme + +# Scripts: +update-schema: + uv run --frozen --all-extras scripts/update_schema.py + +update-examples: + uv run --frozen --all-extras scripts/update_examples.py + +update-skill: + uv run --frozen --all-extras scripts/rendercv_skill/generate.py + +update-entry-figures: + uv run --frozen --all-extras --group update-entry-figures scripts/update_entry_figures.py + +create-executable: + uv run --frozen --all-extras --no-default-groups --group create-executable scripts/create_executable.py + +# Utilities: +count-lines: + wc -l `find src -name '*.py'` diff --git a/mkdocs.yaml b/mkdocs.yaml index 54589db87..0326cb9a1 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -1,35 +1,34 @@ -site_name: RenderCV Engine -site_description: A Python package with CLI tool to write and version-control CVs and resumes as source code -site_author: RenderCV Team -copyright: Copyright © 2023 - 2024 RenderCV -site_url: https://docs.rendercv.com +site_name: RenderCV CLI +site_description: Typst-based CV/resume generator for academics and engineers +copyright: Copyright © 2023 - 2026 RenderCV repo_url: https://github.com/rendercv/rendercv repo_name: rendercv/rendercv edit_uri: edit/main/docs/ theme: name: material - logo: assets/images/icon.svg + icon: + logo: custom/rendercv language: en custom_dir: docs/overrides palette: - media: "(prefers-color-scheme: light)" scheme: default - primary: indigo - accent: indigo + primary: default + accent: default toggle: - icon: material/lightbulb-outline + icon: custom/sun name: "Switch to dark mode" - media: "(prefers-color-scheme: dark)" scheme: slate - primary: indigo - accent: indigo + primary: default + accent: default toggle: - icon: material/lightbulb + icon: custom/moon name: "Switch to light mode" font: - text: Roboto + text: DM Sans code: Roboto Mono features: @@ -37,7 +36,8 @@ theme: - content.action.view # view source button for pages - content.action.edit # view source button for pages - navigation.footer # the previous and next buttons in the footer - - navigation.indexes # allow mother pages to have their own index pages + - navigation.tabs # Top navbar + # - navigation.indexes # allow mother pages to have their own index pages - navigation.instant # instant navigation for faster page loads - navigation.instant.prefetch # prefetch pages for instant navigation - navigation.instant.progress # show progress bar for instant navigation @@ -51,58 +51,42 @@ theme: - content.tabs.link # switch all the content tabs to the same label nav: - - Overview: index.md - User Guide: - - User Guide: user_guide/index.md - - Structure of the YAML input file: user_guide/structure_of_the_yaml_input_file.md - - CLI: user_guide/cli.md - - FAQ: user_guide/faq.md + - Welcome: index.md + - Get Started: user_guide/index.md + - CLI Reference: user_guide/cli_reference.md + - YAML Input Structure: + - Overview: user_guide/yaml_input_structure/index.md + - cv Field: user_guide/yaml_input_structure/cv.md + - design Field: user_guide/yaml_input_structure/design.md + - locale Field: user_guide/yaml_input_structure/locale.md + - settings Field: user_guide/yaml_input_structure/settings.md + - How-To Guides: + - Set Up VS Code for RenderCV: user_guide/how_to/set_up_vs_code_for_rendercv.md + - Arbitrary Keys in Entries: user_guide/how_to/arbitrary_keys_in_entries.md + - Custom Fonts: user_guide/how_to/custom_fonts.md + - Override Default Templates: user_guide/how_to/override_default_templates.md + - Use the AI Agent Skill: user_guide/how_to/use_the_ai_agent_skill.md - Developer Guide: - - Developer Guide: developer_guide/index.md - - Writing Documentation: developer_guide/writing_documentation.md + - Setup: developer_guide/index.md + - Project Management: developer_guide/project_management.md + - Understanding RenderCV: developer_guide/understanding_rendercv.md - Testing: developer_guide/testing.md - - FAQ: developer_guide/faq.md - - API Reference: - - Reference: reference/index.md - - api: - - api: reference/api/index.md - - functions.py: reference/api/functions.md - - cli: - - cli: reference/cli/index.md - - commands.py: reference/cli/commands.md - - printer.py: reference/cli/printer.md - - utilities.py: reference/cli/utilities.md - - data: - - data: reference/data/index.md - - models: - - models: reference/data/models/index.md - - base.py: reference/data/models/base.md - - computers.py: reference/data/models/computers.md - - entry_types.py: reference/data/models/entry_types.md - - curriculum_vitae.py: reference/data/models/curriculum_vitae.md - - design.py: reference/data/models/design.md - - locale.py: reference/data/models/locale.md - - rendercv_settings.py: reference/data/models/rendercv_settings.md - - rendercv_data_model.py: reference/data/models/rendercv_data_model.md - - generator.py: reference/data/generator.md - - reader.py: reference/data/reader.md - - renderer: - - renderer: reference/renderer/index.md - - renderer.py: reference/renderer/renderer.md - - templater.py: reference/renderer/templater.md - - themes: - - themes: reference/themes/index.md - - options.py: reference/themes/options.md - - classic: reference/themes/classic.md - - engineeringresumes: reference/themes/engineeringresumes.md - - sb2nov: reference/themes/sb2nov.md - - moderncv: reference/themes/moderncv.md - - engineeringclassic: reference/themes/engineeringclassic.md - - components: reference/themes/components.md - - Changelog: - - Changelog: changelog/index.md + - Documentation: developer_guide/documentation.md + - JSON Schema: developer_guide/json_schema.md + - GitHub Workflows: developer_guide/github_workflows.md + - Dockerfile: developer_guide/dockerfile.md + - How-To Guides: + - Add Theme: developer_guide/how_to/add_theme.md + - Add Locale: developer_guide/how_to/add_locale.md + - Add Social Network: developer_guide/how_to/add_social_network.md + - API Reference: api_reference/ + - ATS Compatibility: ats_compatibility.md + - Changelog: changelog.md + - New Web App!: https://rendercv.com markdown_extensions: + - github-callouts # see https://facelessuser.github.io/pymdown-extensions/extensions/inlinehilite/ for more pymdownx info - pymdownx.highlight: anchor_linenums: true @@ -124,21 +108,45 @@ markdown_extensions: plugins: - search + - literate-nav: + nav_file: SUMMARY.md + - gen-files: + scripts: + - docs/api_reference/api_reference.py - macros: # mkdocs-macros-plugin - module_name: docs/dynamic_content_generation + module_name: docs/docs_templating + j2_block_start_string: "{$" + j2_block_end_string: "$}" + j2_variable_start_string: "<<" + j2_variable_end_string: ">>" - mkdocstrings: handlers: python: - paths: - - rendercv options: - members_order: source + members_order: alphabetical show_bases: true docstring_section_style: list docstring_style: google - + inherited_members: true + show_root_heading: true + heading_level: 1 + show_symbol_type_heading: True + show_root_full_path: false + show_symbol_type_toc: true + signature_crossrefs: true + show_docstring_attributes: true + show_source: true + show_submodules: false + merge_init_into_class: true + annotations_path: brief + show_signature: true + show_docstring_examples: true + show_docstring_type_aliases: true + show_overloads: false + show_if_no_docstring: true extra_javascript: - assets/javascripts/katex.js + - assets/javascripts/rendercv-logo.js - https://unpkg.com/katex@0/dist/katex.min.js - https://unpkg.com/katex@0/dist/contrib/auto-render.min.js diff --git a/pyproject.toml b/pyproject.toml index 58800fc38..4e4b05b9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,14 @@ # Every modern Python package today has a `pyproject.toml` file. It is a Python -# standard. `pyproject.toml` file contains all the metadata about the package. It also -# includes the dependencies and required information for building the package. For more -# details, see https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/. +# standard. The `pyproject.toml` file contains all the metadata about the package. +# It also includes the dependencies and required information for building the package. +# For more details, see https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/. [build-system] -# If a code needs to be distributed, it might need to be compiled, or it might need to -# be bundled with other files. This process of making a code ready for distribution is -# called building. +# If code needs to be distributed, it might need to be compiled or bundled with other files. +# This process of making code ready for distribution is called building. -# Python packages need to be built too, even though they are not compiled (mostly). At -# the end of the building process, a source distribution package (sdist) and a built +# Python packages need to be built too, even though they are not compiled (mostly). +# At the end of the building process, a source distribution package (sdist) and a built # distribution package (in Wheel format) are created. # See https://packaging.python.org/en/latest/tutorials/packaging-projects/ for details. # Built Distribution: @@ -17,267 +16,208 @@ # Source Distribution: # https://packaging.python.org/en/latest/glossary/#term-Source-Distribution-or-sdist -# To build RenderCV, we need to specify which build package we want to use. There are -# many build packages like `setuptools`, `flit`, `poetry`, `hatchling`, etc. We will use -# `hatchling`. -requires = [ - "hatchling==1.27.0", -] # List of packages that are needed to build RenderCV - -# Python has a standard object format called build-backend object. Python standard asks -# this object to have some specific methods that do a specific job. For example, it -# should have a method called `build_wheel` that builds a wheel file. We use hatchling -# to build RenderCV, and hatchling's build-backend object is `hatchling.build`. +# To build RenderCV, we need to specify which build package to use. There are +# many build backends like `setuptools`, `flit`, `poetry`, `hatchling`, etc. We will use +# `uv_build`. +requires = ["uv_build>=0.10.3,<0.11.0"] # Packages needed to build RenderCV +# Python has a standard object format called a build-backend object. This object must +# implement specific methods that perform defined tasks. For example, it should have a +# method called `build_wheel` that builds a wheel file. # See https://peps.python.org/pep-0517/ -build-backend = "hatchling.build" # A build-backend object for building RenderCV - -[tool.hatch.build.targets.sdist] -# In the sdist, what do we want to exclude? Gif files are huge. -exclude = ["*.gif"] - -[tool.hatch.build.targets.wheel] -# In wheel, what do we want to include and exclude? -packages = ["rendercv"] - -[tool.hatch.version] -# We will use hatchling to generate the version number of RenderCV. It will go to the -# `path` below and get the version number from there. -# See https://hatch.pypa.io/latest/version/ -path = "rendercv/__init__.py" +build-backend = "uv_build" # Build-backend object for building RenderCV [project] -# Under the `project` section, we specify the metadata about RenderCV. -name = 'rendercv' -description = 'Typst-based CV/resume generator' -authors = [{ name = 'Sina Atalay', email = 'dev@atalay.biz' }] -license = "MIT" +# Metadata about RenderCV. +name = "rendercv" +version = "2.8" +description = "Resume builder for academics and engineers" readme = "README.md" -requires-python = '>=3.10' -# RenderCV depends on these packages. They will be installed automatically when RenderCV -# is installed: +requires-python = ">=3.12" +license = "MIT" +authors = [{ name = "Sina Atalay", email = "dev@atalay.biz" }] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Printing", + "Topic :: Text Processing :: Markup", +] # See all classifiers at https://pypi.org/classifiers/ +# RenderCV depends on these packages. They are installed automatically when RenderCV is installed: dependencies = [ - 'Jinja2>=3.1.3', # to generate Typst and Markdown files - 'phonenumbers==9.0.13', # to validate phone numbers - 'email-validator==2.2.0', # to validate email addresses - 'pydantic==2.11.9', # to validate and parse the input file - 'pycountry==24.6.1', # for ISO 639-3 validation - 'pydantic-extra-types==2.10.2', # to validate some extra types - 'ruamel.yaml==0.18.6', # to parse YAML files + "Jinja2>=3.1.6", # Generate Typst and Markdown files + "markdown>=3.10.2", # Convert Markdown to HTML + "phonenumbers>=9.0.24", # Validate phone numbers + "pydantic-extra-types>=2.11.0", # Validate extra types + "pydantic[email]>=2.12.5", # Validate and parse input files + "ruamel.yaml>=0.19.1", # Parse YAML files ] -classifiers = [ - "Intended Audience :: Science/Research", - "Intended Audience :: Education", - "Topic :: Text Processing :: Markup", - "Topic :: Printing", - "Development Status :: 5 - Production/Stable", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", -] # go to https://pypi.org/classifiers/ to see all classifiers -dynamic = ["version"] # We will use hatchling to generate the version number [project.optional-dependencies] full = [ - 'typer==0.16.0', # to create the command-line interface - "markdown==3.9", # to convert Markdown to HTML - "watchdog==6.0.0", # to poll files for updates - "typst==0.13.1", # to render PDF from Typst source files - "rendercv-fonts", # some font files for RenderCV - "packaging==24.2", # to validate the version number + "typer>=0.24.1", # Command-line interface + "watchdog>=6.0.0", # Monitor files for updates + "typst>=0.14.8", # Render PDF from Typst source files + "rendercv-fonts>=0.5.1", # Font files for RenderCV + "packaging>=26.0", # For version checking ] [project.urls] -# Here, we can specify the URLs related to RenderCV. They will be listed under the -# "Project links" section in PyPI. See https://pypi.org/project/rendercv/ -"Web App" = 'https://rendercv.com' -Source = 'https://github.com/rendercv/rendercv' -Documentation = 'https://docs.rendercv.com' -Changelog = 'https://docs.rendercv.com/changelog' +Changelog = "https://docs.rendercv.com/changelog" +Documentation = "https://docs.rendercv.com" +Source = "https://github.com/rendercv/rendercv" +# URLs related to RenderCV. Listed under the "Project links" section on PyPI. +"Web App" = "https://rendercv.com" [project.scripts] -# Here, we specify the entry points of RenderCV. +# Entry points for RenderCV. # See https://packaging.python.org/en/latest/specifications/entry-points/#entry-points # See https://hatch.pypa.io/latest/config/metadata/#cli -# The key and value below mean this: If someone installs RenderCV, then running -# `rendercv` in the terminal will run the function `app` in the module `cli` in the -# package `rendercv`. -rendercv = 'rendercv.cli:app' - -# ====================================================================================== -# Virtual Environments Below =========================================================== -# ====================================================================================== - -# RenderCV depends on other packages, which are listed under the `project` section as -# `dependencies`. However, for the development of RenderCV, we need some other packages -# too (like `black`, `ruff`, `mkdocs`, etc.). We need these packages in our virtual -# environments and we handle the environments with `hatchling`. - -# There will be three virtual environments for RenderCV: `default`, `docs`, and `test`. - -# `default` is the default virtual environment needed to develop RenderCV. -# `docs` is the virtual environment needed to build the documentation of RenderCV. -# `test` is the virtual environment needed to run the tests of RenderCV. - -[tool.hatch.envs.default] -installer = "uv" -python = "3.13" -dependencies = [ - "ruff", # to lint and format the code - "black", # to format the code - "ipython", # for ipython shell - "pyright", # to check the types - "pre-commit", # to run the checks before committing - "pytest==8.3.4", # to run the tests - "coverage==7.6.10", # to generate coverage reports - "pypdf==5.1.0", # to read PDF files - "snakeviz==2.2.2", # for profiling - "pyinstaller==6.11.1", # to build the executable +# The key and value below mean: when someone installs RenderCV, +# running `rendercv` in the terminal executes the function `entry_point` in +# `src/rendercv/cli/entry_point.py`. +rendercv = "rendercv.cli.entry_point:entry_point" + +# Virtual Environment Dependencies: +[dependency-groups] +dev = [ + "black>=26.3.1", # Format the code + "prek>=0.3.6", # Run checks before committing (pre-commit alternative) + "pytest>=9.0.2", # Run tests + "pytest-cov>=7.0.0", # Coverage plugin for pytest with xdist support + "pytest-xdist>=3.8.0", # Run tests in parallel + "ruff>=0.15.7", # Lint and format the code + "ty>=0.0.24", # Type checking ] -features = ["full"] # to install full optional dependencies -[tool.hatch.envs.default.scripts] -# Hatch allows us to define scripts that can be run in the activated virtual environment -# with `hatch run ENV_NAME:SCRIPT_NAME`. -# Format all the code in the `rendercv` package with `black`: -format = "black rendercv docs tests && ruff check --fix && ruff format" # hatch run format -# Lint the code in the `rendercv` package with `ruff`: -lint = "ruff check" # hatch run lint -# Check types in the `rendercv` package with `pyright`: -check-types = "pyright rendercv tests" # hatch run check-types -# Run pre-commit checks: -precommit = "pre-commit run --all-files" # hatch run pre-commit -# Run the tests: -test = "pytest" # hatch run test -# Run the tests and generate the coverage report as HTML: -test-and-report = "coverage run -m pytest && coverage combine && coverage report && coverage html --show-contexts" # hatch run test-and-report -# Profile render command -profile-render-command = "python -m cProfile -o render_command.prof -m rendercv render examples/John_Doe_ClassicTheme_CV.yaml && snakeviz render_command.prof" -# Update schema.json: -update-schema = "python scripts/update_schema.py" # hatch run update-schema -# Update `examples` folder: -update-examples = "python scripts/update_examples.py" # hatch run update-examples - -[tool.hatch.envs.test] -template = "default" -[[tool.hatch.envs.test.matrix]] -python = ["3.10", "3.11", "3.12", "3.13"] - -[tool.hatch.envs.docs] -installer = "uv" -python = "3.13" -# Dependencies to be installed in the `docs` virtual environment. -dependencies = [ - "mkdocs-material==9.6.12", # to build docs - "mkdocstrings-python==1.16.10", # to build reference documentation from docstrings - "pdfCropMargins==2.1.3", # to generate entry figures for the documentation - "pillow==10.4.0", # lock the dependency of pdfCropMargins - "mkdocs-macros-plugin==1.3.7", # to be able to have dynamic content in the documentation - "PyMuPDF==1.24.14", # to convert PDF files to images +create-executable = [ + "pyinstaller>=6.17.0", # Build executables ] -features = ["full"] # to install full optional dependencies -[tool.hatch.envs.docs.scripts] -# Build the documentation with `mkdocs`: -build = "mkdocs build --clean --strict" # hatch run docs:build -# Start the development server for the documentation with `mkdocs`: -serve = "mkdocs serve" # hatch run docs:serve -# Update entry figures in "Structure of the YAML File" page: -update-entry-figures = "python scripts/update_entry_figures.py" # hatch run docs:update-entry-figures - - -[tool.hatch.envs.exe] -installer = "uv" -python = "3.13" -dependencies = [ - "pyinstaller==6.11.1", # to build the executable +docs = [ + "markdown-callouts>=0.4.0", # GitHub alert style admonitions + "mkdocs-gen-files>=0.6.0", # Dynamic page generation for API reference + "mkdocs-literate-nav>=0.6.2", # Dynamic navigation for API reference + "mkdocs-macros-plugin>=1.5.0", # Dynamic content in docs + "mkdocs-material>=9.7.1", + "griffe>=1.0,<3", # Pin to v1 (v2 broke mkdocstrings-python compatibility) + "mkdocstrings[python]>=1.0.3", # Build reference docs from docstrings +] +update-entry-figures = [ + "PyMuPDF==1.26.5", # Convert PDF files to images + "pdfCropMargins==2.2.1", # Generate entry figures for documentation + "pillow==10.4.0", # Lock dependency of pdfCropMargins ] -features = ["full"] -[tool.hatch.envs.exe.scripts] -create = "python scripts/create_executable.py" # hatch run exe:create -# ====================================================================================== -# Virtual Environments Above =========================================================== -# ====================================================================================== -# RenderCV uses different tools to check the code quality, format the code, build the -# documentation, build the package, etc. We can specify the settings for these tools in -# `pyproject.toml` file under `[tool.name_of_the_tool]` so that new contributors can use -# these tools easily. Generally, popular IDEs grab these settings from `pyproject.toml` -# file automatically. +# Tools Settings: + +# RenderCV uses various tools to check code quality, format code, build docs, and package the project. +# Their configurations are specified below so contributors and IDEs can pick them up automatically. + +[tool.uv] +default-groups = ["dev", "docs"] + +[tool.uv.build-backend] +# The rendercv-typst Typst package lives in src/rendercv/renderer/rendercv_typst/. +# Only lib.typ and typst.toml are needed at runtime; exclude everything else from the wheel. +wheel-exclude = [ + "renderer/rendercv_typst/examples/**", + "renderer/rendercv_typst/template/**", + "renderer/rendercv_typst/README.md", + "renderer/rendercv_typst/CHANGELOG.md", + "renderer/rendercv_typst/LICENSE", + "renderer/rendercv_typst/thumbnail.png", + "renderer/typst_fontawesome/example.typ", + "renderer/typst_fontawesome/gallery.typ", + "renderer/typst_fontawesome/helper.py", + "renderer/typst_fontawesome/README.md", + "renderer/typst_fontawesome/LICENSE", + "renderer/typst_fontawesome/.gitignore", +] +[tool.uv.workspace] +members = [ + "scripts/ats_proof", +] [tool.ruff] line-length = 88 +extend-exclude = ["src/rendercv/renderer/typst_fontawesome"] [tool.ruff.format] docstring-code-format = true [tool.ruff.lint] extend-select = [ - "B", # flake8-bugbear - "I", # isort - "ARG", # flake8-unused-arguments - "C4", # flake8-comprehensions - "EM", # flake8-errmsg - "ICN", # flake8-import-conventions - "ISC", # flake8-implicit-str-concat - "G", # flake8-logging-format - "PGH", # pygrep-hooks - "PIE", # flake8-pie - "PL", # pylint - "PT", # flake8-pytest-style - "PTH", # flake8-use-pathlib - "RET", # flake8-return - "RUF", # Ruff-specific - "SIM", # flake8-simplify - "T20", # flake8-print - "UP", # pyupgrade - "YTT", # flake8-2020 - "EXE", # flake8-executable - "NPY", # NumPy specific rules - "PD", # pandas-vet + "B", + "I", + "ARG", + "C4", + "EM", + "ICN", + "ISC", + "G", + "PGH", + "PIE", + "PL", + "PT", + "PTH", + "RET", + "RUF", + "SIM", + "T20", + "UP", + "YTT", + "EXE", + "NPY", + "PD", ] ignore = [ - "PLR", # Design related pylint codes - "ISC001", # Conflicts with formatter - "UP007", # I like Optional type - "PGH003", # It would be nice to not ignore this + "PLR", # Design-related pylint codes + "ISC001", # Conflicts with formatter + "UP007", # Allow Optional type + "PGH003", # Prefer not to ignore this + "RUF001", # Allow other characters for locale + "EM101", # Allow ] flake8-unused-arguments.ignore-variadic-names = true [tool.black] -line-length = 88 # maximum line length -preview = true # to allow enable-unstable-feature +line-length = 88 # Maximum line length +preview = true # Enable preview features enable-unstable-feature = [ - "string_processing", -] # to break strings into multiple lines + "string_processing", +] # Break strings into multiple lines -[tool.pyright] -reportIncompatibleVariableOverride = false # disable this error type -reportIncompatibleMethodOverride = false # disable this error type -exclude = ["rendercv/themes/*"] +[tool.ty.rules] +unused-ignore-comment = "error" [tool.coverage.run] -source = ['rendercv'] # The source to measure during execution -concurrency = ['multiprocessing'] # For watcher tests - -# Use relative paths instead of absolute paths, this is useful for combining coverage -# reports from different OSes: +source = ["src/rendercv"] # Measure coverage in this source +concurrency = ["multiprocessing"] # For watcher tests +# Use relative paths for cross-platform coverage merging: relative_files = true [tool.coverage.report] -# Don't include jinja templates in the coverage report: -omit = ["*.j2.*", "rendercv/__main__.py"] - -# Don't include these lines in the coverage report: -exclude_lines = ["if __name__ == .__main__.:"] +# Exclude templates from coverage reports: +omit = ["*.j2.*"] +# tombi: lint.rules.deprecated.disabled = true +# tombi: lint.rules.one-of-multiple-match.disabled = true [tool.pytest.ini_options] +log_cli_level = "INFO" +xfail_strict = true addopts = [ - "-ra", # Show extra test summary info for all tests - "-v", # Increase verbosity - "--strict-markers", # Don't allow unknown markers - "--strict-config", # Always fail if there are unknown configuration options + "-ra", # Show extra test summary info + "-v", # Increase verbosity + "--strict-markers", # Disallow unknown markers + "--strict-config", # Fail on unknown config options + "--numprocesses=auto", # Number of processes in parallel ] testpaths = ["tests"] + +[tool.codespell] +skip = "*.md" diff --git a/rendercv/__init__.py b/rendercv/__init__.py deleted file mode 100644 index 56dcdd644..000000000 --- a/rendercv/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -RenderCV is a Typst-based Python package with a command-line interface (CLI) that allows -you to version-control your CV/resume as source code. -""" - -__version__ = "2.3" - -from .api import ( - create_a_markdown_file_from_a_python_dictionary, - create_a_markdown_file_from_a_yaml_string, - create_a_pdf_from_a_python_dictionary, - create_a_pdf_from_a_yaml_string, - create_a_typst_file_from_a_python_dictionary, - create_a_typst_file_from_a_yaml_string, - create_an_html_file_from_a_python_dictionary, - create_an_html_file_from_a_yaml_string, - create_contents_of_a_markdown_file_from_a_python_dictionary, - create_contents_of_a_markdown_file_from_a_yaml_string, - create_contents_of_a_typst_file_from_a_python_dictionary, - create_contents_of_a_typst_file_from_a_yaml_string, - read_a_python_dictionary_and_return_a_data_model, - read_a_yaml_string_and_return_a_data_model, -) - -__all__ = [ - "create_a_markdown_file_from_a_python_dictionary", - "create_a_markdown_file_from_a_python_dictionary", - "create_a_markdown_file_from_a_yaml_string", - "create_a_pdf_from_a_python_dictionary", - "create_a_pdf_from_a_yaml_string", - "create_a_typst_file_from_a_python_dictionary", - "create_a_typst_file_from_a_yaml_string", - "create_an_html_file_from_a_python_dictionary", - "create_an_html_file_from_a_yaml_string", - "create_contents_of_a_markdown_file_from_a_python_dictionary", - "create_contents_of_a_markdown_file_from_a_yaml_string", - "create_contents_of_a_typst_file_from_a_python_dictionary", - "create_contents_of_a_typst_file_from_a_python_dictionary", - "create_contents_of_a_typst_file_from_a_yaml_string", - "read_a_python_dictionary_and_return_a_data_model", - "read_a_yaml_string_and_return_a_data_model", -] - -_parial_install_error_message = ( - "It seems you have a partial installation of RenderCV, so this feature is" - " unavailable. To enable full functionality, run:\n\npip install" - ' "rendercv[full]"`' -) diff --git a/rendercv/api/__init__.py b/rendercv/api/__init__.py deleted file mode 100644 index f4b98fad8..000000000 --- a/rendercv/api/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -The `rendercv.api` package contains the functions to create a clean and simple API for -RenderCV. -""" - -from .functions import ( - create_a_markdown_file_from_a_python_dictionary, - create_a_markdown_file_from_a_yaml_string, - create_a_pdf_from_a_python_dictionary, - create_a_pdf_from_a_yaml_string, - create_a_typst_file_from_a_python_dictionary, - create_a_typst_file_from_a_yaml_string, - create_an_html_file_from_a_python_dictionary, - create_an_html_file_from_a_yaml_string, - create_contents_of_a_markdown_file_from_a_python_dictionary, - create_contents_of_a_markdown_file_from_a_yaml_string, - create_contents_of_a_typst_file_from_a_python_dictionary, - create_contents_of_a_typst_file_from_a_yaml_string, - read_a_python_dictionary_and_return_a_data_model, - read_a_yaml_string_and_return_a_data_model, -) - -__all__ = [ - "create_a_markdown_file_from_a_python_dictionary", - "create_a_markdown_file_from_a_yaml_string", - "create_a_pdf_from_a_python_dictionary", - "create_a_pdf_from_a_yaml_string", - "create_a_typst_file_from_a_python_dictionary", - "create_a_typst_file_from_a_yaml_string", - "create_an_html_file_from_a_python_dictionary", - "create_an_html_file_from_a_yaml_string", - "create_contents_of_a_markdown_file_from_a_python_dictionary", - "create_contents_of_a_markdown_file_from_a_yaml_string", - "create_contents_of_a_typst_file_from_a_python_dictionary", - "create_contents_of_a_typst_file_from_a_yaml_string", - "read_a_python_dictionary_and_return_a_data_model", - "read_a_yaml_string_and_return_a_data_model", -] diff --git a/rendercv/api/functions.py b/rendercv/api/functions.py deleted file mode 100644 index 03bd76b7f..000000000 --- a/rendercv/api/functions.py +++ /dev/null @@ -1,382 +0,0 @@ -""" -The `rendercv.api.functions` package contains the basic functions that are used to -interact with the RenderCV. -""" - -import pathlib -import shutil -import tempfile -from collections.abc import Callable -from typing import Optional - -import pydantic - -from .. import data, renderer - - -def _create_contents_of_a_something_from_something( - input: dict | str, parser: Callable, renderer: Callable -) -> str | list[dict]: - """ - Validate the input, generate a file and return it as a string. If there are any - validation errors, return them as a list of dictionaries. - - Args: - input: The input file as a dictionary or a string. - parser: The parser function. - renderer: The renderer function. - - Returns: - The file as a string or a list of dictionaries that contain the error messages, - locations, and the input values. - """ - try: - data_model = parser(input) - except pydantic.ValidationError as e: - if isinstance(input, str): - return data.parse_validation_errors(e, input) - return data.parse_validation_errors(e) - - return renderer(data_model) - - -def _create_a_file_from_something( - input: dict | str, - parser: Callable, - renderer: Callable, - output_file_path: pathlib.Path, -) -> Optional[list[dict]]: - """ - Validate the input, generate a file and save it to the output file path. - - Args: - input: The input file as a dictionary or a string. - parser: The parser function. - renderer: The renderer function. - output_file_path: The output file path. - - Returns: - The output file path. - """ - try: - data_model = parser(input) - except pydantic.ValidationError as e: - return data.parse_validation_errors(e) - - with tempfile.TemporaryDirectory() as temp_dir: - temporary_output_path = pathlib.Path(temp_dir) - file = renderer(data_model, temporary_output_path) - shutil.move(file, output_file_path) - - return None - - -def read_a_python_dictionary_and_return_a_data_model( - input_file_as_a_dict: dict, -) -> data.RenderCVDataModel: - """ - Validate the input dictionary and return the data model. - - Args: - input_file_as_a_dict: The input file as a dictionary. - - Returns: - The data model. - """ - return data.validate_input_dictionary_and_return_the_data_model( - input_file_as_a_dict, - ) - - -def read_a_yaml_string_and_return_a_data_model( - yaml_file_as_string: str, -) -> data.RenderCVDataModel: - """ - Validate the YAML input file given as a string and return the data model. - - Args: - yaml_file_as_string: The input file as a string. - - Returns: - The data model. - """ - input_file_as_a_dict = data.read_a_yaml_file(yaml_file_as_string) - return read_a_python_dictionary_and_return_a_data_model(input_file_as_a_dict) - - -def create_contents_of_a_typst_file_from_a_python_dictionary( - input_file_as_a_dict: dict, -) -> str | list[dict]: - """ - Validate the input dictionary, generate a Typst file and return it as a string. If - there are any validation errors, return them as a list of dictionaries. - - Args: - input_file_as_a_dict: The input file as a dictionary. - - Returns: - The Typst file as a string or a list of dictionaries that contain the error - messages, locations, and the input values. - """ - return _create_contents_of_a_something_from_something( - input_file_as_a_dict, - read_a_python_dictionary_and_return_a_data_model, - renderer.create_contents_of_a_typst_file, - ) - - -def create_contents_of_a_typst_file_from_a_yaml_string( - yaml_file_as_string: str, -) -> str | list[dict]: - """ - Validate the YAML input file given as a string, generate a Typst file and return it - as a string. If there are any validation errors, return them as a list of - dictionaries. - - Args: - yaml_file_as_string: The input file as a string. - - Returns: - The Typst file as a string or a list of dictionaries that contain the error - messages, locations, and the input values. - """ - return _create_contents_of_a_something_from_something( - yaml_file_as_string, - read_a_yaml_string_and_return_a_data_model, - renderer.create_contents_of_a_typst_file, - ) - - -def create_contents_of_a_markdown_file_from_a_python_dictionary( - input_file_as_a_dict: dict, -) -> str | list[dict]: - """ - Validate the input dictionary, generate a Markdown file and return it as a string. - If there are any validation errors, return them as a list of dictionaries. - - Args: - input_file_as_a_dict: The input file as a dictionary. - - Returns: - The Markdown file as a string or a list of dictionaries that contain the error - messages, locations, and the input values. - """ - return _create_contents_of_a_something_from_something( - input_file_as_a_dict, - read_a_python_dictionary_and_return_a_data_model, - renderer.create_contents_of_a_markdown_file, - ) - - -def create_contents_of_a_markdown_file_from_a_yaml_string( - yaml_file_as_string: str, -) -> str | list[dict]: - """ - Validate the input file given as a string, generate a Markdown file and return it as - a string. If there are any validation errors, return them as a list of dictionaries. - - Args: - yaml_file_as_string: The input file as a string. - - Returns: - The Markdown file as a string or a list of dictionaries that contain the error - messages, locations, and the input values. - """ - return _create_contents_of_a_something_from_something( - yaml_file_as_string, - read_a_yaml_string_and_return_a_data_model, - renderer.create_contents_of_a_markdown_file, - ) - - -def create_a_typst_file_from_a_yaml_string( - yaml_file_as_string: str, - output_file_path: pathlib.Path, -) -> Optional[list[dict]]: - """ - Validate the input file given as a string, generate a Typst file and save it to the - output file path. - - Args: - yaml_file_as_string: The input file as a string. - output_file_path: The output file path. - - Returns: - The output file path. - """ - - return _create_a_file_from_something( - yaml_file_as_string, - read_a_yaml_string_and_return_a_data_model, - renderer.create_a_typst_file, - output_file_path, - ) - - -def create_a_typst_file_from_a_python_dictionary( - input_file_as_a_dict: dict, - output_file_path: pathlib.Path, -) -> Optional[list[dict]]: - """ - Validate the input dictionary, generate a Typst file and save it to the output file - path. - - Args: - input_file_as_a_dict: The input file as a dictionary. - output_file_path: The output file path. - - Returns: - The output file path. - """ - return _create_a_file_from_something( - input_file_as_a_dict, - read_a_python_dictionary_and_return_a_data_model, - renderer.create_a_typst_file, - output_file_path, - ) - - -def create_a_markdown_file_from_a_python_dictionary( - input_file_as_a_dict: dict, - output_file_path: pathlib.Path, -) -> Optional[list[dict]]: - """ - Validate the input dictionary, generate a Markdown file and save it to the output - file path. - - Args: - input_file_as_a_dict: The input file as a dictionary. - output_file_path: The output file path. - - Returns: - The output file path. - """ - return _create_a_file_from_something( - input_file_as_a_dict, - read_a_python_dictionary_and_return_a_data_model, - renderer.create_a_markdown_file, - output_file_path, - ) - - -def create_a_markdown_file_from_a_yaml_string( - yaml_file_as_string: str, - output_file_path: pathlib.Path, -) -> Optional[list[dict]]: - """ - Validate the input file given as a string, generate a Markdown file and save it to - the output file path. - - Args: - yaml_file_as_string: The input file as a string. - output_file_path: The output file path. - - Returns: - The output file path. - """ - return _create_a_file_from_something( - yaml_file_as_string, - read_a_yaml_string_and_return_a_data_model, - renderer.create_a_markdown_file, - output_file_path, - ) - - -def create_an_html_file_from_a_python_dictionary( - input_file_as_a_dict: dict, - output_file_path: pathlib.Path, -) -> Optional[list[dict]]: - """ - Validate the input dictionary, generate an HTML file and save it to the output file - path. - - Args: - input_file_as_a_dict: The input file as a dictionary. - output_file_path: The output file path. - - Returns: - The output file path. - """ - return _create_a_file_from_something( - input_file_as_a_dict, - read_a_python_dictionary_and_return_a_data_model, - lambda x, y: renderer.render_an_html_from_markdown( - renderer.create_a_markdown_file(x, y), - ), - output_file_path, - ) - - -def create_an_html_file_from_a_yaml_string( - yaml_file_as_string: str, - output_file_path: pathlib.Path, -) -> Optional[list[dict]]: - """ - Validate the input file given as a string, generate an HTML file and save it to the - output file path. - - Args: - yaml_file_as_string: The input file as a string. - output_file_path: The output file path. - - Returns: - The output file path. - """ - return _create_a_file_from_something( - yaml_file_as_string, - read_a_yaml_string_and_return_a_data_model, - lambda x, y: renderer.render_an_html_from_markdown( - renderer.create_a_markdown_file(x, y), - ), - output_file_path, - ) - - -def create_a_pdf_from_a_yaml_string( - yaml_file_as_string: str, - output_file_path: pathlib.Path, -) -> Optional[list[dict]]: - """ - Validate the input file given as a string, generate a PDF file and save it to the - output file path. - - Args: - yaml_file_as_string: The input file as a string. - output_file_path: The output file path. - - Returns: - The output file path. - """ - return _create_a_file_from_something( - yaml_file_as_string, - read_a_yaml_string_and_return_a_data_model, - lambda x, y: renderer.render_a_pdf_from_typst( - renderer.create_a_typst_file(x, y) - ), - output_file_path, - ) - - -def create_a_pdf_from_a_python_dictionary( - input_file_as_a_dict: dict, - output_file_path: pathlib.Path, -) -> Optional[list[dict]]: - """ - Validate the input dictionary, generate a PDF file and save it to the output file - path. - - Args: - input_file_as_a_dict: The input file as a dictionary. - output_file_path: The output file path. - - Returns: - The output file path. - """ - return _create_a_file_from_something( - input_file_as_a_dict, - read_a_python_dictionary_and_return_a_data_model, - lambda x, y: renderer.render_a_pdf_from_typst( - renderer.create_a_typst_file(x, y), - ), - output_file_path, - ) diff --git a/rendercv/cli/__init__.py b/rendercv/cli/__init__.py deleted file mode 100644 index 074ba7bf8..000000000 --- a/rendercv/cli/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -The `rendercv.cli` package contains the functions and classes that handle RenderCV's -command-line interface (CLI). It uses [Typer](https://typer.tiangolo.com/) to create the -CLI and [Rich](https://rich.readthedocs.io/en/latest/) to provide a nice-looking -interface. -""" - -try: - from .commands import ( - app, - cli_command_create_theme, - cli_command_new, - cli_command_no_args, - cli_command_render, - ) - - __all__ = [ - "app", - "cli_command_create_theme", - "cli_command_new", - "cli_command_no_args", - "cli_command_render", - ] -except ImportError: - from .. import _parial_install_error_message - - print(_parial_install_error_message) # noqa: T201 diff --git a/rendercv/cli/commands.py b/rendercv/cli/commands.py deleted file mode 100644 index 03406b595..000000000 --- a/rendercv/cli/commands.py +++ /dev/null @@ -1,358 +0,0 @@ -""" -The `rendercv.cli.commands` module contains all the command-line interface (CLI) -commands of RenderCV. -""" - -import copy -import pathlib -from typing import Annotated, Optional - -import typer -from rich import print - -from .. import __version__, data -from . import printer, utilities - -app = typer.Typer( - rich_markup_mode="rich", - add_completion=False, - # to make `rendercv --version` work: - invoke_without_command=True, - no_args_is_help=True, - context_settings={"help_option_names": ["-h", "--help"]}, - # don't show local variables in unhandled exceptions: - pretty_exceptions_show_locals=False, -) - - -@app.command( - name="render", - help=( - "Render a YAML input file. Example: [yellow]rendercv render" - " John_Doe_CV.yaml[/yellow]. Details: [cyan]rendercv render --help[/cyan]" - ), - # allow extra arguments for updating the data model (for overriding the values of - # the input file): - context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, -) -@printer.handle_and_print_raised_exceptions -def cli_command_render( - input_file_name: Annotated[str, typer.Argument(help="The YAML input file.")], - design: Annotated[ - Optional[str], - typer.Option( - "--design", - "-d", - help='The "design" field\'s YAML input file.', - ), - ] = None, - locale: Annotated[ - Optional[str], - typer.Option( - "--locale-catalog", - "-lc", - help='The "locale" field\'s YAML input file.', - ), - ] = None, - rendercv_settings: Annotated[ - Optional[str], - typer.Option( - "--rendercv-settings", - "-rs", - help='The "rendercv_settings" field\'s YAML input file.', - ), - ] = None, - output_folder_name: Annotated[ - str, - typer.Option( - "--output-folder-name", - "-o", - help="Name of the output folder", - ), - ] = "rendercv_output", - typst_path: Annotated[ - Optional[str], - typer.Option( - "--typst-path", - "-typst", - help="Copy the Typst file to the given path", - ), - ] = None, - pdf_path: Annotated[ - Optional[str], - typer.Option( - "--pdf-path", - "-pdf", - help="Copy the PDF file to the given path", - ), - ] = None, - markdown_path: Annotated[ - Optional[str], - typer.Option( - "--markdown-path", - "-md", - help="Copy the Markdown file to the given path", - ), - ] = None, - html_path: Annotated[ - Optional[str], - typer.Option( - "--html-path", - "-html", - help="Copy the HTML file to the given path", - ), - ] = None, - png_path: Annotated[ - Optional[str], - typer.Option( - "--png-path", - "-png", - help="Copy the PNG file to the given path", - ), - ] = None, - dont_generate_markdown: Annotated[ - bool, - typer.Option( - "--dont-generate-markdown", - "-nomd", - help="Don't generate the Markdown and HTML file", - ), - ] = False, - dont_generate_html: Annotated[ - bool, - typer.Option( - "--dont-generate-html", - "-nohtml", - help="Don't generate the HTML file", - ), - ] = False, - dont_generate_pdf: Annotated[ - bool, - typer.Option( - "--dont-generate-pdf", - "-nopdf", - help="Don't generate the PDF file", - ), - ] = False, - dont_generate_png: Annotated[ - bool, - typer.Option( - "--dont-generate-png", - "-nopng", - help="Don't generate the PNG file", - ), - ] = False, - watch: Annotated[ - bool, - typer.Option( - "--watch", - "-w", - help="Automatically re-run RenderCV when the input file is updated", - ), - ] = False, - # This is a dummy argument for the help message for - # extra_data_model_override_argumets: - _: Annotated[ - Optional[str], - typer.Option( - "--YAMLLOCATION", - help="Overrides the value of YAMLLOCATION. For example," - ' [cyan bold]--cv.phone "123-456-7890"[/cyan bold].', - ), - ] = None, - extra_data_model_override_arguments: typer.Context = None, # type: ignore -): - """Render a CV from a YAML input file.""" - printer.welcome() - original_working_directory = pathlib.Path.cwd() - input_file_path = pathlib.Path(input_file_name).absolute() - - from . import utilities as u - - argument_names = list(u.get_default_render_command_cli_arguments().keys()) - argument_names.remove("_") - argument_names.remove("extra_data_model_override_arguments") - # This is where the user input is accessed and stored: - variables = copy.copy(locals()) - cli_render_arguments = {name: variables[name] for name in argument_names} - - input_file_as_a_dict = u.read_and_construct_the_input( - input_file_path, cli_render_arguments, extra_data_model_override_arguments - ) - - watch = input_file_as_a_dict["rendercv_settings"]["render_command"]["watch"] - - if watch: - - @printer.handle_and_print_raised_exceptions_without_exit - def run_rendercv(): - input_file_as_a_dict = u.update_render_command_settings_of_the_input_file( - data.read_a_yaml_file(input_file_path), cli_render_arguments - ) - u.run_rendercv_with_printer( - input_file_as_a_dict, original_working_directory, input_file_path - ) - - u.run_a_function_if_a_file_changes(input_file_path, run_rendercv) - else: - u.run_rendercv_with_printer( - input_file_as_a_dict, original_working_directory, input_file_path - ) - - -@app.command( - name="new", - help=( - "Generate a YAML input file to get started. Example: [yellow]rendercv new" - ' "John Doe"[/yellow]. Details: [cyan]rendercv new --help[/cyan]' - ), -) -def cli_command_new( - full_name: Annotated[str, typer.Argument(help="Your full name")], - theme: Annotated[ - str, - typer.Option( - help=( - "The name of the theme (available themes are:" - f" {', '.join(data.available_themes)})" - ) - ), - ] = "classic", - dont_create_theme_source_files: Annotated[ - bool, - typer.Option( - "--dont-create-theme-source-files", - "-notypst", - help="Don't create theme source files", - ), - ] = False, - dont_create_markdown_source_files: Annotated[ - bool, - typer.Option( - "--dont-create-markdown-source-files", - "-nomd", - help="Don't create the Markdown source files", - ), - ] = False, -): - """Generate a YAML input file and the Typst and Markdown source files""" - created_files_and_folders = [] - - input_file_name = f"{full_name.replace(' ', '_')}_CV.yaml" - input_file_path = pathlib.Path(input_file_name) - - if input_file_path.exists(): - printer.warning( - f'The input file "{input_file_name}" already exists! A new input file is' - " not created" - ) - else: - try: - data.create_a_sample_yaml_input_file( - input_file_path, name=full_name, theme=theme - ) - created_files_and_folders.append(input_file_path.name) - except ValueError as e: - # if the theme is not in the available themes, then raise an error - printer.error(exception=e) - - if not dont_create_theme_source_files: - # copy the package's theme files to the current directory - theme_folder = utilities.copy_templates(theme, pathlib.Path.cwd()) - if theme_folder is not None: - created_files_and_folders.append(theme_folder.name) - else: - printer.warning( - f'The theme folder "{theme}" already exists! The theme files are not' - " created" - ) - - if not dont_create_markdown_source_files: - # copy the package's markdown files to the current directory - markdown_folder = utilities.copy_templates("markdown", pathlib.Path.cwd()) - if markdown_folder is not None: - created_files_and_folders.append(markdown_folder.name) - else: - printer.warning( - 'The "markdown" folder already exists! The Markdown files are not' - " created" - ) - - if len(created_files_and_folders) > 0: - created_files_and_folders_string = ",\n".join(created_files_and_folders) - printer.information( - "The following RenderCV input file and folders have been" - f" created:\n{created_files_and_folders_string}" - ) - - -@app.command( - name="create-theme", - help=( - "Create a custom theme folder based on an existing theme. Example:" - " [yellow]rendercv create-theme customtheme[/yellow]. Details: [cyan]rendercv" - " create-theme --help[/cyan]" - ), -) -def cli_command_create_theme( - theme_name: Annotated[ - str, - typer.Argument(help="The name of the new theme"), - ], - based_on: Annotated[ - str, - typer.Option( - help=( - "The name of the existing theme to base the new theme on (available" - f" themes are: {', '.join(data.available_themes)})" - ) - ), - ] = "classic", -): - """Create a custom theme based on an existing theme""" - if based_on not in data.available_themes: - printer.error( - f'The theme "{based_on}" is not in the list of available themes:' - f" {', '.join(data.available_themes)}" - ) - - theme_folder = utilities.copy_templates( - based_on, pathlib.Path.cwd(), new_folder_name=theme_name - ) - - if theme_folder is None: - printer.warning( - f'The theme folder "{theme_name}" already exists! The theme files are not' - " created" - ) - return - - based_on_theme_directory = ( - pathlib.Path(__file__).parent.parent / "themes" / based_on - ) - based_on_theme_init_file = based_on_theme_directory / "__init__.py" - based_on_theme_init_file_contents = based_on_theme_init_file.read_text() - - # generate the new init file: - class_name = f"{theme_name.capitalize()}ThemeOptions" - literal_name = f'Literal["{theme_name}"]' - new_init_file_contents = based_on_theme_init_file_contents.replace( - f'Literal["{based_on}"]', literal_name - ).replace(f"{based_on.capitalize()}ThemeOptions", class_name) - - # create the new __init__.py file: - (theme_folder / "__init__.py").write_text(new_init_file_contents) - - printer.information(f'The theme folder "{theme_folder.name}" has been created.') - - -@app.callback() -def cli_command_no_args( - version_requested: Annotated[ - Optional[bool], typer.Option("--version", "-v", help="Show the version") - ] = None, -): - if version_requested: - there_is_a_new_version = printer.warn_if_new_version_is_available() - if not there_is_a_new_version: - print(f"RenderCV v{__version__}") diff --git a/rendercv/cli/printer.py b/rendercv/cli/printer.py deleted file mode 100644 index cea1b6879..000000000 --- a/rendercv/cli/printer.py +++ /dev/null @@ -1,351 +0,0 @@ -""" -The `rendercv.cli.printer` module contains all the functions and classes that are used -to print nice-looking messages to the terminal. -""" - -import functools -from collections.abc import Callable -from typing import Optional - -import jinja2 -import packaging.version -import pydantic -import rich -import rich.live -import rich.panel -import rich.progress -import rich.table -import rich.text -import ruamel.yaml -import typer -from rich import print - -from .. import __version__, data -from . import utilities - - -class LiveProgressReporter(rich.live.Live): - """This class is a wrapper around `rich.live.Live` that provides the live progress - reporting functionality. - - Args: - number_of_steps: The number of steps to be finished. - end_message: The message to be printed when the progress is finished. Defaults - to "Your CV is rendered!". - """ - - def __init__(self, number_of_steps: int, end_message: str = "Your CV is rendered!"): - class TimeElapsedColumn(rich.progress.ProgressColumn): - def render(self, task: "rich.progress.Task") -> rich.text.Text: - elapsed = task.finished_time if task.finished else task.elapsed - assert elapsed is not None - elapsed = elapsed * 1000 # Convert to milliseconds - delta = f"{elapsed:.0f} ms" - return rich.text.Text(str(delta), style="progress.elapsed") - - self.step_progress = rich.progress.Progress( - TimeElapsedColumn(), rich.progress.TextColumn("{task.description}") - ) - - self.overall_progress = rich.progress.Progress( - TimeElapsedColumn(), - rich.progress.BarColumn(), - rich.progress.TextColumn("{task.description}"), - ) - - self.group = rich.console.Group( - rich.panel.Panel(rich.console.Group(self.step_progress)), - self.overall_progress, - ) - - self.overall_task_id = self.overall_progress.add_task("", total=number_of_steps) - self.number_of_steps = number_of_steps - self.end_message = end_message - self.current_step = 0 - self.overall_progress.update( - self.overall_task_id, - description=( - f"[bold #AAAAAA]({self.current_step} out of" - f" {self.number_of_steps} steps finished)" - ), - ) - super().__init__(self.group) - - def __enter__(self) -> "LiveProgressReporter": - """Overwrite the `__enter__` method for the correct return type.""" - self.start(refresh=self._renderable is not None) - return self - - def start_a_step(self, step_name: str): - """Start a step and update the progress bars. - - Args: - step_name: The name of the step. - """ - self.current_step_name = step_name - self.current_step_id = self.step_progress.add_task( - f"{self.current_step_name} has started." - ) - - def finish_the_current_step(self): - """Finish the current step and update the progress bars.""" - self.step_progress.stop_task(self.current_step_id) - self.step_progress.update( - self.current_step_id, description=f"{self.current_step_name} has finished." - ) - self.current_step += 1 - self.overall_progress.update( - self.overall_task_id, - description=( - f"[bold #AAAAAA]({self.current_step} out of" - f" {self.number_of_steps} steps finished)" - ), - advance=1, - ) - if self.current_step == self.number_of_steps: - self.end() - - def end(self): - """End the live progress reporting.""" - self.overall_progress.update( - self.overall_task_id, - description=f"[yellow]{self.end_message}", - ) - - -def warn_if_new_version_is_available() -> bool: - """Check if a new version of RenderCV is available and print a warning message if - there is a new version. Also, return True if there is a new version, and False - otherwise. - - Returns: - True if there is a new version, and False otherwise. - """ - latest_version = utilities.get_latest_version_number_from_pypi() - version = packaging.version.Version(__version__) - if latest_version is not None and version < latest_version: - warning( - f"A new version of RenderCV is available! You are using v{__version__}," - f" and the latest version is v{latest_version}." - ) - return True - return False - - -def welcome(): - """Print a welcome message to the terminal.""" - warn_if_new_version_is_available() - - table = rich.table.Table( - title=( - "\nWelcome to [bold]Render[dodger_blue3]CV[/dodger_blue3][/bold]! Some" - " useful links:" - ), - title_justify="left", - ) - - table.add_column("Title", style="magenta", justify="left") - table.add_column("Link", style="cyan", justify="right", no_wrap=True) - - table.add_row("[bold]RenderCV App", "https://rendercv.com") - table.add_row("Documentation", "https://docs.rendercv.com") - table.add_row("Source code", "https://github.com/rendercv/rendercv/") - table.add_row("Bug reports", "https://github.com/rendercv/rendercv/issues/") - table.add_row("Feature requests", "https://github.com/rendercv/rendercv/issues/") - table.add_row("Discussions", "https://github.com/rendercv/rendercv/discussions/") - table.add_row("RenderCV Pipeline", "https://github.com/rendercv/rendercv-pipeline/") - - print(table) - - -def warning(text: str): - """Print a warning message to the terminal. - - Args: - text: The text of the warning message. - """ - print(f"[bold yellow]{text}") - - -def error(text: Optional[str] = None, exception: Optional[Exception] = None): - """Print an error message to the terminal and exit the program. If an exception is - given, then print the exception's message as well. If neither text nor exception is - given, then print an empty line and exit the program. - - Args: - text: The text of the error message. - exception: An exception object. Defaults to None. - """ - if exception is not None: - exception_messages = [str(arg) for arg in exception.args] - exception_message = "\n\n".join(exception_messages) - if text is None: - text = "An error occurred:" - - print( - f"\n[bold red]{text}[/bold red]\n\n[orange4]{exception_message}[/orange4]\n" - ) - elif text is not None: - print(f"\n[bold red]{text}\n") - else: - print() - - -def information(text: str): - """Print an information message to the terminal. - - Args: - text: The text of the information message. - """ - print(f"[green]{text}") - - -def print_validation_errors(exception: pydantic.ValidationError): - """Take a Pydantic validation error and print the error messages in a nice table. - - Pydantic's `ValidationError` object is a complex object that contains a lot of - information about the error. This function takes a `ValidationError` object and - extracts the error messages, locations, and the input values. Then, it prints them - in a nice table with [Rich](https://rich.readthedocs.io/en/latest/). - - Args: - exception: The Pydantic validation error object. - """ - errors = data.parse_validation_errors(exception) - - # Print the errors in a nice table: - table = rich.table.Table( - title="[bold red]\nThere are some errors in the data model!\n", - title_justify="left", - show_lines=True, - ) - table.add_column("Location", style="cyan", no_wrap=True) - table.add_column("Input Value", style="magenta") - table.add_column("Error Message", style="orange4") - - for error_object in errors: - table.add_row( - ".".join(error_object["loc"]), - error_object["input"], - error_object["msg"], - ) - - print(table) - - -def handle_and_print_raised_exceptions_without_exit(function: Callable) -> Callable: - """Return a wrapper function that handles exceptions. It does not exit the program - after an exception is raised. It just prints the error message and continues the - execution. - - A decorator in Python is a syntactic convenience that allows a Python to interpret - the code below: - - ```python - @handle_exceptions - def my_function(): - pass - ``` - - as - - ```python - my_function = handle_exceptions(my_function) - ``` - - which means that the function `my_function` is modified by the `handle_exceptions`. - - Args: - function: The function to be wrapped. - - Returns: - The wrapped function. - """ - - @functools.wraps(function) - def wrapper(*args, **kwargs): - code = 4 - try: - function(*args, **kwargs) - except pydantic.ValidationError as e: - print_validation_errors(e) - except ruamel.yaml.YAMLError as e: - error( - "There is a YAML error in the input file!\n\nTry to use quotation marks" - " to make sure the YAML parser understands the field is a string.", - e, - ) - except FileNotFoundError as e: - error(exception=e) - except UnicodeDecodeError as e: - # find the problematic character that cannot be decoded with utf-8 - bad_character = str(e.object[e.start : e.end]) - try: - bad_character_context = str(e.object[e.start - 16 : e.end + 16]) - except IndexError: - bad_character_context = "" - - error( - "The input file contains a character that cannot be decoded with" - f" UTF-8 ({bad_character}):\n {bad_character_context}", - ) - except ValueError as e: - error(exception=e) - except typer.Exit: - pass - except jinja2.exceptions.TemplateSyntaxError as e: - error( - f"There is a problem with the template ({e.filename}) at line" - f" {e.lineno}!", - e, - ) - except RuntimeError as e: - error(exception=e) - except Exception as e: - raise e - else: - code = 0 - - return code - - return wrapper - - -def handle_and_print_raised_exceptions(function: Callable) -> Callable: - """Return a wrapper function that handles exceptions. It exits the program after an - exception is raised. - - A decorator in Python is a syntactic convenience that allows a Python to interpret - the code below: - - ```python - @handle_exceptions - def my_function(): - pass - ``` - - as - - ```python - my_function = handle_exceptions(my_function) - ``` - - which means that the function `my_function` is modified by the `handle_exceptions`. - - Args: - function: The function to be wrapped. - - Returns: - The wrapped function. - """ - - @functools.wraps(function) - def wrapper(*args, **kwargs): - without_exit_wrapper = handle_and_print_raised_exceptions_without_exit(function) - - code = without_exit_wrapper(*args, **kwargs) - - if code != 0: - raise typer.Exit(code) - - return wrapper diff --git a/rendercv/cli/utilities.py b/rendercv/cli/utilities.py deleted file mode 100644 index f02cb3444..000000000 --- a/rendercv/cli/utilities.py +++ /dev/null @@ -1,508 +0,0 @@ -""" -The `rendercv.cli.utilities` module contains utility functions that are required by CLI. -""" - -import inspect -import json -import os -import pathlib -import shutil -import sys -import time -import urllib.request -from collections.abc import Callable -from typing import Any, Optional - -import packaging.version -import typer -import watchdog.events -import watchdog.observers - -from .. import data, renderer -from . import printer - - -def set_or_update_a_value( - dictionary: dict, - key: str, - value: str, - sub_dictionary: Optional[dict | list] = None, -) -> dict: # type: ignore - """Set or update a value in a dictionary for the given key. For example, a key can - be `cv.sections.education.3.institution` and the value can be "Bogazici University". - - Args: - dictionary: The dictionary to set or update the value. - key: The key to set or update the value. - value: The value to set or update. - sub_dictionary: The sub dictionary to set or update the value. This is used for - recursive calls. Defaults to None. - """ - # Recursively call this function until the last key is reached: - - keys = key.split(".") - - updated_dict = sub_dictionary if sub_dictionary is not None else dictionary - - if len(keys) == 1: - # Set the value: - if value.startswith("{") and value.endswith("}"): - # Allow users to assign dictionaries: - value = eval(value) - elif value.startswith("[") and value.endswith("]"): - # Allow users to assign lists: - value = eval(value) - - if isinstance(updated_dict, list): - key = int(key) # type: ignore - - updated_dict[key] = value # type: ignore - - else: - # get the first key and call the function with remaining keys: - first_key = keys[0] - key = ".".join(keys[1:]) - - if isinstance(updated_dict, list): - first_key = int(first_key) - - if isinstance(first_key, int) or first_key in updated_dict: - # Key exists, get the sub dictionary: - sub_dictionary = updated_dict[first_key] # type: ignore - else: - # Key does not exist, create a new sub dictionary: - sub_dictionary = {} - - updated_sub_dict = set_or_update_a_value(dictionary, key, value, sub_dictionary) - updated_dict[first_key] = updated_sub_dict # type: ignore - - return updated_dict # type: ignore - - -def set_or_update_values( - dictionary: dict, - key_and_values: dict[str, str], -) -> dict: - """Set or update values in a dictionary for the given keys. It uses the - `set_or_update_a_value` function to set or update the values. - - Args: - dictionary: The dictionary to set or update the values. - key_and_values: The key and value pairs to set or update. - """ - for key, value in key_and_values.items(): - dictionary = set_or_update_a_value(dictionary, key, value) # type: ignore - - return dictionary - - -def copy_files(paths: list[pathlib.Path] | pathlib.Path, new_path: pathlib.Path): - """Copy files to the given path. If there are multiple files, then rename the new - path by adding a number to the end of the path. - - Args: - paths: The paths of the files to be copied. - new_path: The path to copy the files to. - """ - if isinstance(paths, pathlib.Path): - paths = [paths] - - if len(paths) == 1: - shutil.copy2(paths[0], new_path) - else: - for i, file_path in enumerate(paths): - # append a number to the end of the path: - number = i + 1 - png_path_with_page_number = ( - pathlib.Path(new_path).parent - / f"{pathlib.Path(new_path).stem}_{number}.png" - ) - shutil.copy2(file_path, png_path_with_page_number) - - -def get_latest_version_number_from_pypi() -> Optional[packaging.version.Version]: - """Get the latest version number of RenderCV from PyPI. - - Example: - ```python - get_latest_version_number_from_pypi() - ``` - returns - `"1.1"` - - Returns: - The latest version number of RenderCV from PyPI. Returns None if the version - number cannot be fetched. - """ - version = None - url = "https://pypi.org/pypi/rendercv/json" - try: - with urllib.request.urlopen(url) as response: - data = response.read() - encoding = response.info().get_content_charset("utf-8") - json_data = json.loads(data.decode(encoding)) - version_string = json_data["info"]["version"] - version = packaging.version.Version(version_string) - except Exception: - pass - - return version - - -def copy_templates( - folder_name: str, - copy_to: pathlib.Path, - new_folder_name: Optional[str] = None, -) -> Optional[pathlib.Path]: - """Copy one of the folders found in `rendercv.templates` to `copy_to`. - - Args: - folder_name: The name of the folder to be copied. - copy_to: The path to copy the folder to. - - Returns: - The path to the copied folder. - """ - # copy the package's theme files to the current directory - template_directory = pathlib.Path(__file__).parent.parent / "themes" / folder_name - if new_folder_name: - destination = copy_to / new_folder_name - else: - destination = copy_to / folder_name - - if destination.exists(): - return None - # copy the folder but don't include __init__.py: - shutil.copytree( - template_directory, - destination, - ignore=shutil.ignore_patterns("__init__.py", "__pycache__"), - ) - - return destination - - -def parse_render_command_override_arguments( - extra_arguments: typer.Context, -) -> dict["str", "str"]: - """Parse extra arguments given to the `render` command as data model key and value - pairs and return them as a dictionary. - - Args: - extra_arguments: The extra arguments context. - - Returns: - The key and value pairs. - """ - key_and_values: dict[str, str] = {} - - # `extra_arguments.args` is a list of arbitrary arguments that haven't been - # specified in `cli_render_command` function's definition. They are used to allow - # users to edit their data model in CLI. The elements with even indexes in this list - # are keys that start with double dashed, such as - # `--cv.sections.education.0.institution`. The following elements are the - # corresponding values of the key, such as `"Bogazici University"`. The for loop - # below parses `ctx.args` accordingly. - - if len(extra_arguments.args) % 2 != 0: - message = ( - "There is a problem with the extra arguments" - f" ({','.join(extra_arguments.args)})! Each key should have a corresponding" - " value." - ) - raise ValueError(message) - - for i in range(0, len(extra_arguments.args), 2): - key = extra_arguments.args[i] - value = extra_arguments.args[i + 1] - if not key.startswith("--"): - message = f"The key ({key}) should start with double dashes!" - raise ValueError(message) - - key = key.replace("--", "") - - key_and_values[key] = value - - return key_and_values - - -def get_default_render_command_cli_arguments() -> dict: - """Get the default values of the `render` command's CLI arguments. - - Returns: - The default values of the `render` command's CLI arguments. - """ - from .commands import cli_command_render - - sig = inspect.signature(cli_command_render) - return { - k: v.default - for k, v in sig.parameters.items() - if v.default is not inspect.Parameter.empty - } - - -def update_render_command_settings_of_the_input_file( - input_file_as_a_dict: dict, - render_command_cli_arguments: dict, -) -> dict: - """Update the input file's `rendercv_settings.render_command` field with the given - values (only the non-default ones) of the `render` command's CLI arguments. - - Args: - input_file_as_a_dict: The input file as a dictionary. - render_command_cli_arguments: The command line arguments of the `render` - command. - - Returns: - The updated input file as a dictionary. - """ - default_render_command_cli_arguments = get_default_render_command_cli_arguments() - - # Loop through `render_command_cli_arguments` and if the value is not the default - # value, overwrite the value in the input file's `rendercv_settings.render_command` - # field. If the field is the default value, check if it exists in the input file. - # If it doesn't exist, add it to the input file. If it exists, don't do anything. - if "rendercv_settings" not in input_file_as_a_dict: - input_file_as_a_dict["rendercv_settings"] = {} - - if ( - "render_command" not in input_file_as_a_dict["rendercv_settings"] - or input_file_as_a_dict["rendercv_settings"]["render_command"] is None - ): - input_file_as_a_dict["rendercv_settings"]["render_command"] = {} - - render_command_field = input_file_as_a_dict["rendercv_settings"]["render_command"] - for key, value in render_command_cli_arguments.items(): - if ( - value != default_render_command_cli_arguments[key] - or key not in render_command_field - ): - render_command_field[key] = value - - input_file_as_a_dict["rendercv_settings"]["render_command"] = render_command_field - - return input_file_as_a_dict - - -def run_rendercv_with_printer( - input_file_as_a_dict: dict, - working_directory: pathlib.Path, - input_file_path: pathlib.Path, -): - """Run RenderCV with a live progress reporter. Working dictionary is where the - output files will be saved. Input file path is required for accessing the template - overrides. - - Args: - input_file_as_a_dict: The input file as a dictionary. - working_directory: The working directory where the output files will be saved. - input_file_path: The path of the input file. - """ - render_command_settings_dict = input_file_as_a_dict["rendercv_settings"][ - "render_command" - ] - - # Compute the number of steps - # 1. Validate the input file. - # 2. Create the Typst file. - # 3. Render PDF from Typst. - # 4. Render PNGs from PDF. - # 5. Create the Markdown file. - # 6. Render HTML from Markdown. - number_of_steps = 6 - if render_command_settings_dict["dont_generate_pdf"]: - number_of_steps -= 1 - - if render_command_settings_dict["dont_generate_png"]: - number_of_steps -= 1 - - if render_command_settings_dict["dont_generate_markdown"]: - number_of_steps -= 2 - else: - if render_command_settings_dict["dont_generate_html"]: - number_of_steps -= 1 - - with printer.LiveProgressReporter(number_of_steps=number_of_steps) as progress: - progress.start_a_step("Validating the input file") - - data_model = data.validate_input_dictionary_and_return_the_data_model( - input_file_as_a_dict, - context={"input_file_directory": input_file_path.parent}, - ) - - # Change the current working directory to the input file's directory (because - # the template overrides are looked up in the current working directory). The - # output files will be in the original working directory. It should be done - # after the input file is validated (because of the rendercv_settings). - os.chdir(input_file_path.parent) - - render_command_settings: data.models.RenderCommandSettings = ( - data_model.rendercv_settings.render_command # type: ignore - ) - output_directory = ( - working_directory / render_command_settings.output_folder_name - ) - - progress.finish_the_current_step() - - progress.start_a_step("Generating the Typst file") - - typst_file_path_in_output_folder = ( - renderer.create_a_typst_file_and_copy_theme_files( - data_model, output_directory - ) - ) - if render_command_settings.typst_path: - copy_files( - typst_file_path_in_output_folder, - render_command_settings.typst_path, - ) - - progress.finish_the_current_step() - - if not render_command_settings.dont_generate_pdf: - progress.start_a_step("Rendering the Typst file to a PDF") - - pdf_file_path_in_output_folder = renderer.render_a_pdf_from_typst( - typst_file_path_in_output_folder, - ) - if render_command_settings.pdf_path: - copy_files( - pdf_file_path_in_output_folder, - render_command_settings.pdf_path, - ) - - progress.finish_the_current_step() - - if not render_command_settings.dont_generate_png: - progress.start_a_step("Rendering PNG files from the PDF") - - png_file_paths_in_output_folder = renderer.render_pngs_from_typst( - typst_file_path_in_output_folder - ) - if render_command_settings.png_path: - copy_files( - png_file_paths_in_output_folder, - render_command_settings.png_path, - ) - - progress.finish_the_current_step() - - if not render_command_settings.dont_generate_markdown: - progress.start_a_step("Generating the Markdown file") - - markdown_file_path_in_output_folder = renderer.create_a_markdown_file( - data_model, output_directory - ) - if render_command_settings.markdown_path: - copy_files( - markdown_file_path_in_output_folder, - render_command_settings.markdown_path, - ) - - progress.finish_the_current_step() - - if not render_command_settings.dont_generate_html: - progress.start_a_step( - "Rendering the Markdown file to a HTML (for Grammarly)" - ) - - html_file_path_in_output_folder = renderer.render_an_html_from_markdown( - markdown_file_path_in_output_folder - ) - if render_command_settings.html_path: - copy_files( - html_file_path_in_output_folder, - render_command_settings.html_path, - ) - - progress.finish_the_current_step() - - -def run_a_function_if_a_file_changes(file_path: pathlib.Path, function: Callable): - """Watch the file located at `file_path` and call the `function` when the file is - modified. The function should not take any arguments. - - Args: - file_path (pathlib.Path): The path of the file to watch for. - function (Callable): The function to be called on file modification. - """ - # Run the function immediately for the first time - function() - - path_to_watch = str(file_path.absolute()) - if sys.platform == "win32": - # Windows does not support single file watching, so we watch the directory - path_to_watch = str(file_path.parent.absolute()) - - class EventHandler(watchdog.events.FileSystemEventHandler): - def __init__(self, function: Callable): - super().__init__() - self.function_to_call = function - - def on_modified(self, event: watchdog.events.FileModifiedEvent) -> None: - if event.src_path != str(file_path.absolute()): - return - - printer.information( - "\n\nThe input file has been updated. Re-running RenderCV..." - ) - self.function_to_call() - - event_handler = EventHandler(function) - - observer = watchdog.observers.Observer() - observer.schedule(event_handler, path_to_watch, recursive=True) - observer.start() - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - observer.stop() - observer.join() - - -def read_and_construct_the_input( - input_file_path: pathlib.Path, - cli_render_arguments: dict[str, Any], - extra_data_model_override_arguments: Optional[typer.Context] = None, -) -> dict: - """Read RenderCV YAML files and CLI to construct the user's input as a dictionary. - Input file is read, CLI arguments override the input file, and individual design, - locale catalog, etc. files are read if they are provided. - - Args: - input_file_path: The path of the input file. - cli_render_arguments: The command line arguments of the `render` command. - extra_data_model_override_arguments: The extra arguments context. Defaults to - None. - - Returns: - The input of the user as a dictionary. - """ - input_file_as_a_dict = data.read_a_yaml_file(input_file_path) - - # Read individual `design`, `locale`, etc. files if they are provided in the - # input file: - for field in data.rendercv_data_model_fields: - if field in cli_render_arguments and cli_render_arguments[field] is not None: - yaml_path = pathlib.Path(cli_render_arguments[field]).absolute() - yaml_file_as_a_dict = data.read_a_yaml_file(yaml_path) - input_file_as_a_dict[field] = yaml_file_as_a_dict[field] - - # Update the input file if there are extra override arguments (for example, - # --cv.phone "123-456-7890"): - if extra_data_model_override_arguments: - key_and_values = parse_render_command_override_arguments( - extra_data_model_override_arguments - ) - input_file_as_a_dict = set_or_update_values( - input_file_as_a_dict, key_and_values - ) - - # If non-default CLI arguments are provided, override the - # `rendercv_settings.render_command`: - return update_render_command_settings_of_the_input_file( - input_file_as_a_dict, cli_render_arguments - ) diff --git a/rendercv/data/__init__.py b/rendercv/data/__init__.py deleted file mode 100644 index 9fd82cca0..000000000 --- a/rendercv/data/__init__.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -The `rendercv.data` package contains the necessary classes and functions for - -- Parsing and validating a YAML input file -- Computing some properties based on a YAML input file (like converting ISO dates to - plain English, URLs of social networks, etc.) -- Generating a JSON Schema for RenderCV's data format -- Generating a sample YAML input file - -The validators and data format of RenderCV are written using -[Pydantic](https://github.com/pydantic/pydantic). -""" - -from .generator import ( - create_a_sample_data_model, - create_a_sample_yaml_input_file, - generate_json_schema, - generate_json_schema_file, -) -from .models import ( - BulletEntry, - CurriculumVitae, - EducationEntry, - Entry, - ExperienceEntry, - Locale, - NormalEntry, - NumberedEntry, - OneLineEntry, - PublicationEntry, - RenderCommandSettings, - RenderCVDataModel, - RenderCVSettings, - ReversedNumberedEntry, - SectionContents, - SocialNetwork, - available_entry_models, - available_entry_type_names, - available_social_networks, - available_theme_options, - available_themes, - format_date, - get_date_input, - make_a_url_clean, - make_keywords_bold_in_a_string, - rendercv_data_model_fields, -) -from .reader import ( - get_error_message_and_location_and_value_from_a_custom_error, - parse_validation_errors, - read_a_yaml_file, - read_input_file, - validate_input_dictionary_and_return_the_data_model, -) - -__all__ = [ - "BulletEntry", - "CurriculumVitae", - "EducationEntry", - "Entry", - "ExperienceEntry", - "Locale", - "NormalEntry", - "NumberedEntry", - "OneLineEntry", - "PublicationEntry", - "RenderCVDataModel", - "RenderCVSettings", - "RenderCommandSettings", - "ReversedNumberedEntry", - "SectionContents", - "SocialNetwork", - "available_entry_models", - "available_entry_type_names", - "available_social_networks", - "available_theme_options", - "available_themes", - "create_a_sample_data_model", - "create_a_sample_yaml_input_file", - "format_date", - "generate_json_schema", - "generate_json_schema_file", - "get_date_input", - "get_error_message_and_location_and_value_from_a_custom_error", - "make_a_url_clean", - "make_keywords_bold_in_a_string", - "parse_validation_errors", - "read_a_yaml_file", - "read_input_file", - "rendercv_data_model_fields", - "validate_input_dictionary_and_return_the_data_model", -] diff --git a/rendercv/data/generator.py b/rendercv/data/generator.py deleted file mode 100644 index 63ee45b5b..000000000 --- a/rendercv/data/generator.py +++ /dev/null @@ -1,186 +0,0 @@ -""" -The `rendercv.data.generator` module contains all the functions for generating the JSON -Schema of the input data format and a sample YAML input file. -""" - -import io -import json -import pathlib -from typing import Optional - -import pydantic -import ruamel.yaml - -from .. import __version__ -from . import models, reader - - -def dictionary_to_yaml(dictionary: dict) -> str: - """Converts a dictionary to a YAML string. - - Args: - dictionary: The dictionary to be converted to YAML. - - Returns: - The YAML string. - """ - - # Source: https://gist.github.com/alertedsnake/c521bc485b3805aa3839aef29e39f376 - def str_representer(dumper, data): - if len(data.splitlines()) > 1: # check for multiline string - return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") - return dumper.represent_scalar("tag:yaml.org,2002:str", data) - - yaml_object = ruamel.yaml.YAML() - yaml_object.encoding = "utf-8" - yaml_object.width = 9999 - yaml_object.indent(mapping=2, sequence=4, offset=2) - yaml_object.representer.add_representer(str, str_representer) - - with io.StringIO() as string_stream: - yaml_object.dump(dictionary, string_stream) - return string_stream.getvalue() - - -def create_a_sample_data_model( - name: str = "John Doe", theme: str = "classic" -) -> models.RenderCVDataModel: - """Return a sample data model for new users to start with. - - Args: - name: The name of the person. Defaults to "John Doe". - - Returns: - A sample data model. - """ - # Check if the theme is valid: - if theme not in models.available_theme_options: - available_themes_string = ", ".join(models.available_theme_options.keys()) - message = ( - f"The theme should be one of the following: {available_themes_string}!" - f' The provided theme is "{theme}".' - ) - raise ValueError(message) - - # read the sample_content.yaml file - sample_content = pathlib.Path(__file__).parent / "sample_content.yaml" - sample_content_dictionary = reader.read_a_yaml_file(sample_content) - cv = models.CurriculumVitae(**sample_content_dictionary) - - # Update the name: - name = name.encode().decode("unicode-escape") - cv.name = name - - design = models.available_theme_options[theme](theme=theme) - - return models.RenderCVDataModel(cv=cv, design=design) - - -def create_a_sample_yaml_input_file( - input_file_path: Optional[pathlib.Path] = None, - name: str = "John Doe", - theme: str = "classic", -) -> str: - """Create a sample YAML input file and return it as a string. If the input file path - is provided, then also save the contents to the file. - - Args: - input_file_path: The path to save the input file. Defaults to None. - name: The name of the person. Defaults to "John Doe". - theme: The theme of the CV. Defaults to "classic". - - Returns: - The sample YAML input file as a string. - """ - data_model = create_a_sample_data_model(name=name, theme=theme) - - # Instead of getting the dictionary with data_model.model_dump() directly, we - # convert it to JSON and then to a dictionary. Because the YAML library we are - # using sometimes has problems with the dictionary returned by model_dump(). - - # We exclude "cv.sections" because the data model automatically generates them. - # The user's "cv.sections" input is actually "cv.sections_input" in the data - # model. It is shown as "cv.sections" in the YAML file because an alias is being - # used. If"cv.sections" were not excluded, the automatically generated - # "cv.sections" would overwrite the "cv.sections_input". "cv.sections" are - # automatically generated from "cv.sections_input" to make the templating - # process easier. "cv.sections_input" exists for the convenience of the user. - # Also, we don't want to show the cv.photo field in the Web app. - data_model_as_json = data_model.model_dump_json( - exclude_none=False, - by_alias=True, - exclude={ - "cv": {"sections", "photo"}, - "rendercv_settings": {"render_command"}, - }, - ) - data_model_as_dictionary = json.loads(data_model_as_json) - - yaml_string = dictionary_to_yaml(data_model_as_dictionary) - - # Add a comment to the first line, for JSON Schema: - comment_to_add = ( - "# yaml-language-server:" - f" $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v{__version__}/schema.json\n" - ) - yaml_string = comment_to_add + yaml_string - - if input_file_path is not None: - input_file_path.write_text(yaml_string, encoding="utf-8") - - return yaml_string - - -def generate_json_schema() -> dict: - """Generate the JSON schema of RenderCV. - - JSON schema is generated for the users to make it easier for them to write the input - file. The JSON Schema of RenderCV is saved in the root directory of the repository - and distributed to the users with the - [JSON Schema Store](https://www.schemastore.org/). - - Returns: - The JSON schema of RenderCV. - """ - - class RenderCVSchemaGenerator(pydantic.json_schema.GenerateJsonSchema): - def generate(self, schema, mode="validation"): # type: ignore - json_schema = super().generate(schema, mode=mode) - - # Basic information about the schema: - json_schema["title"] = "RenderCV" - json_schema["description"] = "RenderCV data model." - json_schema["$id"] = ( - "https://raw.githubusercontent.com/rendercv/rendercv/main/schema.json" - ) - json_schema["$schema"] = "http://json-schema.org/draft-07/schema#" - - # Loop through $defs and remove docstring descriptions and fix optional - # fields - for _, value in json_schema["$defs"].items(): - for _, field in value["properties"].items(): - if "anyOf" in field: - field["oneOf"] = field["anyOf"] - del field["anyOf"] - - if "description" in value and value["description"].startswith( - "This class is" - ): - del value["description"] - - return json_schema - - return models.RenderCVDataModel.model_json_schema( - schema_generator=RenderCVSchemaGenerator - ) - - -def generate_json_schema_file(json_schema_path: pathlib.Path): - """Generate the JSON schema of RenderCV and save it to a file. - - Args: - json_schema_path: The path to save the JSON schema. - """ - schema = generate_json_schema() - schema_json = json.dumps(schema, indent=2, ensure_ascii=False) - json_schema_path.write_text(schema_json, encoding="utf-8") diff --git a/rendercv/data/models/__init__.py b/rendercv/data/models/__init__.py deleted file mode 100644 index fae6724f3..000000000 --- a/rendercv/data/models/__init__.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -The `rendercv.data.models` package contains all the Pydantic data models, validators, -and computed fields that are used in RenderCV. The package is divided into several -modules, each containing a different group of data models. - -- `base.py`: Contains `RenderCVBaseModel`, which is the parent class of all the data - models in RenderCV. -- `computers.py`: Contains all the functions that are used to compute some values of - the fields in the data models. For example, converting ISO dates to human-readable - dates. -- `entry_types.py`: Contains all the data models that are used to represent the entries - in the CV. -- `curriculum_vitae.py`: Contains the `CurriculumVitae` data model, which is the main - data model that contains all the content of the CV. -- `design.py`: Contains the data model that is used to represent the design options of - the CV. -- `locale.py`: Contains the data model that is used to represent the locale - catalog of the CV. -- `rendercv_settings.py`: Contains the data model that is used to represent the settings - of the RenderCV. -- `rendercv_data_model.py`: Contains the `RenderCVDataModel` data model, which is the - main data model that defines the whole input file structure. -""" - -import warnings - -from .computers import format_date, get_date_input, make_a_url_clean -from .curriculum_vitae import ( - CurriculumVitae, - SectionContents, - Sections, - SocialNetwork, - available_social_networks, -) -from .design import ( - available_theme_options, - available_themes, -) -from .entry_types import ( - BulletEntry, - EducationEntry, - Entry, - ExperienceEntry, - NormalEntry, - NumberedEntry, - OneLineEntry, - PublicationEntry, - ReversedNumberedEntry, - available_entry_models, - available_entry_type_names, - make_keywords_bold_in_a_string, -) -from .locale import Locale -from .rendercv_data_model import RenderCVDataModel, rendercv_data_model_fields -from .rendercv_settings import RenderCommandSettings, RenderCVSettings - -warnings.filterwarnings("ignore") - -__all__ = [ - "BulletEntry", - "CurriculumVitae", - "EducationEntry", - "Entry", - "ExperienceEntry", - "Locale", - "NormalEntry", - "NumberedEntry", - "OneLineEntry", - "PublicationEntry", - "RenderCVDataModel", - "RenderCVSettings", - "RenderCommandSettings", - "ReversedNumberedEntry", - "SectionContents", - "Sections", - "SocialNetwork", - "available_entry_models", - "available_entry_type_names", - "available_social_networks", - "available_theme_options", - "available_themes", - "format_date", - "get_date_input", - "make_a_url_clean", - "make_keywords_bold_in_a_string", - "rendercv_data_model_fields", -] diff --git a/rendercv/data/models/base.py b/rendercv/data/models/base.py deleted file mode 100644 index f6043f5c2..000000000 --- a/rendercv/data/models/base.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -The `rendercv.data.models.base` module contains the parent classes of all the data -models in RenderCV. -""" - -import pydantic - - -class RenderCVBaseModelWithoutExtraKeys(pydantic.BaseModel): - """This class is the parent class of the data models that do not allow extra keys. - It has only one difference from the default `pydantic.BaseModel`: It raises an error - if an unknown key is provided in the input file. - """ - - model_config = pydantic.ConfigDict(extra="forbid", validate_default=True) - - -class RenderCVBaseModelWithExtraKeys(pydantic.BaseModel): - """This class is the parent class of the data models that allow extra keys. It has - only one difference from the default `pydantic.BaseModel`: It allows extra keys in - the input file. - """ - - model_config = pydantic.ConfigDict(extra="allow", validate_default=True) diff --git a/rendercv/data/models/computers.py b/rendercv/data/models/computers.py deleted file mode 100644 index 5d3b2ef0a..000000000 --- a/rendercv/data/models/computers.py +++ /dev/null @@ -1,440 +0,0 @@ -""" -The `rendercv.data.models.computers` module contains functions that compute some -properties based on the input data. For example, it includes functions that calculate -the time span between two dates, the date string, the URL of a social network, etc. -""" - -import pathlib -import re -from datetime import date as Date -from typing import Optional - -import phonenumbers - -from .curriculum_vitae import curriculum_vitae -from .locale import locale - - -def format_phone_number(phone_number: str) -> str: - """Format a phone number to the format specified in the `locale` dictionary. - - Example: - ```python - format_phone_number("+17034800500") - ``` - returns - ```python - "(703) 480-0500" - ``` - - Args: - phone_number: The phone number to format. - - Returns: - The formatted phone number. - """ - - format = locale["phone_number_format"].upper() # type: ignore - - parsed_number = phonenumbers.parse(phone_number, None) - return phonenumbers.format_number( - parsed_number, getattr(phonenumbers.PhoneNumberFormat, format) - ) - - -def get_date_input() -> Date: - """Return the date input. - - Returns: - The date input. - """ - from .rendercv_settings import DATE_INPUT - - return DATE_INPUT - - -def format_date(date: Date, date_template: Optional[str] = None) -> str: - """Formats a `Date` object to a string in the following format: "Jan 2021". The - month names are taken from the `locale` dictionary from the - `rendercv.data_models.models` module. - - Example: - ```python - format_date(Date(2024, 5, 1)) - ``` - will return - - `"May 2024"` - - Args: - date: The date to format. - date_template: The template of the date string. If not provided, the default date - style from the `locale` dictionary will be used. - - Returns: - The formatted date. - """ - full_month_names = locale["full_names_of_months"] - short_month_names = locale["abbreviations_for_months"] - - month = int(date.strftime("%m")) - year = date.strftime(format="%Y") - - placeholders = { - "FULL_MONTH_NAME": full_month_names[month - 1], - "MONTH_ABBREVIATION": short_month_names[month - 1], - "MONTH_IN_TWO_DIGITS": f"{month:02d}", - "YEAR_IN_TWO_DIGITS": str(year[-2:]), - "MONTH": str(month), - "YEAR": str(year), - } - if date_template is None: - date_template = locale["date_template"] # type: ignore - - assert isinstance(date_template, str) - - for placeholder, value in placeholders.items(): - date_template = date_template.replace(placeholder, value) # type: ignore - - return date_template - - -def replace_placeholders(value: str) -> str: - """Replaces the placeholders in a string with the corresponding values.""" - name = curriculum_vitae.get("name", "None") - full_month_names = locale["full_names_of_months"] - short_month_names = locale["abbreviations_for_months"] - - date_input = get_date_input() - month = date_input.month - year = str(date_input.year) - - name_snake_case = name.replace(" ", "_") - name_kebab_case = name.replace(" ", "-") - - placeholders = ( - ("NAME_IN_SNAKE_CASE", name_snake_case), - ("NAME_IN_LOWER_SNAKE_CASE", name_snake_case.lower()), - ("NAME_IN_UPPER_SNAKE_CASE", name_snake_case.upper()), - ("NAME_IN_KEBAB_CASE", name_kebab_case), - ("NAME_IN_LOWER_KEBAB_CASE", name_kebab_case.lower()), - ("NAME_IN_UPPER_KEBAB_CASE", name_kebab_case.upper()), - ("FULL_MONTH_NAME", full_month_names[month - 1]), - ("MONTH_ABBREVIATION", short_month_names[month - 1]), - ("MONTH_IN_TWO_DIGITS", f"{month:02d}"), - ("YEAR_IN_TWO_DIGITS", year[-2:]), - ("NAME", name), - ("YEAR", year), - ("MONTH", str(month)), - ) - - for placeholder, placeholder_value in placeholders: - value = value.replace(placeholder, placeholder_value) - - return value - - -def convert_string_to_path(value: str) -> pathlib.Path: - """Converts a string to a `pathlib.Path` object by replacing the placeholders - with the corresponding values. If the path is not an absolute path, it is - converted to an absolute path by prepending the current working directory. - """ - value = replace_placeholders(value) - - return pathlib.Path(value).absolute() - - -def compute_time_span_string( - start_date: Optional[str | int], - end_date: Optional[str | int], - date: Optional[str | int], -) -> str: - """ - Return a time span string based on the provided dates. - - Example: - ```python - get_time_span_string("2020-01-01", "2020-05-01", None) - ``` - - returns - - `"4 months"` - - Args: - start_date: A start date in YYYY-MM-DD, YYYY-MM, or YYYY format. - end_date: An end date in YYYY-MM-DD, YYYY-MM, or YYYY format or "present". - date: A date in YYYY-MM-DD, YYYY-MM, or YYYY format or a custom string. If - provided, start_date and end_date will be ignored. - - Returns: - The computed time span string. - """ - date_is_provided = date is not None - start_date_is_provided = start_date is not None - end_date_is_provided = end_date is not None - - if date_is_provided: - # If only the date is provided, the time span is irrelevant. So, return an - # empty string. - return "" - - if not start_date_is_provided and not end_date_is_provided: - # If neither start_date nor end_date is provided, return an empty string. - return "" - - if isinstance(start_date, int) or isinstance(end_date, int): - # Then it means one of the dates is year, so time span cannot be more - # specific than years. - start_year = get_date_object(start_date).year # type: ignore - end_year = get_date_object(end_date).year # type: ignore - - time_span_in_years = end_year - start_year - - if time_span_in_years < 2: - time_span_string = "1 year" - else: - time_span_string = f"{time_span_in_years} years" - - return time_span_string - - # Then it means both start_date and end_date are in YYYY-MM-DD or YYYY-MM - # format. - end_date = get_date_object(end_date) # type: ignore - start_date = get_date_object(start_date) # type: ignore - - # Calculate the number of days between start_date and end_date: - timespan_in_days = (end_date - start_date).days # type: ignore - - # Calculate the number of years and months between start_date and end_date: - how_many_years = timespan_in_days // 365 - how_many_months = (timespan_in_days % 365) // 30 + 1 - # Deal with overflow (prevent rounding to 1 year 12 months, etc.) - how_many_years += how_many_months // 12 - how_many_months %= 12 - - # Format the number of years and months between start_date and end_date: - if how_many_years == 0: - how_many_years_string = None - elif how_many_years == 1: - how_many_years_string = f"1 {locale['year']}" - else: - how_many_years_string = f"{how_many_years} {locale['years']}" - - # Format the number of months between start_date and end_date: - if how_many_months == 1 or (how_many_years_string is None and how_many_months == 0): - how_many_months_string = f"1 {locale['month']}" - elif how_many_months == 0: - how_many_months_string = None - else: - how_many_months_string = f"{how_many_months} {locale['months']}" - - # Combine howManyYearsString and howManyMonthsString: - if how_many_years_string is None and how_many_months_string is not None: - time_span_string = how_many_months_string - elif how_many_months_string is None and how_many_years_string is not None: - time_span_string = how_many_years_string - elif how_many_years_string is not None and how_many_months_string is not None: - time_span_string = f"{how_many_years_string} {how_many_months_string}" - else: - message = "The time span is not valid!" - raise ValueError(message) - - return time_span_string.strip() - - -def compute_date_string( - start_date: Optional[str | int], - end_date: Optional[str | int], - date: Optional[str | int], - show_only_years: bool = False, -) -> str: - """Return a date string based on the provided dates. - - Example: - ```python - get_date_string("2020-01-01", "2021-01-01", None) - ``` - returns - ``` - "Jan 2020 to Jan 2021" - ``` - - Args: - start_date: A start date in YYYY-MM-DD, YYYY-MM, or YYYY format. - end_date: An end date in YYYY-MM-DD, YYYY-MM, or YYYY format or "present". - date: A date in YYYY-MM-DD, YYYY-MM, or YYYY format or a custom string. If - provided, start_date and end_date will be ignored. - show_only_years: If True, only the years will be shown in the date string. - - Returns: - The computed date string. - """ - date_is_provided = date is not None - start_date_is_provided = start_date is not None - end_date_is_provided = end_date is not None - - if date_is_provided: - if isinstance(date, int): - # Then it means only the year is provided - date_string = str(date) - else: - try: - date_object = get_date_object(date) - if show_only_years: - date_string = str(date_object.year) - else: - date_string = format_date(date_object) - except ValueError: - # Then it is a custom date string (e.g., "My Custom Date") - date_string = str(date) - elif start_date_is_provided and end_date_is_provided: - if isinstance(start_date, int): - # Then it means only the year is provided - start_date = str(start_date) - else: - # Then it means start_date is either in YYYY-MM-DD or YYYY-MM format - date_object = get_date_object(start_date) - if show_only_years: - start_date = date_object.year - else: - start_date = format_date(date_object) - - if end_date == "present": - end_date = locale["present"] # type: ignore - elif isinstance(end_date, int): - # Then it means only the year is provided - end_date = str(end_date) - else: - # Then it means end_date is either in YYYY-MM-DD or YYYY-MM format - date_object = get_date_object(end_date) - end_date = date_object.year if show_only_years else format_date(date_object) - - date_string = f"{start_date} {locale['to']} {end_date}" - - else: - # Neither date, start_date, nor end_date are provided, so return an empty - # string: - date_string = "" - - return date_string - - -def make_a_url_clean(url: str) -> str: - """Make a URL clean by removing the protocol, www, and trailing slashes. - - Example: - ```python - make_a_url_clean("https://www.example.com/") - ``` - returns - `"example.com"` - - Args: - url: The URL to make clean. - - Returns: - The clean URL. - """ - url = url.replace("https://", "").replace("http://", "") - if url.endswith("/"): - url = url[:-1] - - return url - - -def get_date_object(date: str | int) -> Date: - """Parse a date string in YYYY-MM-DD, YYYY-MM, or YYYY format and return a - `datetime.date` object. This function is used throughout the validation process of - the data models. - - Args: - date: The date string to parse. - - Returns: - The parsed date. - """ - if isinstance(date, int): - date_object = Date.fromisoformat(f"{date}-01-01") - elif re.fullmatch(r"\d{4}-\d{2}-\d{2}", date): - # Then it is in YYYY-MM-DD format - date_object = Date.fromisoformat(date) - elif re.fullmatch(r"\d{4}-\d{2}", date): - # Then it is in YYYY-MM format - date_object = Date.fromisoformat(f"{date}-01") - elif re.fullmatch(r"\d{4}", date): - # Then it is in YYYY format - date_object = Date.fromisoformat(f"{date}-01-01") - elif date == "present": - date_object = get_date_input() - else: - message = ( - "This is not a valid date! Please use either YYYY-MM-DD, YYYY-MM, or" - " YYYY format." - ) - raise ValueError(message) - - return date_object - - -def dictionary_key_to_proper_section_title(key: str) -> str: - """Convert a dictionary key to a proper section title. - - Example: - ```python - dictionary_key_to_proper_section_title("section_title") - ``` - returns - `"Section Title"` - - Args: - key: The key to convert to a proper section title. - - Returns: - The proper section title. - """ - title = key.replace("_", " ") - words = title.split(" ") - - words_not_capitalized_in_a_title = [ - "a", - "and", - "as", - "at", - "but", - "by", - "for", - "from", - "if", - "in", - "into", - "like", - "near", - "nor", - "of", - "off", - "on", - "onto", - "or", - "over", - "so", - "than", - "that", - "to", - "upon", - "when", - "with", - "yet", - ] - - # loop through the words and if the word doesn't contain any uppercase letters, - # capitalize the first letter of the word. If the word contains uppercase letters, - # don't change the word. - return " ".join( - ( - word.capitalize() - if (word.islower() and word not in words_not_capitalized_in_a_title) - else word - ) - for word in words - ) diff --git a/rendercv/data/models/curriculum_vitae.py b/rendercv/data/models/curriculum_vitae.py deleted file mode 100644 index 7c05a91fd..000000000 --- a/rendercv/data/models/curriculum_vitae.py +++ /dev/null @@ -1,609 +0,0 @@ -""" -The `rendercv.data.models.curriculum_vitae` module contains the data model of the `cv` -field of the input file. -""" - -import functools -import pathlib -import re -from typing import Annotated, Any, Literal, Optional, get_args - -import pydantic -import pydantic_extra_types.phone_numbers as pydantic_phone_numbers - -from . import computers, entry_types -from .base import RenderCVBaseModelWithExtraKeys, RenderCVBaseModelWithoutExtraKeys - -# ====================================================================================== -# Create validator functions: ========================================================== -# ====================================================================================== - - -class SectionBase(RenderCVBaseModelWithoutExtraKeys): - """This class is the parent class of all the section types. It is being used - in RenderCV internally, and it is not meant to be used directly by the users. - It is used by `rendercv.data_models.utilities.create_a_section_model` function to - create a section model based on any entry type. - """ - - title: str - entry_type: str - entries: list[Any] - - -# Create a URL validator: -url_validator = pydantic.TypeAdapter(pydantic.HttpUrl) - - -def validate_url(url: str) -> str: - """Validate a URL. - - Args: - url: The URL to validate. - - Returns: - The validated URL. - """ - url_validator.validate_strings(url) - return url - - -def create_a_section_validator(entry_type: type) -> type[SectionBase]: - """Create a section model based on the entry type. See [Pydantic's documentation - about dynamic model - creation](https://pydantic-docs.helpmanual.io/usage/models/#dynamic-model-creation) - for more information. - - The section model is used to validate a section. - - Args: - entry_type: The entry type to create the section model. It's not an instance of - the entry type, but the entry type itself. - - Returns: - The section validator (a Pydantic model). - """ - if entry_type is str: - model_name = "SectionWithTextEntries" - entry_type_name = "TextEntry" - else: - model_name = "SectionWith" + entry_type.__name__.replace("Entry", "Entries") - entry_type_name = entry_type.__name__ - - return pydantic.create_model( - model_name, - entry_type=(Literal[entry_type_name], ...), # type: ignore - entries=(list[entry_type], ...), - __base__=SectionBase, - ) - - -def get_characteristic_entry_attributes( - entry_types: tuple[type], -) -> dict[type, set[str]]: - """Get the characteristic attributes of the entry types. - - Args: - entry_types: The entry types to get their characteristic attributes. These are - not instances of the entry types, but the entry types themselves. `str` type - should not be included in this list. - - Returns: - The characteristic attributes of the entry types. - """ - # Look at all the entry types, collect their attributes with - # EntryType.model_fields.keys() and find the common ones. - all_attributes = [] - for EntryType in entry_types: - all_attributes.extend(EntryType.model_fields.keys()) - - common_attributes = { - attribute for attribute in all_attributes if all_attributes.count(attribute) > 1 - } - - # Store each entry type's characteristic attributes in a dictionary: - characteristic_entry_attributes = {} - for EntryType in entry_types: - characteristic_entry_attributes[EntryType] = ( - set(EntryType.model_fields.keys()) - common_attributes - ) - - return characteristic_entry_attributes - - -def get_entry_type_name_and_section_validator( - entry: Optional[dict[str, str | list[str]] | str | type], entry_types: tuple[type] -) -> tuple[str, type[SectionBase]]: - """Get the entry type name and the section validator based on the entry. - - It takes an entry (as a dictionary or a string) and a list of entry types. Then - it determines the entry type and creates a section validator based on the entry - type. - - Args: - entry: The entry to determine its type. - entry_types: The entry types to determine the entry type. These are not - instances of the entry types, but the entry types themselves. `str` type - should not be included in this list. - - Returns: - The entry type name and the section validator. - """ - - if isinstance(entry, dict): - entry_type_name = None # the entry type is not determined yet - characteristic_entry_attributes = get_characteristic_entry_attributes( - entry_types - ) - - for ( - EntryType, - characteristic_attributes, - ) in characteristic_entry_attributes.items(): - # If at least one of the characteristic_entry_attributes is in the entry, - # then it means the entry is of this type: - if characteristic_attributes & set(entry.keys()): - entry_type_name = EntryType.__name__ - section_type = create_a_section_validator(EntryType) - break - - if entry_type_name is None: - message = "The entry is not provided correctly." - raise ValueError(message) - - elif isinstance(entry, str): - # Then it is a TextEntry - entry_type_name = "TextEntry" - section_type = create_a_section_validator(str) - - elif entry is None: - message = "The entry cannot be a null value." - raise ValueError(message) - - else: - # Then the entry is already initialized with a data model: - entry_type_name = entry.__class__.__name__ - section_type = create_a_section_validator(entry.__class__) - - return entry_type_name, section_type # type: ignore - - -def validate_a_section( - sections_input: list[Any], entry_types: tuple[type] -) -> list[entry_types.Entry]: - """Validate a list of entries (a section) based on the entry types. - - Sections input is a list of entries. Since there are multiple entry types, it is not - possible to validate it directly. Firstly, the entry type is determined with the - `get_entry_type_name_and_section_validator` function. If the entry type cannot be - determined, an error is raised. If the entry type is determined, the rest of the - list is validated with the section validator. - - Args: - sections_input: The sections input to validate. - entry_types: The entry types to determine the entry type. These are not - instances of the entry types, but the entry types themselves. `str` type - should not be included in this list. - - Returns: - The validated sections input. - """ - if isinstance(sections_input, list): - # Find the entry type based on the first identifiable entry: - entry_type_name = None - section_type = None - for entry in sections_input: - try: - entry_type_name, section_type = ( - get_entry_type_name_and_section_validator(entry, entry_types) - ) - break - except ValueError: - # If the entry type cannot be determined, try the next entry: - pass - - if entry_type_name is None or section_type is None: - message = ( - "RenderCV couldn't match this section with any entry types! Please" - " check the entries and make sure they are provided correctly." - ) - raise ValueError( - message, - "", # This is the location of the error - "", # This is value of the error - ) - - section = { - "title": "Test Section", - "entry_type": entry_type_name, - "entries": sections_input, - } - - try: - section_object = section_type.model_validate( - section, - ) - sections_input = section_object.entries - except pydantic.ValidationError as e: - new_error = ValueError( - "There are problems with the entries. RenderCV detected the entry type" - f" of this section to be {entry_type_name}! The problems are shown" - " below.", - "", # This is the location of the error - "", # This is value of the error - ) - raise new_error from e - - else: - message = ( - "Each section should be a list of entries! Please see the documentation for" - " more information about the sections." - ) - raise ValueError(message) - return sections_input - - -def validate_a_social_network_username(username: str, network: str) -> str: - """Check if the `username` field in the `SocialNetwork` model is provided correctly. - - Args: - username: The username to validate. - - Returns: - The validated username. - """ - if network == "Mastodon": - mastodon_username_pattern = r"@[^@]+@[^@]+" - if not re.fullmatch(mastodon_username_pattern, username): - message = 'Mastodon username should be in the format "@username@domain"!' - raise ValueError(message) - elif network == "StackOverflow": - stackoverflow_username_pattern = r"\d+\/[^\/]+" - if not re.fullmatch(stackoverflow_username_pattern, username): - message = ( - 'StackOverflow username should be in the format "user_id/username"!' - ) - raise ValueError(message) - elif network == "YouTube": - if username.startswith("@"): - message = ( - 'YouTube username should not start with "@"! Remove "@" from the' - " beginning of the username." - ) - raise ValueError(message) - elif network == "ORCID": - orcid_username_pattern = r"\d{4}-\d{4}-\d{4}-\d{3}[\dX]" - if not re.fullmatch(orcid_username_pattern, username): - message = "ORCID username should be in the format 'XXXX-XXXX-XXXX-XXX'!" - raise ValueError(message) - - return username - - -# ====================================================================================== -# Create custom types: ================================================================= -# ====================================================================================== - -# Create a custom type named SectionContents, which is a list of entries. The entries -# can be any of the available entry types. The section is validated with the -# `validate_a_section` function. -SectionContents = Annotated[ - pydantic.json_schema.SkipJsonSchema[Any] | entry_types.ListOfEntries, - pydantic.BeforeValidator( - lambda entries: validate_a_section( - entries, entry_types=entry_types.available_entry_models - ) - ), -] - -# Create a custom type named SectionInput, which is a dictionary where the keys are the -# section titles and the values are the list of entries in that section. -Sections = Optional[dict[str, SectionContents]] - -# Create a custom type named SocialNetworkName, which is a literal type of the available -# social networks. -SocialNetworkName = Literal[ - "LinkedIn", - "GitHub", - "GitLab", - "Instagram", - "ORCID", - "Mastodon", - "StackOverflow", - "ResearchGate", - "YouTube", - "Google Scholar", - "Telegram", - "Leetcode", - "X", -] - -available_social_networks = get_args(SocialNetworkName) - -# ====================================================================================== -# Create the models: =================================================================== -# ====================================================================================== - - -class SocialNetwork(RenderCVBaseModelWithoutExtraKeys): - """This class is the data model of a social network.""" - - model_config = pydantic.ConfigDict( - title="Social Network", - ) - network: SocialNetworkName = pydantic.Field( - title="Social Network", - ) - username: str = pydantic.Field( - title="Username", - description=( - "The username used in the social network. The link will be generated" - " automatically." - ), - ) - - @pydantic.field_validator("username") - @classmethod - def check_username(cls, username: str, info: pydantic.ValidationInfo) -> str: - """Check if the username is provided correctly.""" - if "network" not in info.data: - # the network is either not provided or not one of the available social - # networks. In this case, don't check the username, since Pydantic will - # raise an error for the network. - return username - - network = info.data["network"] - - return validate_a_social_network_username(username, network) - - @pydantic.model_validator(mode="after") # type: ignore - def check_url(self) -> "SocialNetwork": - """Validate the URL of the social network.""" - if self.network == "Mastodon": - # All the other social networks have valid URLs. Mastodon URLs contain both - # the username and the domain. So, we need to validate if the url is valid. - validate_url(self.url) - - return self - - @functools.cached_property - def url(self) -> str: - """Return the URL of the social network and cache `url` as an attribute of the - instance. - """ - if self.network == "Mastodon": - # Split domain and username - _, username, domain = self.username.split("@") - url = f"https://{domain}/@{username}" - else: - url_dictionary = { - "LinkedIn": "https://linkedin.com/in/", - "GitHub": "https://github.com/", - "GitLab": "https://gitlab.com/", - "Instagram": "https://instagram.com/", - "ORCID": "https://orcid.org/", - "StackOverflow": "https://stackoverflow.com/users/", - "ResearchGate": "https://researchgate.net/profile/", - "YouTube": "https://youtube.com/@", - "Google Scholar": "https://scholar.google.com/citations?user=", - "Telegram": "https://t.me/", - "Leetcode": "https://leetcode.com/u/", - "X": "https://x.com/", - } - url = url_dictionary[self.network] + self.username - - return url - - -class CurriculumVitae(RenderCVBaseModelWithExtraKeys): - """This class is the data model of the `cv` field.""" - - model_config = pydantic.ConfigDict( - title="CV", - ) - name: Optional[str] = pydantic.Field( - default=None, - title="Name", - ) - location: Optional[str] = pydantic.Field( - default=None, - title="Location", - ) - email: Optional[pydantic.EmailStr] = pydantic.Field( - default=None, - title="Email", - ) - photo: Optional[pathlib.Path] = pydantic.Field( - default=None, - title="Photo", - description="Path to the photo of the person, relative to the input file.", - ) - phone: Optional[pydantic_phone_numbers.PhoneNumber] = pydantic.Field( - default=None, - title="Phone", - description=( - "Country code should be included. For example, +1 for the United States." - ), - ) - website: Optional[pydantic.HttpUrl] = pydantic.Field( - default=None, - title="Website", - ) - social_networks: Optional[list[SocialNetwork]] = pydantic.Field( - default=None, - title="Social Networks", - ) - sections_input: Sections = pydantic.Field( - default=None, - title="Sections", - description="The sections of the CV, like Education, Experience, etc.", - # This is an alias to allow users to use `sections` in the YAML file: - # `sections` key is preserved for RenderCV's internal use. - alias="sections", - ) - - @pydantic.field_validator("photo") - @classmethod - def update_photo_path(cls, value: Optional[pathlib.Path]) -> Optional[pathlib.Path]: - """Cast `photo` to Path and make the path absolute""" - if value: - from .rendercv_data_model import INPUT_FILE_DIRECTORY - - if INPUT_FILE_DIRECTORY is not None: - profile_picture_parent_folder = INPUT_FILE_DIRECTORY - else: - profile_picture_parent_folder = pathlib.Path.cwd() - - return profile_picture_parent_folder / str(value) - - return value - - @pydantic.field_validator("name") - @classmethod - def update_curriculum_vitae(cls, value: str, info: pydantic.ValidationInfo) -> str: - """Update the `curriculum_vitae` dictionary.""" - if value: - curriculum_vitae[info.field_name] = value # type: ignore - - return value - - @functools.cached_property - def connections(self) -> list[dict[str, Optional[str]]]: - """Return all the connections of the person as a list of dictionaries and cache - `connections` as an attribute of the instance. The connections are used in the - header of the CV. - - Returns: - The connections of the person. - """ - - connections: list[dict[str, Optional[str]]] = [] - - if self.location is not None: - connections.append( - { - "typst_icon": "location-dot", - "url": None, - "clean_url": None, - "placeholder": self.location, - } - ) - - if self.email is not None: - connections.append( - { - "typst_icon": "envelope", - "url": f"mailto:{self.email}", - "clean_url": self.email, - "placeholder": self.email, - } - ) - - if self.phone is not None: - phone_placeholder = computers.format_phone_number(self.phone) - connections.append( - { - "typst_icon": "phone", - "url": self.phone, - "clean_url": phone_placeholder, - "placeholder": phone_placeholder, - } - ) - - if self.website is not None: - website_placeholder = computers.make_a_url_clean(str(self.website)) - connections.append( - { - "typst_icon": "link", - "url": str(self.website), - "clean_url": website_placeholder, - "placeholder": website_placeholder, - } - ) - - if self.social_networks is not None: - icon_dictionary = { - "LinkedIn": "linkedin", - "GitHub": "github", - "GitLab": "gitlab", - "Instagram": "instagram", - "Mastodon": "mastodon", - "ORCID": "orcid", - "StackOverflow": "stack-overflow", - "ResearchGate": "researchgate", - "YouTube": "youtube", - "Google Scholar": "graduation-cap", - "Telegram": "telegram", - "Leetcode": "code", - "X": "x-twitter", - } - for social_network in self.social_networks: - clean_url = computers.make_a_url_clean(social_network.url) - connection = { - "typst_icon": icon_dictionary[social_network.network], - "url": social_network.url, - "clean_url": clean_url, - "placeholder": social_network.username, - } - - if social_network.network == "StackOverflow": - username = social_network.username.split("/")[1] - connection["placeholder"] = username - if social_network.network == "Google Scholar": - connection["placeholder"] = "Google Scholar" - - connections.append(connection) # type: ignore - - return connections - - @functools.cached_property - def sections(self) -> list[SectionBase]: - """Compute the sections of the CV based on the input sections. - - The original `sections` input is a dictionary where the keys are the section titles - and the values are the list of entries in that section. This function converts the - input sections to a list of `SectionBase` objects. This makes it easier to work with - the sections in the rest of the code. - - Returns: - The computed sections. - """ - sections: list[SectionBase] = [] - - if self.sections_input is not None: - for title, entries in self.sections_input.items(): - formatted_title = computers.dictionary_key_to_proper_section_title( - title - ) - - # The first entry can be used because all the entries in the section are - # already validated with the `validate_a_section` function: - entry_type_name, _ = get_entry_type_name_and_section_validator( - entries[0], # type: ignore - entry_types=entry_types.available_entry_models, - ) - - # SectionBase is used so that entries are not validated again: - section = SectionBase( - title=formatted_title, - entry_type=entry_type_name, - entries=entries, - ) - sections.append(section) - - return sections - - @pydantic.field_serializer("phone") - def serialize_phone( - self, phone: Optional[pydantic_phone_numbers.PhoneNumber] - ) -> Optional[str]: - """Serialize the phone number.""" - if phone is not None: - return phone.replace("tel:", "") - - return phone - - -# The dictionary below will be overwritten by CurriculumVitae class, which will contain -# some important data for the CV. -curriculum_vitae: dict[str, str] = {} diff --git a/rendercv/data/models/design.py b/rendercv/data/models/design.py deleted file mode 100644 index d9f9a9e0f..000000000 --- a/rendercv/data/models/design.py +++ /dev/null @@ -1,211 +0,0 @@ -""" -The `rendercv.data.models.design` module contains the data model of the `design` field -of the input file. -""" - -import importlib -import importlib.util -import os -import pathlib -from typing import Annotated, Any - -import pydantic - -from ...themes import ( - ClassicThemeOptions, - EngineeringclassicThemeOptions, - EngineeringresumesThemeOptions, - ModerncvThemeOptions, - Sb2novThemeOptions, -) -from . import entry_types -from .base import RenderCVBaseModelWithoutExtraKeys - -# ====================================================================================== -# Create validator functions: ========================================================== -# ====================================================================================== - - -def validate_design_options( - design: Any, - available_theme_options: dict[str, type], - available_entry_type_names: list[str], -) -> Any: - """Check if the design options are for a built-in theme or a custom theme. If it is - a built-in theme, validate it with the corresponding data model. If it is a custom - theme, check if the necessary files are provided and validate it with the custom - theme data model, found in the `__init__.py` file of the custom theme folder. - - Args: - design: The design options to validate. - available_theme_options: The available theme options. The keys are the theme - names and the values are the corresponding data models. - available_entry_type_names: The available entry type names. These are used to - validate if all the templates are provided in the custom theme folder. - - Returns: - The validated design as a Pydantic data model. - """ - from .rendercv_data_model import INPUT_FILE_DIRECTORY - - original_working_directory = pathlib.Path.cwd() - - # Change the working directory to the input file directory: - - if isinstance(design, tuple(available_theme_options.values())): - # Then it means it is an already validated built-in theme. Return it as it is: - return design - if design["theme"] in available_theme_options: - # Then it is a built-in theme, but it is not validated yet. Validate it and - # return it: - ThemeDataModel = available_theme_options[design["theme"]] - return ThemeDataModel(**design) - # It is a custom theme. Validate it: - theme_name: str = str(design["theme"]) - - # Custom theme should only contain letters and digits: - if not theme_name.isalnum(): - message = "The custom theme name should only contain letters and digits." - raise ValueError( - message, - "theme", # this is the location of the error - theme_name, # this is value of the error - ) - - if INPUT_FILE_DIRECTORY is None: - theme_parent_folder = pathlib.Path.cwd() - else: - theme_parent_folder = INPUT_FILE_DIRECTORY - - custom_theme_folder = theme_parent_folder / theme_name - - # Check if the custom theme folder exists: - if not custom_theme_folder.exists(): - message = ( - ( - f"The custom theme folder `{custom_theme_folder}` does not exist." - " It should be in the working directory as the input file." - ), - ) - raise ValueError( - message, - "", # this is the location of the error - theme_name, # this is value of the error - ) - - # check if all the necessary files are provided in the custom theme folder: - required_entry_files = [ - custom_theme_folder / (entry_type_name + ".j2.typ") - for entry_type_name in available_entry_type_names - ] - required_files = [ - custom_theme_folder / "SectionBeginning.j2.typ", # section beginning template - custom_theme_folder / "SectionEnding.j2.typ", # section ending template - custom_theme_folder / "Preamble.j2.typ", # preamble template - custom_theme_folder / "Header.j2.typ", # header template - *required_entry_files, - ] - - for file in required_files: - if not file.exists(): - message = ( - f"You provided a custom theme, but the file `{file}` is not" - f" found in the folder `{custom_theme_folder}`." - ) - raise ValueError( - message, - "", # This is the location of the error - theme_name, # This is value of the error - ) - - # Import __init__.py file from the custom theme folder if it exists: - path_to_init_file = custom_theme_folder / "__init__.py" - - if path_to_init_file.exists(): - spec = importlib.util.spec_from_file_location( - "theme", - path_to_init_file, - ) - - theme_module = importlib.util.module_from_spec(spec) # type: ignore - try: - spec.loader.exec_module(theme_module) # type: ignore - except SyntaxError as e: - message = ( - f"The custom theme {theme_name}'s __init__.py file has a syntax" - " error. Please fix it." - ) - raise ValueError(message) from e - except ImportError as e: - message = ( - ( - f"The custom theme {theme_name}'s __init__.py file has an" - " import error. If you have copy-pasted RenderCV's built-in" - " themes, make sure to update the import statements (e.g.," - ' "from . import" to "from rendercv.themes import").' - ), - ) - - raise ValueError(message) from e - - ThemeDataModel = getattr( - theme_module, - f"{theme_name.capitalize()}ThemeOptions", # type: ignore - ) - - # Initialize and validate the custom theme data model: - theme_data_model = ThemeDataModel(**design) - else: - # Then it means there is no __init__.py file in the custom theme folder. - # Create a dummy data model and use that instead. - class ThemeOptionsAreNotProvided(RenderCVBaseModelWithoutExtraKeys): - theme: str = theme_name - - theme_data_model = ThemeOptionsAreNotProvided(theme=theme_name) - - os.chdir(original_working_directory) - - return theme_data_model - - -# ====================================================================================== -# Create custom types: ================================================================= -# ====================================================================================== - -available_theme_options = { - "classic": ClassicThemeOptions, - "sb2nov": Sb2novThemeOptions, - "engineeringresumes": EngineeringresumesThemeOptions, - "engineeringclassic": EngineeringclassicThemeOptions, - "moderncv": ModerncvThemeOptions, -} - -available_themes = list(available_theme_options.keys()) - -# Create a custom type named RenderCVBuiltinDesign: -# It is a union of all the design options and the correct design option is determined by -# the theme field, thanks to Pydantic's discriminator feature. -# See https://docs.pydantic.dev/2.7/concepts/fields/#discriminator for more information -RenderCVBuiltinDesign = Annotated[ - ClassicThemeOptions - | Sb2novThemeOptions - | EngineeringresumesThemeOptions - | EngineeringclassicThemeOptions - | ModerncvThemeOptions, - pydantic.Field(discriminator="theme"), -] - -# Create a custom type named RenderCVDesign: -# RenderCV supports custom themes as well. Therefore, `Any` type is used to allow custom -# themes. However, the JSON Schema generation is skipped, otherwise, the JSON Schema -# would accept any `design` field in the YAML input file. -RenderCVDesign = Annotated[ - pydantic.json_schema.SkipJsonSchema[Any] | RenderCVBuiltinDesign, - pydantic.BeforeValidator( - lambda design: validate_design_options( - design, - available_theme_options=available_theme_options, - available_entry_type_names=entry_types.available_entry_type_names, # type: ignore - ) - ), -] diff --git a/rendercv/data/models/entry_types.py b/rendercv/data/models/entry_types.py deleted file mode 100644 index ca5e1e30a..000000000 --- a/rendercv/data/models/entry_types.py +++ /dev/null @@ -1,632 +0,0 @@ -""" -The `rendercv.models.data.entry_types` module contains the data models of all the available -entry types in RenderCV. -""" - -import abc -import functools -import re -from datetime import date as Date -from typing import Annotated, Literal, Optional - -import pydantic - -from . import computers -from .base import RenderCVBaseModelWithExtraKeys - -# ====================================================================================== -# Create validator functions: ========================================================== -# ====================================================================================== - - -def validate_date_field(date: Optional[int | str]) -> Optional[int | str]: - """Check if the `date` field is provided correctly. - - Args: - date: The date to validate. - - Returns: - The validated date. - """ - date_is_provided = date is not None - - if date_is_provided: - if isinstance(date, str): - if re.fullmatch(r"\d{4}-\d{2}(-\d{2})?", date): - # Then it is in YYYY-MM-DD or YYYY-MMY format - # Check if it is a valid date: - computers.get_date_object(date) - elif re.fullmatch(r"\d{4}", date): - # Then it is in YYYY format, so, convert it to an integer: - - # This is not required for start_date and end_date because they - # can't be casted into a general string. For date, this needs to - # be done manually, because it can be a general string. - date = int(date) - - elif isinstance(date, Date): - # Pydantic parses YYYY-MM-DD dates as datetime.date objects. We need to - # convert them to strings because that is how RenderCV uses them. - date = date.isoformat() - - return date - - -def validate_start_and_end_date_fields( - date: str | Date, -) -> str: - """Check if the `start_date` and `end_date` fields are provided correctly. - - Args: - date: The date to validate. - - Returns: - The validated date. - """ - date_is_provided = date is not None - - if date_is_provided: - if isinstance(date, Date): - # Pydantic parses YYYY-MM-DD dates as datetime.date objects. We need to - # convert them to strings because that is how RenderCV uses them. - date = date.isoformat() - - elif date != "present": - # Validate the date: - computers.get_date_object(date) - - return date - - -# See https://peps.python.org/pep-0484/#forward-references for more information about -# the quotes around the type hints. -def validate_and_adjust_dates_for_an_entry( - start_date: "StartDate", - end_date: "EndDate", - date: "ArbitraryDate", -) -> tuple["StartDate", "EndDate", "ArbitraryDate"]: - """Check if the dates are provided correctly and make the necessary adjustments. - - Args: - start_date: The start date of the event. - end_date: The end date of the event. - date: The date of the event. - - Returns: - The validated and adjusted `start_date`, `end_date`, and `date`. - """ - date_is_provided = date is not None - start_date_is_provided = start_date is not None - end_date_is_provided = end_date is not None - - if date_is_provided: - # If only date is provided, ignore start_date and end_date: - start_date = None - end_date = None - elif not start_date_is_provided and end_date_is_provided: - # If only end_date is provided, assume it is a one-day event and act like - # only the date is provided: - date = end_date - start_date = None - end_date = None - elif start_date_is_provided: - start_date_object = computers.get_date_object(start_date) - if not end_date_is_provided: - # If only start_date is provided, assume it is an ongoing event, i.e., - # the end_date is present: - end_date = "present" - - if end_date != "present": - end_date_object = computers.get_date_object(end_date) - - if start_date_object > end_date_object: - message = '"start_date" can not be after "end_date"!' - - raise ValueError( - message, - "start_date", # This is the location of the error - str(start_date), # This is value of the error - ) - - return start_date, end_date, date - - -# ====================================================================================== -# Create custom types: ================================================================= -# ====================================================================================== - - -# See https://docs.pydantic.dev/2.7/concepts/types/#custom-types and -# https://docs.pydantic.dev/2.7/concepts/validators/#annotated-validators -# for more information about custom types. - -# ExactDate that accepts only strings in YYYY-MM-DD or YYYY-MM format: -ExactDate = Annotated[ - str, - pydantic.Field( - pattern=r"\d{4}-\d{2}(-\d{2})?", - ), -] - -# ArbitraryDate that accepts either an integer or a string, but it is validated with -# `validate_date_field` function: -ArbitraryDate = Annotated[ - Optional[int | str], - pydantic.BeforeValidator(validate_date_field), -] - -# StartDate that accepts either an integer or an ExactDate, but it is validated with -# `validate_start_and_end_date_fields` function: -StartDate = Annotated[ - Optional[int | ExactDate], - pydantic.BeforeValidator(validate_start_and_end_date_fields), -] - -# EndDate that accepts either an integer, the string "present", or an ExactDate, but it -# is validated with `validate_start_and_end_date_fields` function: -EndDate = Annotated[ - Optional[Literal["present"] | int | ExactDate], - pydantic.BeforeValidator(validate_start_and_end_date_fields), -] - -# ====================================================================================== -# Create the entry models: ============================================================= -# ====================================================================================== - - -class EntryType(abc.ABC): - """This class is an abstract class that defines all the methods an entry type should - have.""" - - @abc.abstractmethod - def make_keywords_bold(self, keywords: list[str]) -> "EntryType": ... - - -def make_keywords_bold_in_a_string(string: str, keywords: list[str]) -> str: - """Make the given keywords bold in the given string, handling capitalization and substring issues. - - Examples: - >>> make_keywords_bold_in_a_string("I know java and javascript", ["java"]) - 'I know **java** and javascript' - - >>> make_keywords_bold_in_a_string("Experience with aws, Aws and AWS", ["aws"]) - 'Experience with **aws**, **Aws** and **AWS**' - """ - - def bold_match(match): - return f"**{match.group(0)}**" - - for keyword in keywords: - # Use re.escape to ensure special characters in keywords are handled - pattern = r"\b" + re.escape(keyword) + r"\b" - string = re.sub(pattern, bold_match, string, flags=re.IGNORECASE) - - return string - - -class OneLineEntry(RenderCVBaseModelWithExtraKeys, EntryType): - """This class is the data model of `OneLineEntry`.""" - - model_config = pydantic.ConfigDict(title="One Line Entry") - label: str = pydantic.Field( - title="Label", - ) - details: str = pydantic.Field( - title="Details", - ) - - def make_keywords_bold(self, keywords: list[str]) -> "OneLineEntry": - """Make the given keywords bold in the `details` field. - - Args: - keywords: The keywords to make bold. - - Returns: - A OneLineEntry with the keywords made bold in the `details` field. - """ - self.details = make_keywords_bold_in_a_string(self.details, keywords) - return self - - -class BulletEntry(RenderCVBaseModelWithExtraKeys, EntryType): - """This class is the data model of `BulletEntry`.""" - - model_config = pydantic.ConfigDict(title="Bullet Entry") - bullet: str = pydantic.Field( - title="Bullet", - ) - - def make_keywords_bold(self, keywords: list[str]) -> "BulletEntry": - """Make the given keywords bold in the `bullet` field. - - Args: - keywords: The keywords to make bold. - - Returns: - A BulletEntry with the keywords made bold in the `bullet` field. - """ - self.bullet = make_keywords_bold_in_a_string(self.bullet, keywords) - return self - - -class NumberedEntry(RenderCVBaseModelWithExtraKeys, EntryType): - """This class is the data model of `NumberedEntry`.""" - - model_config = pydantic.ConfigDict(title="Numbered Entry") - - number: str = pydantic.Field( - title="Number", - ) - - def make_keywords_bold(self, keywords: list[str]) -> "NumberedEntry": - """Make the given keywords bold in the `number` field. - - Args: - keywords: The keywords to make bold. - - Returns: - A NumberedEntry with the keywords made bold in the `number` field. - """ - self.number = make_keywords_bold_in_a_string(str(self.number), keywords) - return self - - -class ReversedNumberedEntry(RenderCVBaseModelWithExtraKeys, EntryType): - """This class is the data model of `ReversedNumberedEntry`.""" - - model_config = pydantic.ConfigDict(title="Reversed Numbered Entry") - reversed_number: str = pydantic.Field( - title="Reversed Number", - ) - - def make_keywords_bold(self, keywords: list[str]) -> "ReversedNumberedEntry": - """Make the given keywords bold in the `reversed_number` field. - - Args: - keywords: The keywords to make bold. - - Returns: - A ReversedNumberedEntry with the keywords made bold in the `reversed_number` field. - """ - self.reversed_number = make_keywords_bold_in_a_string( - str(self.reversed_number), keywords - ) - return self - - -class EntryWithDate(RenderCVBaseModelWithExtraKeys): - """This class is the parent class of some of the entry types that have date - fields. - """ - - date: ArbitraryDate = pydantic.Field( - default=None, - title="Date", - description=( - "The date can be written in the formats YYYY-MM-DD, YYYY-MM, or YYYY, or as" - ' an arbitrary string such as "Fall 2023."' - ), - examples=["2020-09-24", "Fall 2023"], - ) - - @functools.cached_property - def date_string(self) -> str: - """Return a date string based on the `date` field and cache `date_string` as - an attribute of the instance. - """ - return computers.compute_date_string( - start_date=None, end_date=None, date=self.date - ) - - -class PublicationEntryBase(RenderCVBaseModelWithExtraKeys): - """This class is the parent class of the `PublicationEntry` class.""" - - title: str = pydantic.Field( - title="Publication Title", - ) - authors: list[str] = pydantic.Field( - title="Authors", - ) - doi: Optional[Annotated[str, pydantic.Field(pattern=r"\b10\..*")]] = pydantic.Field( - default=None, - title="DOI", - examples=["10.48550/arXiv.2310.03138"], - ) - url: Optional[pydantic.HttpUrl] = pydantic.Field( - default=None, - title="URL", - description="If DOI is provided, it will be ignored.", - ) - journal: Optional[str] = pydantic.Field( - default=None, - title="Journal", - ) - - @pydantic.model_validator(mode="after") # type: ignore - def ignore_url_if_doi_is_given(self) -> "PublicationEntryBase": - """Check if DOI is provided and ignore the URL if it is provided.""" - doi_is_provided = self.doi is not None - - if doi_is_provided: - self.url = None - - return self - - @functools.cached_property - def doi_url(self) -> str: - """Return the URL of the DOI and cache `doi_url` as an attribute of the - instance. - """ - doi_is_provided = self.doi is not None - - if doi_is_provided: - return f"https://doi.org/{self.doi}" - return "" - - @functools.cached_property - def clean_url(self) -> str: - """Return the clean URL of the publication and cache `clean_url` as an attribute - of the instance. - """ - url_is_provided = self.url is not None - - if url_is_provided: - return computers.make_a_url_clean(str(self.url)) # type: ignore - return "" - - def make_keywords_bold( - self, - keywords: list[str], # NOQA: ARG002 - ) -> "PublicationEntryBase": - return self - - -# The following class is to ensure PublicationEntryBase keys come first, -# then the keys of the EntryWithDate class. The only way to achieve this in Pydantic is -# to do this. The same thing is done for the other classes as well. -class PublicationEntry(EntryWithDate, PublicationEntryBase, EntryType): - """This class is the data model of `PublicationEntry`. `PublicationEntry` class is - created by combining the `EntryWithDate` and `PublicationEntryBase` classes to have - the fields in the correct order. - """ - - model_config = pydantic.ConfigDict(title="Publication Entry") - - -class EntryBase(EntryWithDate): - """This class is the parent class of some of the entry types. It is being used - because some of the entry types have common fields like dates, highlights, location, - etc. - """ - - start_date: StartDate = pydantic.Field( - default=None, - title="Start Date", - description=( - "The event's start date, written in YYYY-MM-DD, YYYY-MM, or YYYY format." - ), - examples=["2020-09-24"], - ) - end_date: EndDate = pydantic.Field( - default=None, - title="End Date", - description=( - "The event's end date, written in YYYY-MM-DD, YYYY-MM, or YYYY format. If" - " the event is ongoing, type “present” or provide only the start date." - ), - examples=["2020-09-24", "present"], - ) - location: Optional[str] = pydantic.Field( - default=None, - title="Location", - examples=["Istanbul, Türkiye"], - ) - summary: Optional[str] = pydantic.Field( - default=None, - title="Summary", - examples=["Did this and that."], - ) - highlights: Optional[list[str]] = pydantic.Field( - default=None, - title="Highlights", - examples=["Did this.", "Did that."], - ) - - @pydantic.field_validator("highlights", mode="after") - @classmethod - def handle_nested_bullets_in_highlights( - cls, highlights: Optional[list[str]] - ) -> Optional[list[str]]: - """Handle nested bullets in the `highlights` field.""" - if highlights: - return [highlight.replace(" - ", "\n - ") for highlight in highlights] - - return highlights - - @pydantic.model_validator(mode="after") # type: ignore - def check_and_adjust_dates(self) -> "EntryBase": - """Call the `validate_adjust_dates_of_an_entry` function to validate the - dates. - """ - self.start_date, self.end_date, self.date = ( - validate_and_adjust_dates_for_an_entry( - start_date=self.start_date, end_date=self.end_date, date=self.date - ) - ) - return self - - @functools.cached_property - def date_string(self) -> str: - """Return a date string based on the `date`, `start_date`, and `end_date` fields - and cache `date_string` as an attribute of the instance. - - Example: - ```python - entry = dm.EntryBase( - start_date="2020-10-11", end_date="2021-04-04" - ).date_string - ``` - returns - `"Nov 2020 to Apr 2021"` - """ - return computers.compute_date_string( - start_date=self.start_date, end_date=self.end_date, date=self.date - ) - - @functools.cached_property - def date_string_only_years(self) -> str: - """Return a date string that only contains years based on the `date`, - `start_date`, and `end_date` fields and cache `date_string_only_years` as an - attribute of the instance. - - Example: - ```python - entry = dm.EntryBase( - start_date="2020-10-11", end_date="2021-04-04" - ).date_string_only_years - ``` - returns - `"2020 to 2021"` - """ - return computers.compute_date_string( - start_date=self.start_date, - end_date=self.end_date, - date=self.date, - show_only_years=True, - ) - - @functools.cached_property - def time_span_string(self) -> str: - """Return a time span string based on the `date`, `start_date`, and `end_date` - fields and cache `time_span_string` as an attribute of the instance. - """ - return computers.compute_time_span_string( - start_date=self.start_date, end_date=self.end_date, date=self.date - ) - - def make_keywords_bold(self, keywords: list[str]) -> "EntryBase": - """Make the given keywords bold in the `summary` and `highlights` fields. - - Args: - keywords: The keywords to make bold. - - Returns: - An EntryBase with the keywords made bold in the `summary` and `highlights` - fields. - """ - if self.summary: - self.summary = make_keywords_bold_in_a_string(self.summary, keywords) - - if self.highlights: - self.highlights = [ - make_keywords_bold_in_a_string(highlight, keywords) - for highlight in self.highlights - ] - - return self - - -class NormalEntryBase(RenderCVBaseModelWithExtraKeys): - """This class is the parent class of the `NormalEntry` class.""" - - name: str = pydantic.Field( - title="Name", - ) - - -class NormalEntry(EntryBase, NormalEntryBase, EntryType): - """This class is the data model of `NormalEntry`. `NormalEntry` class is created by - combining the `EntryBase` and `NormalEntryBase` classes to have the fields in the - correct order. - """ - - model_config = pydantic.ConfigDict(title="Normal Entry") - - -class ExperienceEntryBase(RenderCVBaseModelWithExtraKeys): - """This class is the parent class of the `ExperienceEntry` class.""" - - company: str = pydantic.Field( - title="Company", - ) - position: str = pydantic.Field( - title="Position", - ) - - -class ExperienceEntry(EntryBase, ExperienceEntryBase, EntryType): - """This class is the data model of `ExperienceEntry`. `ExperienceEntry` class is - created by combining the `EntryBase` and `ExperienceEntryBase` classes to have the - fields in the correct order. - """ - - model_config = pydantic.ConfigDict(title="Experience Entry") - - -class EducationEntryBase(RenderCVBaseModelWithExtraKeys): - """This class is the parent class of the `EducationEntry` class.""" - - institution: str = pydantic.Field( - title="Institution", - ) - area: str = pydantic.Field( - title="Area", - ) - degree: Optional[str] = pydantic.Field( - default=None, - title="Degree", - description="The type of the degree, such as BS, BA, PhD, MS.", - examples=["BS", "BA", "PhD", "MS"], - ) - - -class EducationEntry(EntryBase, EducationEntryBase, EntryType): - """This class is the data model of `EducationEntry`. `EducationEntry` class is - created by combining the `EntryBase` and `EducationEntryBase` classes to have the - fields in the correct order. - """ - - model_config = pydantic.ConfigDict(title="Education Entry") - - -# ====================================================================================== -# Create custom types based on the entry models: ======================================= -# ====================================================================================== -# Create a custom type named Entry: -Entry = ( - OneLineEntry - | NormalEntry - | ExperienceEntry - | EducationEntry - | PublicationEntry - | BulletEntry - | NumberedEntry - | ReversedNumberedEntry - | str -) - -# Create a custom type named ListOfEntries: -ListOfEntries = ( - list[OneLineEntry] - | list[NormalEntry] - | list[ExperienceEntry] - | list[EducationEntry] - | list[PublicationEntry] - | list[BulletEntry] - | list[NumberedEntry] - | list[ReversedNumberedEntry] - | list[str] -) - -# ====================================================================================== -# Store the available entry types: ===================================================== -# ====================================================================================== -# Entry.__args__[:-1] is a tuple of all the entry types except `str``: -# `str` (TextEntry) is not included because it's validation handled differently. It is -# not a Pydantic model, but a string. -available_entry_models: tuple[type[Entry]] = tuple(Entry.__args__[:-1]) - -available_entry_type_names = tuple( - [entry_type.__name__ for entry_type in available_entry_models] + ["TextEntry"] -) diff --git a/rendercv/data/models/locale.py b/rendercv/data/models/locale.py deleted file mode 100644 index 643372579..000000000 --- a/rendercv/data/models/locale.py +++ /dev/null @@ -1,176 +0,0 @@ -""" -The `rendercv.models.locale` module contains the data model of the -`locale` field of the input file. -""" - -from typing import Annotated, Literal, Optional - -import annotated_types as at -import pydantic -import pydantic_extra_types.language_code - -from .base import RenderCVBaseModelWithoutExtraKeys - - -class Locale(RenderCVBaseModelWithoutExtraKeys): - """This class is the data model of the locale catalog. The values of each field - updates the `locale` dictionary. - """ - - model_config = pydantic.ConfigDict(title="Locale") - - language: pydantic_extra_types.language_code.LanguageAlpha2 = pydantic.Field( - default="en", # type: ignore - title="Language", - description=( - "The language as an ISO 639 alpha-2 code. It is used for hyphenation" - " patterns. The default value is 'en'." - ), - ) - phone_number_format: Optional[Literal["national", "international", "E164"]] = ( - pydantic.Field( - default="national", - title="Phone Number Format", - description=( - "If 'national', phone numbers are formatted without the country code." - " If 'international', phone numbers are formatted with the country" - " code. The default value is 'national'." - ), - ) - ) - page_numbering_template: str = pydantic.Field( - default="NAME - Page PAGE_NUMBER of TOTAL_PAGES", - title="Page Numbering Template", - description=( - "The template of the page numbering. The following placeholders can be" - " used:\n- NAME: The name of the person\n- PAGE_NUMBER: The current page" - " number\n- TOTAL_PAGES: The total number of pages\n- TODAY: Today's date" - ' with `locale.date_template`\nThe default value is "NAME -' - ' Page PAGE_NUMBER of TOTAL_PAGES".' - ), - ) - last_updated_date_template: str = pydantic.Field( - default="Last updated in TODAY", - title="Last Updated Date Template", - description=( - "The template of the last updated date. The following placeholders can be" - " used:\n- TODAY: Today's date with `locale.date_template`\nThe" - ' default value is "Last updated in TODAY".' - ), - ) - date_template: Optional[str] = pydantic.Field( - default="MONTH_ABBREVIATION YEAR", - title="Date Template", - description=( - "The template of the date. The following placeholders can be" - " used:\n-FULL_MONTH_NAME: Full name of the month\n- MONTH_ABBREVIATION:" - " Abbreviation of the month\n- MONTH: Month as a number\n-" - " MONTH_IN_TWO_DIGITS: Month as a number in two digits\n- YEAR: Year as a" - " number\n- YEAR_IN_TWO_DIGITS: Year as a number in two digits\nThe" - ' default value is "MONTH_ABBREVIATION YEAR".' - ), - ) - month: Optional[str] = pydantic.Field( - default="month", - title='Translation of "month"', - description='Translation of the word "month" in the locale.', - ) - months: Optional[str] = pydantic.Field( - default="months", - title='Translation of "months"', - description='Translation of the word "months" in the locale.', - ) - year: Optional[str] = pydantic.Field( - default="year", - title='Translation of "year"', - description='Translation of the word "year" in the locale.', - ) - years: Optional[str] = pydantic.Field( - default="years", - title='Translation of "years"', - description='Translation of the word "years" in the locale.', - ) - present: Optional[str] = pydantic.Field( - default="present", - title='Translation of "present"', - description='Translation of the word "present" in the locale.', - ) - to: Optional[str] = pydantic.Field( - default="–", # NOQA: RUF001 - title='Translation of "to"', - description=( - "The word or character used to indicate a range in the locale (e.g.," - ' "2020 - 2021").' - ), - ) - abbreviations_for_months: Optional[ - Annotated[list[str], at.Len(min_length=12, max_length=12)] - ] = pydantic.Field( - # Month abbreviations are taken from - # https://web.library.yale.edu/cataloging/months: - default=[ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "June", - "July", - "Aug", - "Sept", - "Oct", - "Nov", - "Dec", - ], - title="Abbreviations of Months", - description="Abbreviations of the months in the locale.", - ) - full_names_of_months: Optional[ - Annotated[list[str], at.Len(min_length=12, max_length=12)] - ] = pydantic.Field( - default=[ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - title="Full Names of Months", - description="Full names of the months in the locale.", - ) - - @pydantic.field_validator( - "month", - "months", - "year", - "years", - "present", - "abbreviations_for_months", - "to", - "full_names_of_months", - "phone_number_format", - "date_template", - ) - @classmethod - def update_locale(cls, value: str, info: pydantic.ValidationInfo) -> str: - """Update the `locale` dictionary.""" - if value: - locale[info.field_name] = value # type: ignore - - return value - - -# The dictionary below will be overwritten by Locale class, which will contain -# month names, month abbreviations, and other locale-specific strings. -locale: dict[str, str | list[str]] = {} - -# Initialize even if the RenderCVDataModel is not called (to make `format_date` function -# work on its own): -Locale() diff --git a/rendercv/data/models/rendercv_data_model.py b/rendercv/data/models/rendercv_data_model.py deleted file mode 100644 index 5fbb1d6d2..000000000 --- a/rendercv/data/models/rendercv_data_model.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -The `rendercv.data.models.rendercv_data_model` module contains the `RenderCVDataModel` -data model, which is the main data model that defines the whole input file structure. -""" - -import pathlib -from typing import Optional - -import pydantic - -from ...themes import ClassicThemeOptions -from .base import RenderCVBaseModelWithoutExtraKeys -from .curriculum_vitae import CurriculumVitae -from .design import RenderCVDesign -from .locale import Locale -from .rendercv_settings import RenderCVSettings - -INPUT_FILE_DIRECTORY: Optional[pathlib.Path] = None - - -class RenderCVDataModel(RenderCVBaseModelWithoutExtraKeys): - """This class binds both the CV and the design information together.""" - - # `cv` is normally required, but don't enforce it in JSON Schema to allow - # `design` or `locale` fields to have individual YAML files. - model_config = pydantic.ConfigDict(json_schema_extra={"required": []}) - cv: CurriculumVitae = pydantic.Field( - title="CV", - description="The content of the CV.", - ) - design: RenderCVDesign = pydantic.Field( - default=ClassicThemeOptions(theme="classic"), - title="Design", - description=( - "The design information of the CV. The default is the `classic` theme." - ), - ) - locale: Locale = pydantic.Field( - default=None, # type: ignore - title="Locale Catalog", - description=( - "The locale catalog of the CV to allow the support of multiple languages." - ), - ) - rendercv_settings: RenderCVSettings = pydantic.Field( - default=RenderCVSettings(), - title="RenderCV Settings", - description="The settings of the RenderCV.", - ) - - @pydantic.model_validator(mode="before") - @classmethod - def update_paths( - cls, model, info: pydantic.ValidationInfo - ) -> Optional[RenderCVSettings]: - """Update the paths in the RenderCV settings.""" - global INPUT_FILE_DIRECTORY # NOQA: PLW0603 - - context = info.context - if context: - input_file_directory = context.get("input_file_directory", None) - INPUT_FILE_DIRECTORY = input_file_directory - else: - INPUT_FILE_DIRECTORY = None - - return model - - @pydantic.field_validator("locale", mode="before") - @classmethod - def update_locale(cls, value) -> Locale: - """Update the output folder name in the RenderCV settings.""" - # Somehow, we need this for `test_if_local_catalog_resets` to pass. - if value is None: - return Locale() - - return value - - -rendercv_data_model_fields = tuple(RenderCVDataModel.model_fields.keys()) diff --git a/rendercv/data/models/rendercv_settings.py b/rendercv/data/models/rendercv_settings.py deleted file mode 100644 index 429655329..000000000 --- a/rendercv/data/models/rendercv_settings.py +++ /dev/null @@ -1,244 +0,0 @@ -""" -The `rendercv.models.rendercv_settings` module contains the data model of the -`rendercv_settings` field of the input file. -""" - -import datetime -import pathlib -from typing import Optional - -import pydantic - -from .base import RenderCVBaseModelWithoutExtraKeys -from .computers import convert_string_to_path, replace_placeholders - -file_path_placeholder_description = ( - "The following placeholders can be used:\n- FULL_MONTH_NAME: Full name of the" - " month\n- MONTH_ABBREVIATION: Abbreviation of the month\n- MONTH: Month as a" - " number\n- MONTH_IN_TWO_DIGITS: Month as a number in two digits\n- YEAR: Year as a" - " number\n- YEAR_IN_TWO_DIGITS: Year as a number in two digits\n- NAME: The name of" - " the CV owner\n- NAME_IN_SNAKE_CASE: The name of the CV owner in snake case\n-" - " NAME_IN_LOWER_SNAKE_CASE: The name of the CV owner in lower snake case\n-" - " NAME_IN_UPPER_SNAKE_CASE: The name of the CV owner in upper snake case\n-" - " NAME_IN_KEBAB_CASE: The name of the CV owner in kebab case\n-" - " NAME_IN_LOWER_KEBAB_CASE: The name of the CV owner in lower kebab case\n-" - " NAME_IN_UPPER_KEBAB_CASE: The name of the CV owner in upper kebab case\n-" - " FULL_MONTH_NAME: Full name of the month\n- MONTH_ABBREVIATION: Abbreviation of" - " the month\n- MONTH: Month as a number\n- MONTH_IN_TWO_DIGITS: Month as a number" - " in two digits\n- YEAR: Year as a number\n- YEAR_IN_TWO_DIGITS: Year as a number" - ' in two digits\nThe default value is "MONTH_ABBREVIATION YEAR".\nThe default value' - " is null." -) - -file_path_placeholder_description_without_default = ( - file_path_placeholder_description.replace("\nThe default value is null.", "") -) - -DATE_INPUT = datetime.date.today() - - -class RenderCommandSettings(RenderCVBaseModelWithoutExtraKeys): - """This class is the data model of the `render` command's settings.""" - - design: Optional[pathlib.Path] = pydantic.Field( - default=None, - title="`design` Field's YAML File", - description=( - "The file path to the yaml file containing the `design` field separately." - ), - ) - - rendercv_settings: Optional[pathlib.Path] = pydantic.Field( - default=None, - title="`rendercv_settings` Field's YAML File", - description=( - "The file path to the yaml file containing the `rendercv_settings` field" - " separately." - ), - ) - - locale: Optional[pathlib.Path] = pydantic.Field( - default=None, - title="`locale` Field's YAML File", - description=( - "The file path to the yaml file containing the `locale` field separately." - ), - ) - - output_folder_name: str = pydantic.Field( - default="rendercv_output", - title="Output Folder Name", - description=( - "The name of the folder where the output files will be saved." - f" {file_path_placeholder_description_without_default}\nThe default value" - ' is "rendercv_output".' - ), - ) - - pdf_path: Optional[pathlib.Path] = pydantic.Field( - default=None, - title="PDF Path", - description=( - "The path to copy the PDF file to. If it is not provided, the PDF file will" - f" not be copied. {file_path_placeholder_description}" - ), - ) - - typst_path: Optional[pathlib.Path] = pydantic.Field( - default=None, - title="Typst Path", - description=( - "The path to copy the Typst file to. If it is not provided, the Typst file" - f" will not be copied. {file_path_placeholder_description}" - ), - ) - - html_path: Optional[pathlib.Path] = pydantic.Field( - default=None, - title="HTML Path", - description=( - "The path to copy the HTML file to. If it is not provided, the HTML file" - f" will not be copied. {file_path_placeholder_description}" - ), - ) - - png_path: Optional[pathlib.Path] = pydantic.Field( - default=None, - title="PNG Path", - description=( - "The path to copy the PNG file to. If it is not provided, the PNG file will" - f" not be copied. {file_path_placeholder_description}" - ), - ) - - markdown_path: Optional[pathlib.Path] = pydantic.Field( - default=None, - title="Markdown Path", - description=( - "The path to copy the Markdown file to. If it is not provided, the Markdown" - f" file will not be copied. {file_path_placeholder_description}" - ), - ) - - dont_generate_html: bool = pydantic.Field( - default=False, - title="Don't Generate HTML", - description=( - "A boolean value to determine whether the HTML file will be generated. The" - " default value is False." - ), - ) - - dont_generate_markdown: bool = pydantic.Field( - default=False, - title="Don't Generate Markdown", - description=( - "A boolean value to determine whether the Markdown file will be generated." - ' The default value is "false".' - ), - ) - - dont_generate_pdf: bool = pydantic.Field( - default=False, - title="Don't Generate PDF", - description=( - "A boolean value to determine whether the PDF file will be generated. The" - " default value is False." - ), - ) - - dont_generate_png: bool = pydantic.Field( - default=False, - title="Don't Generate PNG", - description=( - "A boolean value to determine whether the PNG file will be generated. The" - " default value is False." - ), - ) - - watch: bool = pydantic.Field( - default=False, - title="Re-run RenderCV When the Input File is Updated", - description=( - "A boolean value to determine whether to re-run RenderCV when the input" - 'file is updated. The default value is "false".' - ), - ) - - @pydantic.field_validator( - "output_folder_name", - mode="before", - ) - @classmethod - def replace_placeholders(cls, value: str) -> str: - """Replaces the placeholders in a string with the corresponding values.""" - return replace_placeholders(value) - - @pydantic.field_validator( - "design", - "locale", - "rendercv_settings", - "pdf_path", - "typst_path", - "html_path", - "png_path", - "markdown_path", - mode="before", - ) - @classmethod - def convert_string_to_path(cls, value: Optional[str]) -> Optional[pathlib.Path]: - """Converts a string to a `pathlib.Path` object by replacing the placeholders - with the corresponding values. If the path is not an absolute path, it is - converted to an absolute path by prepending the current working directory. - """ - if value is None: - return None - - return convert_string_to_path(value) - - -class RenderCVSettings(RenderCVBaseModelWithoutExtraKeys): - """This class is the data model of the RenderCV settings.""" - - model_config = pydantic.ConfigDict(title="RenderCV Settings") - - date: datetime.date = pydantic.Field( - default=datetime.date.today(), - title="Date", - description=( - "The date that will be used everywhere (e.g., in the output file names," - " last updated date, computation of time spans for the events that are" - " currently happening, etc.). The default value is the current date." - ), - json_schema_extra={ - "default": None, - }, - ) - render_command: Optional[RenderCommandSettings] = pydantic.Field( - default=None, - title="Render Command Settings", - description=( - "RenderCV's `render` command settings. They are the same as the command" - " line arguments. CLI arguments have higher priority than the settings in" - " the input file." - ), - ) - bold_keywords: list[str] = pydantic.Field( - default=[], - title="Bold Keywords", - description=( - "The keywords that will be bold in the output. The default value is an" - " empty list." - ), - ) - - @pydantic.field_validator("date") - @classmethod - def mock_today(cls, value: datetime.date) -> datetime.date: - """Mocks the current date for testing.""" - - global DATE_INPUT # NOQA: PLW0603 - - DATE_INPUT = value - - return value diff --git a/rendercv/data/reader.py b/rendercv/data/reader.py deleted file mode 100644 index b37e007c9..000000000 --- a/rendercv/data/reader.py +++ /dev/null @@ -1,382 +0,0 @@ -""" -The `rendercv.data.reader` module contains the functions that are used to read the input -file (YAML or JSON) and return them as an instance of `RenderCVDataModel`, which is a -Pydantic data model of RenderCV's data format. -""" - -import pathlib -import re -from typing import Optional - -import pydantic -import ruamel.yaml -from ruamel.yaml.comments import CommentedMap - -from . import models -from .models import entry_types - - -def make_given_keywords_bold_in_sections( - sections_input: models.Sections, keywords: list[str] -) -> models.Sections: - """Iterate over the dictionary recursively and make the given keywords bold. - - Args: - sections_input: The sections input as a Pydantic model. - keywords: The keywords to make bold. - - Returns: - The dictionary with the given keywords bold. - """ - if sections_input is None: - return None - - for entries in sections_input.values(): - for i, entry in enumerate(entries): - if isinstance(entry, str): - entries[i] = entry_types.make_keywords_bold_in_a_string( # type: ignore - entry, keywords - ) - elif callable(getattr(entry, "make_keywords_bold", None)): - entries[i] = entry.make_keywords_bold(keywords) # type: ignore - - return sections_input - - -def get_error_message_and_location_and_value_from_a_custom_error( - error_string: str, -) -> tuple[Optional[str], Optional[str], Optional[str]]: - """Look at a string and figure out if it's a custom error message that has been - sent from `rendercv.data.reader.read_input_file`. If it is, then return the custom - message, location, and the input value. - - This is done because sometimes we raise an error about a specific field in the model - validation level, but Pydantic doesn't give us the exact location of the error - because it's a model-level error. So, we raise a custom error with three string - arguments: message, location, and input value. Those arguments then combined into a - string by Python. This function is used to parse that custom error message and - return the three values. - - Args: - error_string: The error message. - - Returns: - The custom message, location, and the input value. - """ - pattern = r"""\(['"](.*)['"], '(.*)', '(.*)'\)""" - match = re.search(pattern, error_string) - if match: - return match.group(1), match.group(2), match.group(3) - return None, None, None - - -def get_coordinates_of_a_key_in_a_yaml_object( - yaml_object: ruamel.yaml.YAML, location: list[str] -) -> tuple[tuple[int, int], tuple[int, int]]: - """Find the coordinates of a key in a YAML object. - - Args: - yaml_object: The YAML object. - location: The location of the key in the YAML object. For example, - `['cv', 'sections', 'education', '0', 'degree']`. - - Returns: - The coordinates of the key in the YAML object in the format - ((start_line, start_column), (end_line, end_column)). - (Line and column numbers are 0-indexed.) - """ - - def get_inner_yaml_object_from_its_key( - yaml_object: CommentedMap, location_key: str - ) -> tuple[CommentedMap, tuple[tuple[int, int], tuple[int, int]]]: - # If the part is numeric, interpret it as a list index: - try: - index = int(location_key) - try: - inner_yaml_object = yaml_object[index] - # Get the coordinates from the list's lc.data (which is a list of tuples). - start_line, start_col = yaml_object.lc.data[index] - end_line, end_col = start_line, start_col - coordinates = ((start_line + 1, start_col - 1), (end_line + 1, end_col)) - except IndexError as e: - message = f"Index {index} is out of range in the YAML file." - raise KeyError(message) from e - except ValueError as e: - # Otherwise, the part is a key in a mapping. - if location_key not in yaml_object: - message = f"Key '{location_key}' not found in the YAML file." - raise KeyError(message) from e - - inner_yaml_object = yaml_object[location_key] - start_line, start_col, end_line, end_col = yaml_object.lc.data[location_key] - coordinates = ((start_line + 1, start_col + 1), (end_line + 1, end_col)) - - return inner_yaml_object, coordinates - - current_yaml_object: ruamel.yaml.YAML = yaml_object - coordinates = ((0, 0), (0, 0)) - # start from the first key and move forward: - for location_key in location: - current_yaml_object, coordinates = get_inner_yaml_object_from_its_key( - current_yaml_object, location_key - ) - - return coordinates - - -def parse_validation_errors( - exception: pydantic.ValidationError, yaml_file_as_string: Optional[str] = None -) -> list[dict[str, str]]: - """Take a Pydantic validation error, parse it, and return a list of error - dictionaries that contain the error messages, locations, and the input values. - - Pydantic's `ValidationError` object is a complex object that contains a lot of - information about the error. This function takes a `ValidationError` object and - extracts the error messages, locations, and the input values. - - Args: - exception: The Pydantic validation error object. - yaml_file_as_string: The YAML file as a string. - - Returns: - A list of error dictionaries that contain the error messages, locations, and the - input values. - """ - # This dictionary is used to convert the error messages that Pydantic returns to - # more user-friendly messages. - error_dictionary: dict[str, str] = { - "Input should be 'present'": ( - "This is not a valid date! Please use either YYYY-MM-DD, YYYY-MM, or YYYY" - ' format or "present"!' - ), - "Input should be a valid integer, unable to parse string as an integer": ( - "This is not a valid date! Please use either YYYY-MM-DD, YYYY-MM, or YYYY" - " format!" - ), - "String should match pattern '\\d{4}-\\d{2}(-\\d{2})?'": ( - "This is not a valid date! Please use either YYYY-MM-DD, YYYY-MM, or YYYY" - " format!" - ), - "String should match pattern '\\b10\\..*'": ( - 'A DOI prefix should always start with "10.". For example,' - ' "10.1109/TASC.2023.3340648".' - ), - "URL scheme should be 'http' or 'https'": "This is not a valid URL!", - "Field required": "This field is required!", - "value is not a valid phone number": "This is not a valid phone number!", - "month must be in 1..12": "The month must be between 1 and 12!", - "day is out of range for month": "The day is out of range for the month!", - "Extra inputs are not permitted": ( - "This field is unknown for this object! Please remove it." - ), - "Input should be a valid string": ( - "This field should be provided or removed to use the default value!" - ), - "Input should be a valid list": ( - "This field should contain a list of items but it doesn't!" - ), - "value is not a valid color: value must be tuple, list or string": ( - "This is not a valid color! Here are some examples of valid colors:" - ' "red", "#ff0000", "rgb(255, 0, 0)", "hsl(0, 100%, 50%)"' - ), - "value is not a valid color: string not recognised as a valid color": ( - "This is not a valid color! Here are some examples of valid colors:" - ' "red", "#ff0000", "rgb(255, 0, 0)", "hsl(0, 100%, 50%)"' - ), - } - - unwanted_texts = ["value is not a valid email address: ", "Value error, "] - - # Check if this is a section error. If it is, we need to handle it differently. - # This is needed because how dm.validate_section_input function raises an exception. - # This is done to tell the user which which EntryType RenderCV excepts to see. - errors = exception.errors() - for error_object in errors.copy(): - if ( - "There are problems with the entries." in error_object["msg"] - and "ctx" in error_object - ): - location = error_object["loc"] - ctx_object = error_object["ctx"] - if "error" in ctx_object: - inner_error_object = ctx_object["error"] - if hasattr(inner_error_object, "__cause__"): - cause_object = inner_error_object.__cause__ - cause_object_errors = cause_object.errors() - for cause_error_object in cause_object_errors: - # we use [1:] to avoid `entries` location. It is a location for - # RenderCV's own data model, not the user's data model. - cause_error_object["loc"] = tuple( - list(location) + list(cause_error_object["loc"][1:]) - ) - errors.extend(cause_object_errors) - - # some locations are not really the locations in the input file, but some - # information about the model coming from Pydantic. We need to remove them. - # (e.g. avoid stuff like .end_date.literal['present']) - unwanted_locations = ["tagged-union", "list", "literal", "int", "constrained-str"] - for error_object in errors: - location = [str(location_element) for location_element in error_object["loc"]] - new_location = [str(location_element) for location_element in location] - for location_element in location: - for unwanted_location in unwanted_locations: - if unwanted_location in location_element: - new_location.remove(location_element) - error_object["loc"] = new_location # type: ignore - - # Parse all the errors and create a new list of errors. - new_errors: list[dict[str, str]] = [] - for error_object in errors: - message = error_object["msg"] - location = ".".join(error_object["loc"]) # type: ignore - input = error_object["input"] - - # Check if this is a custom error message: - custom_message, custom_location, custom_input_value = ( - get_error_message_and_location_and_value_from_a_custom_error(message) - ) - if custom_message is not None: - message = custom_message - if custom_location: - # If the custom location is not empty, then add it to the location. - location = f"{location}.{custom_location}" - input = custom_input_value - - # Don't show unwanted texts in the error message: - for unwanted_text in unwanted_texts: - message = message.replace(unwanted_text, "") - - # Convert the error message to a more user-friendly message if it's in the - # error_dictionary: - if message in error_dictionary: - message = error_dictionary[message] - - # Special case for end_date because Pydantic returns multiple end_date errors - # since it has multiple valid formats: - if "end_date" in location: - message = ( - "This is not a valid end date! Please use either YYYY-MM-DD, YYYY-MM," - ' or YYYY format or "present"!' - ) - - # If the input is a dictionary or a list (the model itself fails to validate), - # then don't show the input. It looks confusing and it is not helpful. - if isinstance(input, dict | list): - input = "" - - new_error = { - "loc": tuple(location.split(".")), - "msg": message, - "input": str(input), - } - - if yaml_file_as_string: - yaml_object = read_a_yaml_file(yaml_file_as_string) - coordinates = get_coordinates_of_a_key_in_a_yaml_object( - yaml_object, - list(new_error["loc"]), # type: ignore - ) - new_error["yaml_loc"] = coordinates - - # if new_error is not in new_errors, then add it to new_errors - if new_error not in new_errors: - new_errors.append(new_error) - - return new_errors - - -def read_a_yaml_file(file_path_or_contents: pathlib.Path | str) -> dict: - """Read a YAML file and return its content as a dictionary. The YAML file can be - given as a path to the file or as the contents of the file as a string. - - Args: - file_path_or_contents: The path to the YAML file or the contents of the YAML - file as a string. - - Returns: - The content of the YAML file as a dictionary. - """ - - if isinstance(file_path_or_contents, pathlib.Path): - # Check if the file exists: - if not file_path_or_contents.exists(): - message = f"The input file {file_path_or_contents} doesn't exist!" - raise FileNotFoundError(message) - - # Check the file extension: - accepted_extensions = [".yaml", ".yml", ".json", ".json5"] - if file_path_or_contents.suffix not in accepted_extensions: - user_friendly_accepted_extensions = [ - f"[green]{ext}[/green]" for ext in accepted_extensions - ] - user_friendly_accepted_extensions = ", ".join( - user_friendly_accepted_extensions - ) - message = ( - "The input file should have one of the following extensions:" - f" {user_friendly_accepted_extensions}. The input file is" - f" {file_path_or_contents}." - ) - raise ValueError(message) - - file_content = file_path_or_contents.read_text(encoding="utf-8") - else: - file_content = file_path_or_contents - - yaml_as_a_dictionary: dict = ruamel.yaml.YAML().load(file_content) - - if yaml_as_a_dictionary is None: - message = "The input file is empty!" - raise ValueError(message) - - return yaml_as_a_dictionary - - -def validate_input_dictionary_and_return_the_data_model( - input_dictionary: dict, - context: Optional[dict] = None, -) -> models.RenderCVDataModel: - """Validate the input dictionary by creating an instance of `RenderCVDataModel`, - which is a Pydantic data model of RenderCV's data format. - - Args: - input_dictionary: The input dictionary. - context: The context dictionary that is used to validate the input dictionary. - It's used to send the input file path with the context object, but it's not - required. - - Returns: - The data model. - """ - # Validate the parsed dictionary by creating an instance of RenderCVDataModel: - data_model = models.RenderCVDataModel.model_validate( - input_dictionary, context=context - ) - - # If the `bold_keywords` field is provided in the `rendercv_settings`, make the - # given keywords bold in the `cv.sections` field: - if data_model.rendercv_settings and data_model.rendercv_settings.bold_keywords: - data_model.cv.sections_input = make_given_keywords_bold_in_sections( - data_model.cv.sections_input, - data_model.rendercv_settings.bold_keywords, - ) - - return data_model - - -def read_input_file( - file_path_or_contents: pathlib.Path | str, -) -> models.RenderCVDataModel: - """Read the input file (YAML or JSON) and return them as an instance of - `RenderCVDataModel`, which is a Pydantic data model of RenderCV's data format. - - Args: - file_path_or_contents: The path to the input file or the contents of the input - file as a string. - - Returns: - The data model. - """ - input_as_dictionary = read_a_yaml_file(file_path_or_contents) - - return validate_input_dictionary_and_return_the_data_model(input_as_dictionary) diff --git a/rendercv/data/sample_content.yaml b/rendercv/data/sample_content.yaml deleted file mode 100644 index f1e303027..000000000 --- a/rendercv/data/sample_content.yaml +++ /dev/null @@ -1,95 +0,0 @@ ---- -name: John Doe -location: Location -email: john.doe@example.com -phone: +1-609-999-9995 -social_networks: - - network: LinkedIn - username: john.doe - - network: GitHub - username: john.doe -sections: - welcome_to_RenderCV!: - - "[RenderCV](https://rendercv.com) is a Typst-based CV framework designed for academics and engineers, with Markdown syntax support." - - "Each section title is arbitrary. Each section contains a list of entries, and there are 7 different entry types to choose from." - education: - - institution: Stanford University - location: Stanford, CA, USA - area: Computer Science - degree: PhD - start_date: 2023-09 - end_date: present - highlights: - - Working on the optimization of autonomous vehicles in urban environments - - institution: Boğaziçi University - location: Istanbul, Türkiye - area: Computer Engineering - degree: BS - start_date: 2018-09 - end_date: 2022-06 - highlights: - - "GPA: 3.9/4.0, ranked 1st out of 100 students" - - "Awards: Best Senior Project, High Honor" - experience: - - company: Company C - position: Summer Intern - location: Livingston, LA, USA - start_date: 2024-06 - end_date: 2024-09 - highlights: - - Developed deep learning models for the detection of gravitational waves in LIGO data - - Published [3 peer-reviewed research papers](https://example.com) about the project and results - - company: Company B - position: Summer Intern - location: Ankara, Türkiye - start_date: 2023-06 - end_date: 2023-09 - highlights: - - Optimized the production line by 15% by implementing a new scheduling algorithm - - company: Company A - position: Summer Intern - location: Istanbul, Türkiye - start_date: 2022-06 - end_date: 2022-09 - highlights: - - Designed an inventory management web application for a warehouse - projects: - - name: "[Example Project](https://example.com)" - start_date: 2024-05 - end_date: present - summary: "A web application for writing essays" - highlights: - - "Launched an [iOS app](https://example.com) in 09/2024 that currently has 10k+ monthly active users" - - "The app is made open-source (3,000+ stars [on GitHub](https://github.com))" - - name: "[Teaching on Udemy](https://example.com)" - date: Fall 2023 - highlights: - - 'Instructed the "Statistics" course on Udemy (60,000+ students, 200,000+ hours watched)' - skills: - - label: Programming - details: Proficient with Python, C++, and Git; good understanding of Web, app development, and DevOps - - label: Mathematics - details: Good understanding of differential equations, calculus, and linear algebra - - label: Languages - details: "English (fluent, TOEFL: 118/120), Turkish (native)" - publications: - - title: 3D Finite Element Analysis of No-Insulation Coils - authors: - - Frodo Baggins - - "***John Doe***" - - Samwise Gamgee - doi: 10.1109/TASC.2023.3340648 - date: 2004-01 - extracurricular_activities: - - bullet: - "There are 7 unique entry types in RenderCV: *BulletEntry*, *TextEntry*, *EducationEntry*, - *ExperienceEntry*, *NormalEntry*, *PublicationEntry*, and *OneLineEntry*." - - bullet: "Each entry type has a different structure and layout. This document demonstrates all of them." - numbered_entries: - - number: "This is a numbered entry." - - number: "This is another numbered entry." - - number: "This is the third numbered entry." - reversed_numbered_entries: - - reversed_number: "This is a reversed numbered entry." - - reversed_number: "This is another reversed numbered entry." - - reversed_number: "This is the third reversed numbered entry." diff --git a/rendercv/renderer/__init__.py b/rendercv/renderer/__init__.py deleted file mode 100644 index 23bb16e7f..000000000 --- a/rendercv/renderer/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -The `rendercv.renderer` package contains the necessary classes and functions for -generating Typst, PDF, Markdown, HTML, and PNG files from the `RenderCVDataModel` -object. - -The Typst and Markdown files are generated with -[Jinja2](https://jinja.palletsprojects.com/en/3.1.x/) templates. Then, the Typst -file is rendered into a PDF and PNGs with -[`typst` package](https://github.com/messense/typst-py). The Markdown file is rendered -into an HTML file with -[`markdown` package](https://github.com/Python-Markdown/markdown). -""" - -from .renderer import ( - create_a_markdown_file, - create_a_typst_file, - create_a_typst_file_and_copy_theme_files, - create_contents_of_a_markdown_file, - create_contents_of_a_typst_file, - render_a_pdf_from_typst, - render_an_html_from_markdown, - render_pngs_from_typst, -) - -__all__ = [ - "create_a_markdown_file", - "create_a_typst_file", - "create_a_typst_file_and_copy_theme_files", - "create_contents_of_a_markdown_file", - "create_contents_of_a_typst_file", - "render_a_pdf_from_typst", - "render_an_html_from_markdown", - "render_pngs_from_typst", -] diff --git a/rendercv/renderer/renderer.py b/rendercv/renderer/renderer.py deleted file mode 100644 index 509ba00a4..000000000 --- a/rendercv/renderer/renderer.py +++ /dev/null @@ -1,359 +0,0 @@ -""" -The `rendercv.renderer.renderer` module contains the necessary functions for rendering -Typst, PDF, Markdown, HTML, and PNG files from the `RenderCVDataModel` object. -""" - -import importlib.resources -import pathlib -import re -import shutil -import sys -from typing import Any, Literal, Optional - -from .. import data -from . import templater - - -def create_a_file_name_without_extension_from_name(name: Optional[str]) -> str: - """Create a file name from the given name by replacing the spaces with underscores - and removing typst commands. - - Args: - name: The name to be converted. - - Returns: - The converted name (without the extension). - """ - name_without_typst_commands = templater.remove_typst_commands(str(name)) - return f"{name_without_typst_commands.replace(' ', '_')}_CV" - - -def create_a_file_and_write_contents_to_it( - contents: str, file_name: str, output_directory: pathlib.Path -) -> pathlib.Path: - """Create a file with the given contents in the output directory. - - Args: - contents: The contents of the file. - file_name: The name of the file. - output_directory: Path to the output directory. - - Returns: - The path to the created file. - """ - # Create output directory if it doesn't exist: - if not output_directory.is_dir(): - output_directory.mkdir(parents=True) - - file_path = output_directory / file_name - file_path.write_text(contents, encoding="utf-8") - - return file_path - - -def copy_theme_files_to_output_directory( - theme_name: str, - output_directory_path: pathlib.Path, -): - """Copy the auxiliary files (all the files that don't end with `.j2.typ` and `.py`) - of the theme to the output directory. - - Args: - theme_name: The name of the theme. - output_directory_path: Path to the output directory. - """ - if theme_name in data.available_themes: - theme_directory_path = importlib.resources.files( - f"rendercv.themes.{theme_name}" - ) - else: - # Then it means the theme is a custom theme. If theme_directory is not given - # as an argument, then look for the theme in the current working directory. - theme_directory_path = pathlib.Path.cwd() / theme_name - - if not theme_directory_path.is_dir(): - message = ( - f"The theme {theme_name} doesn't exist in the available themes and" - " the current working directory!" - ) - raise FileNotFoundError(message) - - dont_copy_files_with_these_extensions = [".py", ".j2.typ"] - for theme_file in theme_directory_path.iterdir(): - # theme_file.suffix returns the latest part of the file name after the last dot. - # But we need the latest part of the file name after the first dot: - try: - suffix = re.search(r"\..*", theme_file.name)[0] # type: ignore - except TypeError: - suffix = "" - - if suffix not in dont_copy_files_with_these_extensions: - if theme_file.is_dir(): - shutil.copytree( - str(theme_file), - output_directory_path / theme_file.name, - dirs_exist_ok=True, - ) - else: - shutil.copyfile( - str(theme_file), output_directory_path / theme_file.name - ) - - -def create_contents_of_a_typst_file( - rendercv_data_model: data.RenderCVDataModel, -) -> str: - """Create a Typst file with the given data model and return it as a string. - - Args: - rendercv_data_model: The data model. - - Returns: - The path to the generated Typst file. - """ - jinja2_environment = templater.Jinja2Environment().environment - - file_object = templater.TypstFile( - rendercv_data_model, - jinja2_environment, - ) - - return file_object.get_full_code() - - -def create_a_typst_file( - rendercv_data_model: data.RenderCVDataModel, - output_directory: pathlib.Path, -) -> pathlib.Path: - """Create a Typst file (depending on the theme) with the given data model and write - it to the output directory. - - Args: - rendercv_data_model: The data model. - output_directory: Path to the output directory. If not given, the Typst file - will be returned as a string. - - Returns: - The path to the generated Typst file. - """ - - typst_contents = create_contents_of_a_typst_file(rendercv_data_model) - - file_name_without_extension = create_a_file_name_without_extension_from_name( - rendercv_data_model.cv.name - ) - file_name = f"{file_name_without_extension}.typ" - - return create_a_file_and_write_contents_to_it( - typst_contents, - file_name, - output_directory, - ) - - -def create_contents_of_a_markdown_file( - rendercv_data_model: data.RenderCVDataModel, -) -> str: - """Create a Markdown file with the given data model and return it as a string. - - Args: - rendercv_data_model: The data model. - - Returns: - The path to the generated Markdown file. - """ - jinja2_environment = templater.Jinja2Environment().environment - - markdown_file_object = templater.MarkdownFile( - rendercv_data_model, - jinja2_environment, - ) - - return markdown_file_object.get_full_code() - - -def create_a_markdown_file( - rendercv_data_model: data.RenderCVDataModel, output_directory: pathlib.Path -) -> pathlib.Path: - """Render the Markdown file with the given data model and write it to the output - directory. - - Args: - rendercv_data_model: The data model. - output_directory: Path to the output directory. - - Returns: - The path to the rendered Markdown file. - """ - markdown_contents = create_contents_of_a_markdown_file(rendercv_data_model) - - file_name_without_extension = create_a_file_name_without_extension_from_name( - rendercv_data_model.cv.name - ) - file_name = f"{file_name_without_extension}.md" - - return create_a_file_and_write_contents_to_it( - markdown_contents, - file_name, - output_directory, - ) - - -def create_a_typst_file_and_copy_theme_files( - rendercv_data_model: data.RenderCVDataModel, output_directory: pathlib.Path -) -> pathlib.Path: - """Render the Typst file with the given data model in the output directory and - copy the auxiliary theme files to the output directory. - - Args: - rendercv_data_model: The data model. - output_directory: Path to the output directory. - - Returns: - The path to the rendered Typst file. - """ - file_path = create_a_typst_file(rendercv_data_model, output_directory) - copy_theme_files_to_output_directory( - rendercv_data_model.design.theme, output_directory - ) - - # Copy the profile picture to the output directory, if it exists: - if rendercv_data_model.cv.photo: - shutil.copyfile( - rendercv_data_model.cv.photo, - output_directory / rendercv_data_model.cv.photo.name, - ) - - return file_path - - -class TypstCompiler: - """A singleton class for the Typst compiler.""" - - instance: "TypstCompiler" - compiler: Any - file_path: pathlib.Path - - def __new__(cls, file_path: pathlib.Path): - if not hasattr(cls, "instance") or cls.instance.file_path != file_path: - try: - import rendercv_fonts - import typst - except Exception as e: - from .. import _parial_install_error_message - - raise ImportError(_parial_install_error_message) from e - - cls.instance = super().__new__(cls) - cls.instance.file_path = file_path - cls.instance.compiler = typst.Compiler( - file_path, - font_paths=[ - *rendercv_fonts.paths_to_font_folders, - pathlib.Path.cwd() / "fonts", - ], - ) - - return cls.instance - - def run( - self, - output: pathlib.Path, - format: Literal["png", "pdf"], - ppi: Optional[float] = None, - ) -> pathlib.Path | list[pathlib.Path]: - return self.instance.compiler.compile(format=format, output=output, ppi=ppi) - - -def render_a_pdf_from_typst(file_path: pathlib.Path) -> pathlib.Path: - """Run TinyTeX with the given Typst file to render the PDF. - - Args: - file_path: The path to the Typst file. - - Returns: - The path to the rendered PDF file. - """ - typst_compiler = TypstCompiler(file_path) - - # Before running Typst, make sure the PDF file is not open in another program, - # that wouldn't allow Typst to write to it. Remove the PDF file if it exists, - # if it's not removable, then raise an error: - pdf_output_path = file_path.with_suffix(".pdf") - - if sys.platform == "win32": - if pdf_output_path.is_file(): - try: - pdf_output_path.unlink() - except PermissionError as e: - message = ( - f"The PDF file {pdf_output_path} is open in another program and" - " doesn't allow RenderCV to rewrite it. Please close the PDF file." - ) - raise RuntimeError(message) from e - - typst_compiler.run(output=pdf_output_path, format="pdf") - - return pdf_output_path - - -def render_pngs_from_typst( - file_path: pathlib.Path, ppi: float = 150 -) -> list[pathlib.Path]: - """Run Typst with the given Typst file to render the PNG files. - - Args: - file_path: The path to the Typst file. - ppi: Pixels per inch for PNG output, defaults to 150. - - Returns: - Paths to the rendered PNG files. - """ - typst_compiler = TypstCompiler(file_path) - output_path = file_path.parent / (file_path.stem + "_{p}.png") - typst_compiler.run(format="png", ppi=ppi, output=output_path) - - # Look at the outtput folder and find the PNG files: - png_files = list(output_path.parent.glob(f"{file_path.stem}_*.png")) - return sorted(png_files, key=lambda x: int(x.stem.split("_")[-1])) - - -def render_an_html_from_markdown(markdown_file_path: pathlib.Path) -> pathlib.Path: - """Render an HTML file from a Markdown file with the same name and in the same - directory. It uses `rendercv/themes/main.j2.html` as the Jinja2 template. - - Args: - markdown_file_path: The path to the Markdown file. - - Returns: - The path to the rendered HTML file. - """ - try: - import markdown - except Exception as e: - from .. import _parial_install_error_message - - raise ImportError(_parial_install_error_message) from e - - # check if the file exists: - if not markdown_file_path.is_file(): - message = f"The file {markdown_file_path} doesn't exist!" - raise FileNotFoundError(message) - - # Convert the markdown file to HTML: - markdown_text = markdown_file_path.read_text(encoding="utf-8") - html_body = markdown.markdown(markdown_text) - - # Get the title of the markdown content: - title = re.search(r"# (.*)\n", markdown_text) - title = title.group(1) if title else None - - jinja2_environment = templater.Jinja2Environment().environment - html_template = jinja2_environment.get_template("main.j2.html") - html = html_template.render(html_body=html_body, title=title) - - # Write html into a file: - html_file_path = markdown_file_path.parent / f"{markdown_file_path.stem}.html" - html_file_path.write_text(html, encoding="utf-8") - - return html_file_path diff --git a/rendercv/renderer/templater.py b/rendercv/renderer/templater.py deleted file mode 100644 index 01cc6cc6f..000000000 --- a/rendercv/renderer/templater.py +++ /dev/null @@ -1,796 +0,0 @@ -""" -The `rendercv.renderer.templater` module contains all the necessary classes and -functions for templating the Typst and Markdown files from the `RenderCVDataModel` -object. -""" - -import copy -import pathlib -import re -from collections.abc import Callable -from typing import Optional, overload - -import jinja2 -import pydantic - -from .. import data - - -class TemplatedFile: - """This class is a base class for `TypstFile`, and `MarkdownFile` classes. It - contains the common methods and attributes for both classes. These classes are used - to generate the Typst and Markdown files with the data model and Jinja2 - templates. - - Args: - data_model: The data model. - environment: The Jinja2 environment. - """ - - def __init__( - self, - data_model: data.RenderCVDataModel, - environment: jinja2.Environment, - ): - self.cv = data_model.cv - self.design = data_model.design - self.locale = data_model.locale - self.environment = environment - - def template( - self, - theme_name: str, - template_name: str, - extension: str, - entry: Optional[data.Entry] = None, - **kwargs, - ) -> str: - """Template one of the files in the `themes` directory. - - Args: - template_name: The name of the template file. - entry: The title of the section. - - Returns: - The templated file. - """ - template = self.environment.get_template( - f"{theme_name}/{template_name}.j2.{extension}" - ) - - # Loop through the entry attributes and make them "" if they are None: - # This is necessary because otherwise they will be templated as "None" since - # it's the string representation of None. - - # Only don't touch the date fields, because only date_string is called and - # setting dates to "" will cause problems. - fields_to_ignore = ["start_date", "end_date", "date"] - - if entry is not None and not isinstance(entry, str): - entry_dictionary = entry.model_dump() - for key, value in entry_dictionary.items(): - if value is None and key not in fields_to_ignore: - entry.__setattr__(key, "") - - # The arguments of the template can be used in the template file: - return template.render( - cv=self.cv, - design=self.design, - locale=self.locale, - entry=entry, - today=data.format_date(data.get_date_input()), - **kwargs, - ) - - def get_full_code(self, main_template_name: str, **kwargs) -> str: - """Combine all the templates to get the full code of the file.""" - main_template = self.environment.get_template(main_template_name) - return main_template.render( - **kwargs, - ) - - -class TypstFile(TemplatedFile): - """This class represents a Typst file. It generates the Typst code with the - data model and Jinja2 templates. - """ - - def __init__( - self, - data_model: data.RenderCVDataModel, - environment: jinja2.Environment, - ): - typst_file_data_model = copy.deepcopy(data_model) - - if typst_file_data_model.cv.sections_input is not None: - transformed_sections = transform_markdown_sections_to_typst_sections( - typst_file_data_model.cv.sections_input - ) - typst_file_data_model.cv.sections_input = transformed_sections - - super().__init__(typst_file_data_model, environment) - - def render_templates( - self, - ) -> tuple[str, str, list[tuple[str, list[str], str, str]]]: - """Render and return all the templates for the Typst file. - - Returns: - The preamble, header, and sections of the Typst file. - """ - # All the template field names: - all_template_names = [ - "main_column_first_row_template", - "main_column_second_row_template", - "main_column_second_row_without_url_template", - "main_column_second_row_without_journal_template", - "date_and_location_column_template", - "template", - "degree_column_template", - ] - - # All the placeholders used in the templates: - sections_input: dict[str, list[pydantic.BaseModel]] = self.cv.sections_input # type: ignore - # Loop through the sections and entries to find all the field names: - placeholder_keys: set[str] = set() - if sections_input: - for section in sections_input.values(): - for entry in section: - if isinstance(entry, str): - break - entry_dictionary = entry.model_dump() - for key in entry_dictionary: - placeholder_keys.add(key.upper()) - - pattern = re.compile(r"(? str: - return pattern.sub("_", name).lower() - - # Template the preamble, header, and sections: - preamble = self.template("Preamble") - header = self.template("Header") - sections: list[tuple[str, list[str], str, str]] = [] - for section in self.cv.sections: - section_beginning = self.template( - "SectionBeginning", - section_title=escape_typst_characters(section.title), - entry_type=section.entry_type, - ) - - templates = { - template_name: getattr( - getattr( - getattr(self.design, "entry_types", None), - camel_to_snake(section.entry_type), - None, - ), - template_name, - None, - ) - for template_name in all_template_names - } - - entries: list[str] = [] - for i, entry in enumerate(section.entries): - # Prepare placeholders: - placeholders = {} - for placeholder_key in placeholder_keys: - components_path = ( - pathlib.Path(__file__).parent.parent / "themes" / "components" - ) - lowercase_placeholder_key = placeholder_key.lower() - if ( - components_path / f"{lowercase_placeholder_key}.j2.typ" - ).exists(): - placeholder_value = super().template( - "components", - lowercase_placeholder_key, - "typ", - entry, - section_title=section.title, - ) - else: - placeholder_value = getattr(entry, placeholder_key, None) - - placeholders[placeholder_key] = ( - placeholder_value if placeholder_value != "None" else None - ) - - # Substitute the placeholders in the templates: - templates_with_substitutions = { - template_name: ( - input_template_to_typst( - templates[template_name], - placeholders, # type: ignore - ) - if templates.get(template_name) - else None - ) - for template_name in all_template_names - } - - entries.append( - self.template( - section.entry_type, - entry=entry, - section_title=section.title, - entry_type=section.entry_type, - is_first_entry=i == 0, - **templates_with_substitutions, # all the templates - ) - ) - section_ending = self.template( - "SectionEnding", - section_title=section.title, - entry_type=section.entry_type, - ) - sections.append( - (section_beginning, entries, section_ending, section.entry_type) - ) - - return preamble, header, sections - - def template( - self, - template_name: str, - entry: Optional[data.Entry] = None, - **kwargs, - ) -> str: - """Template one of the files in the `themes` directory. - - Args: - template_name: The name of the template file. - entry: The data model of the entry. - - Returns: - The templated file. - """ - return super().template( - self.design.theme, - template_name, - "typ", - entry, - **kwargs, - ) - - def get_full_code(self) -> str: - """Get the Typst code of the file. - - Returns: - The Typst code. - """ - preamble, header, sections = self.render_templates() - code: str = super().get_full_code( - "main.j2.typ", - preamble=preamble, - header=header, - sections=sections, - ) - return code - - def create_file(self, file_path: pathlib.Path): - """Write the Typst code to a file.""" - file_path.write_text(self.get_full_code(), encoding="utf-8") - - -class MarkdownFile(TemplatedFile): - """This class represents a Markdown file. It generates the Markdown code with the - data model and Jinja2 templates. Markdown files are generated to produce an HTML - which can be copy-pasted to [Grammarly](https://app.grammarly.com/) for - proofreading. - """ - - def render_templates(self) -> tuple[str, list[tuple[str, list[str]]]]: - """Render and return all the templates for the Markdown file. - - Returns: - The header and sections of the Markdown file. - """ - # Template the header and sections: - header = self.template("Header") - sections: list[tuple[str, list[str]]] = [] - for section in self.cv.sections: - section_beginning = self.template( - "SectionBeginning", - section_title=section.title, - entry_type=section.entry_type, - ) - entries: list[str] = [] - for i, entry in enumerate(section.entries): - is_first_entry = bool(i == 0) - entries.append( - self.template( - section.entry_type, - entry=entry, - section_title=section.title, - entry_type=section.entry_type, - is_first_entry=is_first_entry, - ) - ) - sections.append((section_beginning, entries)) - - result: tuple[str, list[tuple[str, list[str]]]] = (header, sections) - return result - - def template( - self, - template_name: str, - entry: Optional[data.Entry] = None, - **kwargs, - ) -> str: - """Template one of the files in the `themes` directory. - - Args: - template_name: The name of the template file. - entry: The data model of the entry. - - Returns: - The templated file. - """ - return super().template( - "markdown", - template_name, - "md", - entry, - **kwargs, - ) - - def get_full_code(self) -> str: - """Get the Markdown code of the file. - - Returns: - The Markdown code. - """ - header, sections = self.render_templates() - code: str = super().get_full_code( - "main.j2.md", - header=header, - sections=sections, - ) - return code - - def create_file(self, file_path: pathlib.Path): - """Write the Markdown code to a file.""" - file_path.write_text(self.get_full_code(), encoding="utf-8") - - -def input_template_to_typst( - input_template: Optional[str], placeholders: dict[str, Optional[str]] -) -> str: - """Convert an input template to Typst. - - Args: - input_template: The input template. - placeholders: The placeholders and their values. - - Returns: - Typst string. - """ - if input_template is None: - return "" - - output = replace_placeholders_with_actual_values( - markdown_to_typst(input_template), - placeholders, - ) - - # If \n is escaped, revert: - output = output.replace("\\n", "\n") - - # If there are blank italics and bolds, remove them: - output = output.replace("#emph[]", "") - output = output.replace("#strong[]", "") - - # Check if there are any letters in the input template. If not, return an empty - if not re.search(r"[a-zA-Z]", input_template): - return "" - - # Find italic and bold links and fix them: - # For example: - # Convert `#emph[#link("https://google.com")[italic link]]` to - # `#link("https://google.com")[#emph[italic link]]` - output = re.sub( - r"#emph\[#link\(\"(.*?)\"\)\[(.*?)\]\]", - r'#link("\1")[#emph[\2]]', - output, - ) - output = re.sub( - r"#strong\[#link\(\"(.*?)\"\)\[(.*?)\]\]", - r'#link("\1")[#strong[\2]]', - output, - ) - output = re.sub( - r"#strong\[#emph\[#link\(\"(.*?)\"\)\[(.*?)\]\]\]", - r'#link("\1")[#strong[#emph[\2]]]', - output, - ) - - # Replace all multiple \n with a double \n: - output = re.sub(r"\n+", r"\n\n", output) - - # Strip whitespace - output = output.strip() - - # Strip non-alphanumeric, non-typst characters from the beginning and end of the - # string. For example, when location is not given in a template like this: - # "NAME -- LOCATION", "NAME -- " should become "NAME". - output = re.sub(r"^[^\w\s#\[\]\n\(\)]*", "", output) - output = re.sub(r"[^\w\s#\[\]\n\(\)]*$", "", output) - - return output # noqa: RET504 - - -@overload -def remove_typst_commands(string: None) -> None: ... - - -@overload -def remove_typst_commands(string: str) -> str: ... - - -def remove_typst_commands(string: Optional[str]) -> Optional[str]: - """Remove Typst commands from a string. - - Args: - string: The string to remove Typst commands from. - - Returns: - The string without Typst commands. - """ - if string: - commands = re.findall(r"\#.*?\[.*?\]", string) - for command in commands: - string = string.replace(command, "") - - return string - - return None - - -def escape_characters(string: str, escape_dictionary: dict[str, str]) -> str: - """Escape characters in a string by using `escape_dictionary`, where keys are - characters to escape and values are their escaped versions. - - Example: - ```python - escape_characters("This is a # string.", {"#": "\\#"}) - ``` - returns - `"This is a \\# string."` - - Args: - string: The string to escape. - escape_dictionary: The dictionary of escape characters. - - Returns: - The escaped string. - """ - - translation_map = str.maketrans(escape_dictionary) - - # Don't escape urls as hyperref package will do it automatically: - # Find all the links in the sentence: - links = re.findall(r"\[(.*?)\]\((.*?)\)", string) - - # Replace the links with a dummy string and save links with escaped characters: - new_links = [] - for i, link in enumerate(links): - placeholder = link[0] - escaped_placeholder = placeholder.translate(translation_map) - url = link[1] - - original_link = f"[{placeholder}]({url})" - string = string.replace(original_link, f"!!-link{i}-!!") - - new_link = f"[{escaped_placeholder}]({url})" - new_links.append(new_link) - - # If there are equations in the sentence, don't escape the special characters: - # Find all the equations in the sentence: - equations = re.findall(r"(\$\$.*?\$\$)", string) - new_equations = [] - for i, equation in enumerate(equations): - string = string.replace(equation, f"!!-equation{i}-!!") - - # Keep only one dollar sign for inline equations: - new_equation = equation.replace("$$", "$") - new_equations.append(new_equation) - - # If there are Typst commands, don't escape the special characters: - commands = re.findall(r"(\#.*?\[.*?\])", string) - for i, command in enumerate(commands): - string = string.replace(command, f"!!-command{i}-!!") - - # Loop through the letters of the sentence and if you find an escape character, - # replace it with their equivalent: - string = string.translate(translation_map) - - # Replace !!-link{i}-!!" with the original urls: - for i, new_link in enumerate(new_links): - string = string.replace(f"!!-link{i}-!!", new_link) - - # Replace !!-equation{i}-!!" with the original equations: - for i, new_equation in enumerate(new_equations): - string = string.replace(f"!!-equation{i}-!!", new_equation) - - # Replace !!-command{i}-!!" with the original commands: - for i, command in enumerate(commands): - string = string.replace(f"!!-command{i}-!!", command) - - return string - - -@overload -def escape_typst_characters(string: None) -> None: ... - - -@overload -def escape_typst_characters(string: str) -> str: ... - - -def escape_typst_characters(string: Optional[str]) -> Optional[str]: - """Escape Typst characters in a string by adding a backslash before them. - - Example: - ```python - escape_typst_characters("This is a # string.") - ``` - returns - `"This is a \\# string."` - - Args: - string: The string to escape. - - Returns: - The escaped string. - """ - if string is None: - return None - - escape_dictionary = { - "[": "\\[", - "]": "\\]", - "(": "\\(", - ")": "\\)", - "\\": "\\\\", - '"': '\\"', - "#": "\\#", - "$": "\\$", - "@": "\\@", - "%": "\\%", - "~": "\\~", - "_": "\\_", - "/": "\\/", - } - - return escape_characters(string, escape_dictionary) - - -def markdown_to_typst(markdown_string: str) -> str: - """Convert a Markdown string to Typst. - - Example: - ```python - markdown_to_typst( - "This is a **bold** text with an [*italic link*](https://google.com)." - ) - ``` - - returns - - `"This is a *bold* text with an #link("https://google.com")[_italic link_]."` - - Args: - markdown_string: The Markdown string to convert. - - Returns: - The Typst string. - """ - # convert links - links = re.findall(r"\[([^\]\[]*)\]\((.*?)\)", markdown_string) - if links is not None: - for link in links: - link_text = link[0] - link_url = link[1] - - old_link_string = f"[{link_text}]({link_url})" - new_link_string = f'#link("{link_url}")[{link_text}]' - - markdown_string = markdown_string.replace(old_link_string, new_link_string) - - # Process escaped asterisks in the yaml (such that they are actual asterisks, - # and not markers for bold/italics). We need to temporarily replace them with - # a dummy string. - - ONE_STAR = "ONE_STAR" - - # NOTE: We get a mix of escape levels depending on whether the star is in a quoted - # or unquoted yaml entry. This is a bit of a mess but below seems to work - # as i would instinctively expect. - markdown_string = markdown_string.replace("\\\\*", ONE_STAR) - markdown_string = markdown_string.replace("\\*", ONE_STAR) - - # convert bold and italic: - bold_and_italics = re.findall(r"\*\*\*(.+?)\*\*\*", markdown_string) - if bold_and_italics is not None: - for bold_and_italic_text in bold_and_italics: - old_bold_and_italic_text = f"***{bold_and_italic_text}***" - new_bold_and_italic_text = f"#strong[#emph[{bold_and_italic_text}]]" - - markdown_string = markdown_string.replace( - old_bold_and_italic_text, new_bold_and_italic_text - ) - - # convert bold - bolds = re.findall(r"\*\*(.+?)\*\*", markdown_string) - if bolds is not None: - for bold_text in bolds: - old_bold_text = f"**{bold_text}**" - new_bold_text = f"#strong[{bold_text}]" - markdown_string = markdown_string.replace(old_bold_text, new_bold_text) - - # convert italic - italics = re.findall(r"\*(.+?)\*", markdown_string) - if italics is not None: - for italic_text in italics: - old_italic_text = f"*{italic_text}*" - new_italic_text = f"#emph[{italic_text}]" - - markdown_string = markdown_string.replace(old_italic_text, new_italic_text) - - # Revert normal asterisks then convert them to Typst's asterisks - markdown_string = markdown_string.replace(ONE_STAR, "*") - - # convert any remaining asterisks to Typst's asterisk - # - Asterisk with a space can just be replaced. - # - Asterisk without a space needs a zero-width box to delimit it. - typst_asterisk = "#sym.ast.basic" - zero_box = "#h(0pt, weak: true) " - markdown_string = markdown_string.replace("* ", typst_asterisk + " ") - markdown_string = markdown_string.replace("*", typst_asterisk + zero_box) - - # At this point, the document ought to have absolutely no '*' characters left! - # NOTE: The final typst file might still have some asterisks when specifying a - # size, for example `#v(design-text-font-size * 0.4)` - # XXX: Maybe put this behind some kind of debug flag? -MK - # assert "*" not in markdown_string - - return markdown_string # noqa: RET504 - - -def transform_markdown_sections_to_something_else_sections( - sections: dict[str, data.SectionContents], - functions_to_apply: list[Callable], -) -> Optional[dict[str, data.SectionContents]]: - """ - Recursively loop through sections and update all the strings by applying the - `functions_to_apply` functions, given as an argument. - - Args: - sections: Sections with Markdown strings. - functions_to_apply: Functions to apply to the strings. - - Returns: - Sections with updated strings. - """ - - def apply_functions_to_string(string: str): - for function in functions_to_apply: - string = function(string) - return string - - for key, value in sections.items(): - transformed_list = [] - for entry in value: - if isinstance(entry, str): - # Then it means it's a TextEntry. - result = apply_functions_to_string(entry) - transformed_list.append(result) - else: - # Then it means it's one of the other entries. - fields_to_skip = ["doi", "url"] - entry_as_dict = entry.model_dump() - for entry_key, inner_value in entry_as_dict.items(): - if entry_key in fields_to_skip: - continue - if isinstance(inner_value, str): - result = apply_functions_to_string(inner_value) - setattr(entry, entry_key, result) - elif isinstance(inner_value, list): - for j, item in enumerate(inner_value): - if isinstance(item, str): - inner_value[j] = apply_functions_to_string(item) - setattr(entry, entry_key, inner_value) - transformed_list.append(entry) - - sections[key] = transformed_list - - return sections - - -def transform_markdown_sections_to_typst_sections( - sections: dict[str, data.SectionContents], -) -> Optional[dict[str, data.SectionContents]]: - """ - Recursively loop through sections and convert all the Markdown strings (user input - is in Markdown format) to Typst strings. - - Args: - sections: Sections with Markdown strings. - - Returns: - Sections with Typst strings. - """ - return transform_markdown_sections_to_something_else_sections( - sections, - [escape_typst_characters, markdown_to_typst], - ) - - -def replace_placeholders_with_actual_values( - text: str, - placeholders: dict[str, Optional[str]], -) -> str: - """Replace the placeholders in a string with actual values. - - This function can be used as a Jinja2 filter in templates. - - Args: - text: The text with placeholders. - placeholders: The placeholders and their values. - - Returns: - The string with actual values. - """ - for placeholder, value in placeholders.items(): - if value: - text = text.replace(placeholder, str(value)) - else: - text = text.replace(placeholder, "") - - return text - - -class Jinja2Environment: - instance: "Jinja2Environment" - environment: jinja2.Environment - current_working_directory: Optional[pathlib.Path] = None - - def __new__(cls): - if ( - not hasattr(cls, "instance") - or cls.current_working_directory != pathlib.Path.cwd() - ): - cls.instance = super().__new__(cls) - - themes_directory = pathlib.Path(__file__).parent.parent / "themes" - - # create a Jinja2 environment: - # we need to add the current working directory because custom themes might be used. - environment = jinja2.Environment( - loader=jinja2.FileSystemLoader([pathlib.Path.cwd(), themes_directory]), - trim_blocks=True, - lstrip_blocks=True, - ) - - # set custom delimiters: - environment.block_start_string = "((*" - environment.block_end_string = "*))" - environment.variable_start_string = "<<" - environment.variable_end_string = ">>" - environment.comment_start_string = "((#" - environment.comment_end_string = "#))" - - # add custom Jinja2 filters: - environment.filters["replace_placeholders_with_actual_values"] = ( - replace_placeholders_with_actual_values - ) - environment.filters["escape_typst_characters"] = escape_typst_characters - environment.filters["markdown_to_typst"] = markdown_to_typst - environment.filters["make_a_url_clean"] = data.make_a_url_clean - environment.filters["remove_typst_commands"] = remove_typst_commands - - cls.environment = environment - - return cls.instance diff --git a/rendercv/themes/__init__.py b/rendercv/themes/__init__.py deleted file mode 100644 index 799c81385..000000000 --- a/rendercv/themes/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -The `rendercv.themes` package contains all the built-in templates and the design data -models for the themes. -""" - -from .classic import ClassicThemeOptions -from .engineeringclassic import EngineeringclassicThemeOptions -from .engineeringresumes import EngineeringresumesThemeOptions -from .moderncv import ModerncvThemeOptions -from .sb2nov import Sb2novThemeOptions - -__all__ = [ - "ClassicThemeOptions", - "EngineeringclassicThemeOptions", - "EngineeringresumesThemeOptions", - "ModerncvThemeOptions", - "Sb2novThemeOptions", -] diff --git a/rendercv/themes/classic/BulletEntry.j2.typ b/rendercv/themes/classic/BulletEntry.j2.typ deleted file mode 100644 index addd8c5ab..000000000 --- a/rendercv/themes/classic/BulletEntry.j2.typ +++ /dev/null @@ -1 +0,0 @@ -#one-col-entry(content: [#bullet-entry[<>]]) diff --git a/rendercv/themes/classic/EducationEntry.j2.typ b/rendercv/themes/classic/EducationEntry.j2.typ deleted file mode 100644 index 6c4eb9d36..000000000 --- a/rendercv/themes/classic/EducationEntry.j2.typ +++ /dev/null @@ -1,106 +0,0 @@ -((* if date_and_location_column_template and design.entry_types.education_entry.degree_column_template *)) -// YES DATE, YES DEGREE -#three-col-entry( - left-column-width: <>, - left-content: [<>], - middle-content: [ - <> - ((* if design.entries.short_second_row or date_and_location_column_template.count("\n\n") > main_column_first_row_template.count("\n\n") or design.section_titles.type=="moderncv" *)) - ((* if main_column_second_row_template *)) - #v(-design-text-leading) - ((* endif *)) - - <> - ((* endif *)) - ], - right-content: [ - <> - ], -) -((* if not (design.entries.short_second_row or date_and_location_column_template.count("\n\n") > main_column_first_row_template.count("\n\n")) and main_column_second_row_template *)) -#block( - [ - #set par(spacing: 0pt) - <> - ], - inset: ( - left: design-entry-types-education-entry-degree-column-width + design-entries-horizontal-space-between-columns + design-entries-left-and-right-margin, - right: design-entries-left-and-right-margin, - ), -) -((* endif *)) -((* elif date_and_location_column_template and not design.entry_types.education_entry.degree_column_template *)) -// YES DATE, NO DEGREE -#two-col-entry( - left-content: [ - <> - ((* if design.entries.short_second_row or date_and_location_column_template.count("\n\n") > main_column_first_row_template.count("\n\n") or design.section_titles.type=="moderncv" *)) - ((* if main_column_second_row_template *)) - #v(-design-text-leading) - ((* endif *)) - - <> - ((* endif *)) - ], - right-content: [ - <> - ], -) - ((* if not (design.entries.short_second_row or date_and_location_column_template.count("\n\n") > main_column_first_row_template.count("\n\n") or design.section_titles.type=="moderncv") *)) -#block( - [ - #set par(spacing: 0pt) - <> - ], - inset: ( - left: design-entries-left-and-right-margin, - right: design-entries-left-and-right-margin, - ), -) -((* endif *)) -((* elif not date_and_location_column_template and design.entry_types.education_entry.degree_column_template *)) -// NO DATE, YES DEGREE -#two-col-entry( - left-column-width: <>, - right-column-width: 1fr, - alignments: (left, left), - left-content: [ - <> - ], - right-content: [ - <> - ((* if design.entries.short_second_row or date_and_location_column_template.count("\n\n") > main_column_first_row_template.count("\n\n") or design.section_titles.type=="moderncv" *)) - ((* if main_column_second_row_template *)) - #v(-design-text-leading) - ((* endif *)) - - <> - ((* endif *)) - ], -) -((* if not (design.entries.short_second_row or date_and_location_column_template.count("\n\n") > main_column_first_row_template.count("\n\n")) and main_column_second_row_template *)) -#block( - [ - #set par(spacing: 0pt) - <> - ], - inset: ( - left: design-entry-types-education-entry-degree-column-width + design-entries-horizontal-space-between-columns + design-entries-left-and-right-margin, - right: design-entries-left-and-right-margin, - ), -) -((* endif *)) -((* else *)) -// NO DATE, NO DEGREE - -#one-col-entry( - content: [ - <> - - ((* if main_column_second_row_template *)) - #v(-design-text-leading) - ((* endif *)) - <> - ], -) -((* endif *)) \ No newline at end of file diff --git a/rendercv/themes/classic/ExperienceEntry.j2.typ b/rendercv/themes/classic/ExperienceEntry.j2.typ deleted file mode 100644 index 163487f71..000000000 --- a/rendercv/themes/classic/ExperienceEntry.j2.typ +++ /dev/null @@ -1,36 +0,0 @@ -((* if date_and_location_column_template *)) -#two-col-entry( - left-content: [ - <> - ((* if design.entries.short_second_row or date_and_location_column_template.count("\n\n") > main_column_first_row_template.count("\n\n") or design.section_titles.type=="moderncv" *)) - ((* if main_column_second_row_template *)) - #v(-design-text-leading) - ((* endif *)) - - <> - ((* endif *)) - ], - right-content: [ - <> - ], -) - ((* if not (design.entries.short_second_row or date_and_location_column_template.count("\n\n") > main_column_first_row_template.count("\n\n") or design.section_titles.type=="moderncv") *)) -#one-col-entry( - content: [ - <> - ], -) -((* endif *)) -((* else *)) - -#one-col-entry( - content: [ - <> - - ((* if main_column_second_row_template *)) - #v(-design-text-leading) - ((* endif *)) - <> - ], -) -((* endif *)) diff --git a/rendercv/themes/classic/Header.j2.typ b/rendercv/themes/classic/Header.j2.typ deleted file mode 100644 index 4a2614ec2..000000000 --- a/rendercv/themes/classic/Header.j2.typ +++ /dev/null @@ -1,42 +0,0 @@ -((* if cv.photo *)) -#two-col( - left-column-width: design-header-photo-width * 1.1, - right-column-width: 1fr, - left-content: [ - #align( - left + horizon, - image("profile_picture.jpg", width: design-header-photo-width), - ) - ], - column-gutter: 0cm, - right-content: [ -((* endif *)) -((* if cv.name *)) -= <> -((* endif *)) - -// Print connections: -#let connections-list = ( -((* for connection in cv.connections *)) - [((*- if connection["url"] and design.header.make_connections_links -*)) - #box(original-link("<>")[ - ((*- endif -*)) - ((*- if design.header.use_icons_for_connections -*)) - #fa-icon("<>", size: 0.9em) #h(0.05cm) - ((*- endif -*)) - ((*- if not design.header.use_urls_as_placeholders_for_connections or not connection["url"] -*)) - <> - ((*- else -*)) - <> - ((*- endif -*)) - ((*- if connection["url"] and design.header.make_connections_links -*)) - ]) - ((*- endif -*))], -((* endfor *)) -) -#connections(connections-list) - -((* if cv.photo *)) - ], -) -((* endif *)) diff --git a/rendercv/themes/classic/NormalEntry.j2.typ b/rendercv/themes/classic/NormalEntry.j2.typ deleted file mode 100644 index 163487f71..000000000 --- a/rendercv/themes/classic/NormalEntry.j2.typ +++ /dev/null @@ -1,36 +0,0 @@ -((* if date_and_location_column_template *)) -#two-col-entry( - left-content: [ - <> - ((* if design.entries.short_second_row or date_and_location_column_template.count("\n\n") > main_column_first_row_template.count("\n\n") or design.section_titles.type=="moderncv" *)) - ((* if main_column_second_row_template *)) - #v(-design-text-leading) - ((* endif *)) - - <> - ((* endif *)) - ], - right-content: [ - <> - ], -) - ((* if not (design.entries.short_second_row or date_and_location_column_template.count("\n\n") > main_column_first_row_template.count("\n\n") or design.section_titles.type=="moderncv") *)) -#one-col-entry( - content: [ - <> - ], -) -((* endif *)) -((* else *)) - -#one-col-entry( - content: [ - <> - - ((* if main_column_second_row_template *)) - #v(-design-text-leading) - ((* endif *)) - <> - ], -) -((* endif *)) diff --git a/rendercv/themes/classic/NumberedEntry.j2.typ b/rendercv/themes/classic/NumberedEntry.j2.typ deleted file mode 100644 index b8ec33eea..000000000 --- a/rendercv/themes/classic/NumberedEntry.j2.typ +++ /dev/null @@ -1 +0,0 @@ -+ <> \ No newline at end of file diff --git a/rendercv/themes/classic/OneLineEntry.j2.typ b/rendercv/themes/classic/OneLineEntry.j2.typ deleted file mode 100644 index f48dd4115..000000000 --- a/rendercv/themes/classic/OneLineEntry.j2.typ +++ /dev/null @@ -1,3 +0,0 @@ -#one-col-entry( - content: [<