├── .gitattributes ├── .github └── workflows │ ├── evict_caches.yaml │ ├── push_tip_to_trybot.yaml │ ├── trybot.yaml │ └── trybot_dispatch.yaml ├── .gitignore ├── LICENSE ├── README.md ├── codereview.cfg ├── cue.mod ├── module.cue ├── pkg │ └── github.com │ │ └── SchemaStore │ │ └── schemastore │ │ └── src │ │ └── schemas │ │ └── json │ │ └── github-workflow.cue └── usr │ └── github.com │ └── SchemaStore │ └── schemastore │ └── src │ └── schemas │ └── json │ └── workflow.cue ├── cue ├── __init__.py ├── build.py ├── compile.py ├── context.py ├── error.py ├── eval.py ├── kind.py ├── res.py ├── result.py └── value.py ├── internal └── ci │ ├── base │ ├── base.cue │ ├── codereview.cue │ ├── gerrithub.cue │ ├── github.cue │ └── helpers.cue │ ├── ci_tool.cue │ ├── github │ ├── repo.cue │ ├── trybot.cue │ └── workflows.cue │ ├── repo │ └── repo.cue │ └── vendor │ └── vendor_tool.cue ├── libcue ├── __init__.py └── api.py ├── requirements.txt └── tests ├── __init__.py ├── test_context.py └── test_value.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/evict_caches.yaml: -------------------------------------------------------------------------------- 1 | # Code generated internal/ci/ci_tool.cue; DO NOT EDIT. 2 | 3 | name: Evict caches 4 | "on": 5 | schedule: 6 | - cron: 0 2 * * * 7 | jobs: 8 | test: 9 | if: ${{github.repository == 'cue-lang/cue-py'}} 10 | runs-on: ubuntu-22.04 11 | defaults: 12 | run: 13 | shell: bash 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | with: 18 | ref: ${{ github.event.pull_request.head.sha }} 19 | fetch-depth: 0 20 | - name: Reset git directory modification times 21 | run: touch -t 202211302355 $(find * -type d) 22 | - name: Restore git file modification times 23 | uses: chetan/git-restore-mtime-action@075f9bc9d159805603419d50f794bd9f33252ebe 24 | - id: DispatchTrailer 25 | name: Try to extract Dispatch-Trailer 26 | run: |- 27 | x="$(git log -1 --pretty='%(trailers:key=Dispatch-Trailer,valueonly)')" 28 | if [[ "$x" == "" ]] 29 | then 30 | # Some steps rely on the presence or otherwise of the Dispatch-Trailer. 31 | # We know that we don't have a Dispatch-Trailer in this situation, 32 | # hence we use the JSON value null in order to represent that state. 33 | # This means that GitHub expressions can determine whether a Dispatch-Trailer 34 | # is present or not by checking whether the fromJSON() result of the 35 | # output from this step is the JSON value null or not. 36 | x=null 37 | fi 38 | echo "value<> $GITHUB_OUTPUT 39 | echo "$x" >> $GITHUB_OUTPUT 40 | echo "EOD" >> $GITHUB_OUTPUT 41 | - if: |- 42 | ((github.ref == 'refs/heads/main') && (! (contains(github.event.head_commit.message, ' 43 | Dispatch-Trailer: {"type":"')))) && (contains(github.event.head_commit.message, ' 44 | Dispatch-Trailer: {"type":"')) 45 | name: Check we don't have Dispatch-Trailer on a protected branch 46 | run: |- 47 | echo "github.event.head_commit.message contains Dispatch-Trailer but we are on a protected branch" 48 | false 49 | - name: Delete caches 50 | run: |- 51 | set -x 52 | 53 | echo ${{ secrets.CUECKOO_GITHUB_PAT }} | gh auth login --with-token 54 | gh extension install actions/gh-actions-cache 55 | for i in https://github.com/cue-lang/cue-py https://github.com/cue-lang/cue-py-trybot 56 | do 57 | echo "Evicting caches for $i" 58 | cd $(mktemp -d) 59 | git init -b initialbranch 60 | git remote add origin $i 61 | for j in $(gh actions-cache list -L 100 | grep refs/ | awk '{print $1}') 62 | do 63 | gh actions-cache delete --confirm $j 64 | done 65 | done 66 | - name: Trigger workflow runs to repopulate caches 67 | run: |- 68 | # Prepare git for pushes to trybot repo. Note 69 | # because we have already checked out code we don't 70 | # need origin. Fetch origin default branch for later use 71 | git config user.name cueckoo 72 | git config user.email cueckoo@gmail.com 73 | git config http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n cueckoo:${{ secrets.CUECKOO_GITHUB_PAT }} | base64)" 74 | git remote add trybot https://github.com/cue-lang/cue-py-trybot 75 | 76 | # Now trigger the most recent workflow run on each of the default branches. 77 | # We do this by listing all the branches on the main repo and finding those 78 | # which match the protected branch patterns (globs). 79 | for j in $(curl -s -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.CUECKOO_GITHUB_PAT }}" -H "X-GitHub-Api-Version: 2022-11-28" -f https://api.github.com/repos/cue-lang/cue-py/branches | jq -r '.[] | .name') 80 | do 81 | for i in main 82 | do 83 | if [[ "$j" != $i ]]; then 84 | continue 85 | fi 86 | 87 | echo Branch: $j 88 | sha=$(curl -s -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.CUECKOO_GITHUB_PAT }}" -H "X-GitHub-Api-Version: 2022-11-28" "https://api.github.com/repos/cue-lang/cue-py/commits/$j" | jq -r '.sha') 89 | echo Latest commit: $sha 90 | 91 | echo "Trigger workflow on cue-lang/cue-py" 92 | curl -s -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.CUECKOO_GITHUB_PAT }}" -H "X-GitHub-Api-Version: 2022-11-28" --fail-with-body -X POST https://api.github.com/repos/cue-lang/cue-py/actions/workflows/trybot.yaml/dispatches -d "{\"ref\":\"$j\"}" 93 | 94 | # Ensure that the trybot repo has the latest commit for 95 | # this branch. If the force-push results in a commit 96 | # being pushed, that will trigger the trybot workflows 97 | # so we don't need to do anything, otherwise we need to 98 | # trigger the most recent commit on that branch 99 | git remote -v 100 | git fetch origin refs/heads/$j 101 | git log -1 FETCH_HEAD 102 | 103 | success=false 104 | for try in {1..20}; do 105 | echo "Push to trybot try $try" 106 | exitCode=0; push="$(git push -f trybot FETCH_HEAD:$j 2>&1)" || exitCode=$? 107 | echo "$push" 108 | if [[ $exitCode -eq 0 ]]; then 109 | success=true 110 | break 111 | fi 112 | sleep 1 113 | done 114 | if ! $success; then 115 | echo "Giving up" 116 | exit 1 117 | fi 118 | 119 | if echo "$push" | grep up-to-date 120 | then 121 | # We are up-to-date, i.e. the push did nothing, hence we need to trigger a workflow_dispatch 122 | # in the trybot repo. 123 | echo "Trigger workflow on cue-lang/cue-py-trybot" 124 | curl -s -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.CUECKOO_GITHUB_PAT }}" -H "X-GitHub-Api-Version: 2022-11-28" --fail-with-body -X POST https://api.github.com/repos/cue-lang/cue-py-trybot/actions/workflows/trybot.yaml/dispatches -d "{\"ref\":\"$j\"}" 125 | else 126 | echo "Force-push to cue-lang/cue-py-trybot did work; nothing to do" 127 | fi 128 | done 129 | done 130 | -------------------------------------------------------------------------------- /.github/workflows/push_tip_to_trybot.yaml: -------------------------------------------------------------------------------- 1 | # Code generated internal/ci/ci_tool.cue; DO NOT EDIT. 2 | 3 | name: Push tip to trybot 4 | "on": 5 | push: 6 | branches: 7 | - main 8 | concurrency: push_tip_to_trybot 9 | jobs: 10 | push: 11 | runs-on: ubuntu-22.04 12 | defaults: 13 | run: 14 | shell: bash 15 | if: ${{github.repository == 'cue-lang/cue-py'}} 16 | steps: 17 | - name: Write netrc file for cueckoo Gerrithub 18 | run: |- 19 | cat < ~/.netrc 20 | machine review.gerrithub.io 21 | login cueckoo 22 | password ${{ secrets.CUECKOO_GERRITHUB_PASSWORD }} 23 | EOD 24 | chmod 600 ~/.netrc 25 | - name: Push tip to trybot 26 | run: |- 27 | mkdir tmpgit 28 | cd tmpgit 29 | git init -b initialbranch 30 | git config user.name cueckoo 31 | git config user.email cueckoo@gmail.com 32 | git config http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n cueckoo:${{ secrets.CUECKOO_GITHUB_PAT }} | base64)" 33 | git remote add origin https://review.gerrithub.io/a/cue-lang/cue-py 34 | git remote add trybot https://github.com/cue-lang/cue-py-trybot 35 | 36 | git fetch origin "${{ github.ref }}" 37 | 38 | success=false 39 | for try in {1..20}; do 40 | echo "Push to trybot try $try" 41 | if git push -f trybot "FETCH_HEAD:${{ github.ref }}"; then 42 | success=true 43 | break 44 | fi 45 | sleep 1 46 | done 47 | if ! $success; then 48 | echo "Giving up" 49 | exit 1 50 | fi 51 | -------------------------------------------------------------------------------- /.github/workflows/trybot.yaml: -------------------------------------------------------------------------------- 1 | # Code generated internal/ci/ci_tool.cue; DO NOT EDIT. 2 | 3 | name: TryBot 4 | "on": 5 | push: 6 | branches: 7 | - ci/test 8 | - main 9 | pull_request: {} 10 | workflow_dispatch: {} 11 | jobs: 12 | test: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | go-version: 17 | - 1.23.x 18 | python-version: 19 | - "3.12" 20 | runner: 21 | - ubuntu-22.04 22 | - macos-14 23 | runs-on: ${{ matrix.runner }} 24 | defaults: 25 | run: 26 | shell: bash 27 | if: |- 28 | (contains(github.event.head_commit.message, ' 29 | Dispatch-Trailer: {"type":"trybot"')) || ! (contains(github.event.head_commit.message, ' 30 | Dispatch-Trailer: {"type":"')) 31 | steps: 32 | - name: Checkout code 33 | uses: actions/checkout@v4 34 | with: 35 | ref: ${{ github.event.pull_request.head.sha }} 36 | fetch-depth: 0 37 | - name: Reset git directory modification times 38 | run: touch -t 202211302355 $(find * -type d) 39 | - name: Restore git file modification times 40 | uses: chetan/git-restore-mtime-action@075f9bc9d159805603419d50f794bd9f33252ebe 41 | - id: DispatchTrailer 42 | name: Try to extract Dispatch-Trailer 43 | run: |- 44 | x="$(git log -1 --pretty='%(trailers:key=Dispatch-Trailer,valueonly)')" 45 | if [[ "$x" == "" ]] 46 | then 47 | # Some steps rely on the presence or otherwise of the Dispatch-Trailer. 48 | # We know that we don't have a Dispatch-Trailer in this situation, 49 | # hence we use the JSON value null in order to represent that state. 50 | # This means that GitHub expressions can determine whether a Dispatch-Trailer 51 | # is present or not by checking whether the fromJSON() result of the 52 | # output from this step is the JSON value null or not. 53 | x=null 54 | fi 55 | echo "value<> $GITHUB_OUTPUT 56 | echo "$x" >> $GITHUB_OUTPUT 57 | echo "EOD" >> $GITHUB_OUTPUT 58 | - if: |- 59 | ((github.ref == 'refs/heads/main') && (! (contains(github.event.head_commit.message, ' 60 | Dispatch-Trailer: {"type":"')))) && (contains(github.event.head_commit.message, ' 61 | Dispatch-Trailer: {"type":"')) 62 | name: Check we don't have Dispatch-Trailer on a protected branch 63 | run: |- 64 | echo "github.event.head_commit.message contains Dispatch-Trailer but we are on a protected branch" 65 | false 66 | - uses: cue-lang/setup-cue@v1.0.0 67 | with: 68 | version: v0.10.0 69 | - name: Install Go 70 | uses: actions/setup-go@v5 71 | with: 72 | cache: false 73 | go-version: ${{ matrix.go-version }} 74 | - name: Set common go env vars 75 | run: |- 76 | go env -w GOTOOLCHAIN=local 77 | 78 | # Dump env for good measure 79 | go env 80 | - name: Early git and code sanity checks 81 | run: go run cuelang.org/go/internal/ci/checks@v0.11.0-0.dev.0.20240903133435-46fb300df650 82 | - name: Re-generate CI 83 | run: |- 84 | cue cmd importjsonschema ./vendor 85 | cue cmd gen 86 | working-directory: ./internal/ci 87 | - if: always() 88 | name: Check that git is clean at the end of the job 89 | run: test -z "$(git status --porcelain)" || (git status; git diff; false) 90 | - name: Install Python 91 | uses: actions/setup-python@v5 92 | with: 93 | python-version: ${{ matrix.python-version }} 94 | cache: pip 95 | - id: go-mod-cache-dir 96 | name: Get go mod cache directory 97 | run: echo "dir=$(go env GOMODCACHE)" >> ${GITHUB_OUTPUT} 98 | - id: go-cache-dir 99 | name: Get go build/test cache directory 100 | run: echo "dir=$(go env GOCACHE)" >> ${GITHUB_OUTPUT} 101 | - if: |- 102 | (((github.ref == 'refs/heads/main') && (! (contains(github.event.head_commit.message, ' 103 | Dispatch-Trailer: {"type":"')))) || (github.ref == 'refs/heads/ci/test')) 104 | uses: actions/cache@v4 105 | with: 106 | path: |- 107 | ${{ steps.go-mod-cache-dir.outputs.dir }}/cache/download 108 | ${{ steps.go-cache-dir.outputs.dir }} 109 | key: ${{ runner.os }}-${{ matrix.go-version }}-${{ github.run_id }} 110 | restore-keys: ${{ runner.os }}-${{ matrix.go-version }} 111 | - if: |- 112 | ! (((github.ref == 'refs/heads/main') && (! (contains(github.event.head_commit.message, ' 113 | Dispatch-Trailer: {"type":"')))) || (github.ref == 'refs/heads/ci/test')) 114 | uses: actions/cache/restore@v4 115 | with: 116 | path: |- 117 | ${{ steps.go-mod-cache-dir.outputs.dir }}/cache/download 118 | ${{ steps.go-cache-dir.outputs.dir }} 119 | key: ${{ runner.os }}-${{ matrix.go-version }}-${{ github.run_id }} 120 | restore-keys: ${{ runner.os }}-${{ matrix.go-version }} 121 | - if: |- 122 | github.repository == 'cue-lang/cue-py' && (((github.ref == 'refs/heads/main') && (! (contains(github.event.head_commit.message, ' 123 | Dispatch-Trailer: {"type":"')))) || github.ref == 'refs/heads/ci/test') 124 | run: go clean -testcache 125 | - name: pip install 126 | run: pip install -r requirements.txt 127 | - name: Checkout libcue 128 | uses: actions/checkout@v4 129 | with: 130 | repository: cue-lang/libcue 131 | path: libcue-checkout 132 | - name: Build libcue 133 | run: |- 134 | go build -o libcue.so -buildmode=c-shared 135 | cp libcue.so libcue.dylib 136 | cp libcue.so cue.dll 137 | working-directory: libcue-checkout 138 | - name: mypy 139 | run: mypy . 140 | - if: runner.os == 'Windows' 141 | name: Add libcue to PATH 142 | run: echo '${{ github.workspace }}/libcue-checkout' >> $GITHUB_PATH 143 | - name: pytest 144 | run: pytest 145 | env: 146 | LD_LIBRARY_PATH: ${{ github.workspace }}/libcue-checkout 147 | DYLD_LIBRARY_PATH: ${{ github.workspace }}/libcue-checkout 148 | - if: always() 149 | name: Check that git is clean at the end of the job 150 | run: test -z "$(git status --porcelain)" || (git status; git diff; false) 151 | -------------------------------------------------------------------------------- /.github/workflows/trybot_dispatch.yaml: -------------------------------------------------------------------------------- 1 | # Code generated internal/ci/ci_tool.cue; DO NOT EDIT. 2 | 3 | name: Dispatch trybot 4 | "on": 5 | repository_dispatch: {} 6 | push: 7 | branches: 8 | - ci/test 9 | jobs: 10 | trybot: 11 | runs-on: ubuntu-22.04 12 | defaults: 13 | run: 14 | shell: bash 15 | if: ${{ ((github.ref == 'refs/heads/ci/test') && false) || github.event.client_payload.type == 'trybot' }} 16 | steps: 17 | - name: Write netrc file for cueckoo Gerrithub 18 | run: |- 19 | cat < ~/.netrc 20 | machine review.gerrithub.io 21 | login cueckoo 22 | password ${{ secrets.CUECKOO_GERRITHUB_PASSWORD }} 23 | EOD 24 | chmod 600 ~/.netrc 25 | - id: payload 26 | if: github.repository == 'cue-lang/cue-py' && (github.ref == 'refs/heads/ci/test') 27 | name: Write fake payload 28 | run: |- 29 | cat <> $GITHUB_OUTPUT 30 | value<.needs 490 | // keyword. 491 | // Each job runs in a fresh instance of the virtual environment 492 | // specified by runs-on. 493 | // You can run an unlimited number of jobs as long as you are 494 | // within the workflow usage limits. For more information, see 495 | // https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#usage-limits. 496 | jobs!: { 497 | {[=~"^[_a-zA-Z][a-zA-Z0-9_-]*$" & !~"^()$"]: #normalJob | #reusableWorkflowCallJob} 498 | } 499 | 500 | // The name for workflow runs generated from the workflow. GitHub 501 | // displays the workflow run name in the list of workflow runs on 502 | // your repository's 'Actions' tab. 503 | "run-name"?: string 504 | permissions?: #permissions 505 | 506 | #architecture: "ARM32" | "x64" | "x86" 507 | 508 | #branch: #globs 509 | 510 | #concurrency: { 511 | // When a concurrent job or workflow is queued, if another job or 512 | // workflow using the same concurrency group in the repository is 513 | // in progress, the queued job or workflow will be pending. Any 514 | // previously pending job or workflow in the concurrency group 515 | // will be canceled. 516 | group!: string 517 | 518 | // To cancel any currently running job or workflow in the same 519 | // concurrency group, specify cancel-in-progress: true. 520 | "cancel-in-progress"?: bool | #expressionSyntax 521 | } 522 | 523 | #configuration: string | number | bool | { 524 | [string]: #configuration 525 | } | [...#configuration] 526 | 527 | #container: { 528 | // The Docker image to use as the container to run the action. The 529 | // value can be the Docker Hub image name or a registry name. 530 | image!: string 531 | 532 | // If the image's container registry requires authentication to 533 | // pull the image, you can use credentials to set a map of the 534 | // username and password. The credentials are the same values 535 | // that you would provide to the `docker login` command. 536 | credentials?: { 537 | username?: string 538 | password?: string 539 | ... 540 | } 541 | 542 | // Sets an array of environment variables in the container. 543 | env?: #env 544 | 545 | // Sets an array of ports to expose on the container. 546 | ports?: [...number | string] & [_, ...] 547 | 548 | // Sets an array of volumes for the container to use. You can use 549 | // volumes to share data between services or other steps in a 550 | // job. You can specify named Docker volumes, anonymous Docker 551 | // volumes, or bind mounts on the host. 552 | // To specify a volume, you specify the source and destination 553 | // path: : 554 | // The is a volume name or an absolute path on the host 555 | // machine, and is an absolute path in the 556 | // container. 557 | volumes?: [...=~"^[^:]+:[^:]+$"] & [_, ...] 558 | 559 | // Additional Docker container resource options. For a list of 560 | // options, see 561 | // https://docs.docker.com/engine/reference/commandline/create/#options. 562 | options?: string 563 | } 564 | 565 | #defaults: run?: { 566 | shell?: #shell 567 | "working-directory"?: #["working-directory"] 568 | } 569 | 570 | #permissions: "read-all" | "write-all" | #["permissions-event"] 571 | 572 | #: "permissions-event": { 573 | actions?: #["permissions-level"] 574 | attestations?: #["permissions-level"] 575 | checks?: #["permissions-level"] 576 | contents?: #["permissions-level"] 577 | deployments?: #["permissions-level"] 578 | discussions?: #["permissions-level"] 579 | "id-token"?: #["permissions-level"] 580 | issues?: #["permissions-level"] 581 | packages?: #["permissions-level"] 582 | pages?: #["permissions-level"] 583 | "pull-requests"?: #["permissions-level"] 584 | "repository-projects"?: #["permissions-level"] 585 | "security-events"?: #["permissions-level"] 586 | statuses?: #["permissions-level"] 587 | } 588 | 589 | #: "permissions-level": "read" | "write" | "none" 590 | 591 | #env: { 592 | [string]: bool | number | string 593 | } | #stringContainingExpressionSyntax 594 | 595 | #environment: { 596 | // The name of the environment configured in the repo. 597 | name!: string 598 | 599 | // A deployment URL 600 | url?: string 601 | } 602 | 603 | #event: "branch_protection_rule" | "check_run" | "check_suite" | "create" | "delete" | "deployment" | "deployment_status" | "discussion" | "discussion_comment" | "fork" | "gollum" | "issue_comment" | "issues" | "label" | "merge_group" | "milestone" | "page_build" | "project" | "project_card" | "project_column" | "public" | "pull_request" | "pull_request_review" | "pull_request_review_comment" | "pull_request_target" | "push" | "registry_package" | "release" | "status" | "watch" | "workflow_call" | "workflow_dispatch" | "workflow_run" | "repository_dispatch" 604 | 605 | #eventObject: null | { 606 | ... 607 | } 608 | 609 | #expressionSyntax: =~""" 610 | ^\\$\\{\\{(.|[\r 611 | ])*\\}\\}$ 612 | """ 613 | 614 | #stringContainingExpressionSyntax: =~""" 615 | ^.*\\$\\{\\{(.|[\r 616 | ])*\\}\\}.*$ 617 | """ 618 | 619 | #globs: [...strings.MinRunes(1)] & [_, ...] 620 | 621 | #machine: "linux" | "macos" | "windows" 622 | 623 | #name: =~"^[_a-zA-Z][a-zA-Z0-9_-]*$" 624 | 625 | #path: #globs 626 | 627 | #ref: null | { 628 | branches?: #branch 629 | "branches-ignore"?: #branch 630 | tags?: #branch 631 | "tags-ignore"?: #branch 632 | paths?: #path 633 | "paths-ignore"?: #path 634 | ... 635 | } 636 | 637 | #shell: string | ("bash" | "pwsh" | "python" | "sh" | "cmd" | "powershell") 638 | 639 | #types: [_, ...] 640 | 641 | #: "working-directory": string 642 | 643 | #jobNeeds: [...#name] & [_, ...] | #name 644 | 645 | #matrix: { 646 | {[=~"^(in|ex)clude$" & !~"^()$"]: #expressionSyntax | [...{ 647 | [string]: #configuration 648 | }] & [_, ...]} 649 | {[!~"^(in|ex)clude$" & !~"^()$"]: [...#configuration] & [_, ...] | #expressionSyntax} 650 | } | #expressionSyntax 651 | 652 | #reusableWorkflowCallJob: { 653 | // The name of the job displayed on GitHub. 654 | name?: string 655 | needs?: #jobNeeds 656 | permissions?: #permissions 657 | 658 | // You can use the if conditional to prevent a job from running 659 | // unless a condition is met. You can use any supported context 660 | // and expression to create a conditional. 661 | // Expressions in an if conditional do not require the ${{ }} 662 | // syntax. For more information, see 663 | // https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. 664 | if?: bool | number | string 665 | 666 | // The location and version of a reusable workflow file to run as 667 | // a job, of the form './{path/to}/{localfile}.yml' or 668 | // '{owner}/{repo}/{path}/{filename}@{ref}'. {ref} can be a SHA, 669 | // a release tag, or a branch name. Using the commit SHA is the 670 | // safest for stability and security. 671 | uses!: =~"^(.+/)+(.+)\\.(ya?ml)(@.+)?$" 672 | 673 | // A map of inputs that are passed to the called workflow. Any 674 | // inputs that you pass must match the input specifications 675 | // defined in the called workflow. Unlike 676 | // 'jobs..steps[*].with', the inputs you pass with 677 | // 'jobs..with' are not be available as environment 678 | // variables in the called workflow. Instead, you can reference 679 | // the inputs by using the inputs context. 680 | with?: #env 681 | 682 | // When a job is used to call a reusable workflow, you can use 683 | // 'secrets' to provide a map of secrets that are passed to the 684 | // called workflow. Any secrets that you pass must match the 685 | // names defined in the called workflow. 686 | secrets?: #env | "inherit" 687 | 688 | // A strategy creates a build matrix for your jobs. You can define 689 | // different variations of an environment to run each job in. 690 | strategy?: { 691 | matrix!: #matrix 692 | 693 | // When set to true, GitHub cancels all in-progress jobs if any 694 | // matrix job fails. Default: true 695 | "fail-fast"?: bool | string | *true 696 | 697 | // The maximum number of jobs that can run simultaneously when 698 | // using a matrix job strategy. By default, GitHub will maximize 699 | // the number of jobs run in parallel depending on the available 700 | // runners on GitHub-hosted virtual machines. 701 | "max-parallel"?: number | string 702 | } 703 | 704 | // Concurrency ensures that only a single job or workflow using 705 | // the same concurrency group will run at a time. A concurrency 706 | // group can be any string or expression. The expression can use 707 | // any context except for the secrets context. 708 | // You can also specify concurrency at the workflow level. 709 | // When a concurrent job or workflow is queued, if another job or 710 | // workflow using the same concurrency group in the repository is 711 | // in progress, the queued job or workflow will be pending. Any 712 | // previously pending job or workflow in the concurrency group 713 | // will be canceled. To also cancel any currently running job or 714 | // workflow in the same concurrency group, specify 715 | // cancel-in-progress: true. 716 | concurrency?: string | #concurrency 717 | } 718 | 719 | #normalJob: { 720 | // The name of the job displayed on GitHub. 721 | name?: string 722 | needs?: #jobNeeds 723 | permissions?: #permissions 724 | 725 | // The type of machine to run the job on. The machine can be 726 | // either a GitHub-hosted runner, or a self-hosted runner. 727 | "runs-on"!: string | [string] & [_, ...] | { 728 | group?: string 729 | labels?: string | [...string] 730 | ... 731 | } | #stringContainingExpressionSyntax | #expressionSyntax 732 | 733 | // The environment that the job references. 734 | environment?: string | #environment 735 | 736 | // A map of outputs for a job. Job outputs are available to all 737 | // downstream jobs that depend on this job. 738 | outputs?: { 739 | [string]: string 740 | } 741 | 742 | // A map of environment variables that are available to all steps 743 | // in the job. 744 | env?: #env 745 | 746 | // A map of default settings that will apply to all steps in the 747 | // job. 748 | defaults?: #defaults 749 | 750 | // You can use the if conditional to prevent a job from running 751 | // unless a condition is met. You can use any supported context 752 | // and expression to create a conditional. 753 | // Expressions in an if conditional do not require the ${{ }} 754 | // syntax. For more information, see 755 | // https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. 756 | if?: bool | number | string 757 | 758 | // A job contains a sequence of tasks called steps. Steps can run 759 | // commands, run setup tasks, or run an action in your 760 | // repository, a public repository, or an action published in a 761 | // Docker registry. Not all steps run actions, but all actions 762 | // run as a step. Each step runs in its own process in the 763 | // virtual environment and has access to the workspace and 764 | // filesystem. Because steps run in their own process, changes to 765 | // environment variables are not preserved between steps. GitHub 766 | // provides built-in steps to set up and complete a job. 767 | // Must contain either `uses` or `run` 768 | steps?: [...({ 769 | uses!: string 770 | ... 771 | } | { 772 | run!: string 773 | ... 774 | }) & { 775 | // A unique identifier for the step. You can use the id to 776 | // reference the step in contexts. For more information, see 777 | // https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. 778 | id?: string 779 | 780 | // You can use the if conditional to prevent a step from running 781 | // unless a condition is met. You can use any supported context 782 | // and expression to create a conditional. 783 | // Expressions in an if conditional do not require the ${{ }} 784 | // syntax. For more information, see 785 | // https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions. 786 | if?: bool | number | string 787 | 788 | // A name for your step to display on GitHub. 789 | name?: string 790 | 791 | // Selects an action to run as part of a step in your job. An 792 | // action is a reusable unit of code. You can use an action 793 | // defined in the same repository as the workflow, a public 794 | // repository, or in a published Docker container image 795 | // (https://hub.docker.com/). 796 | // We strongly recommend that you include the version of the 797 | // action you are using by specifying a Git ref, SHA, or Docker 798 | // tag number. If you don't specify a version, it could break 799 | // your workflows or cause unexpected behavior when the action 800 | // owner publishes an update. 801 | // - Using the commit SHA of a released action version is the 802 | // safest for stability and security. 803 | // - Using the specific major action version allows you to receive 804 | // critical fixes and security patches while still maintaining 805 | // compatibility. It also assures that your workflow should still 806 | // work. 807 | // - Using the master branch of an action may be convenient, but 808 | // if someone releases a new major version with a breaking 809 | // change, your workflow could break. 810 | // Some actions require inputs that you must set using the with 811 | // keyword. Review the action's README file to determine the 812 | // inputs required. 813 | // Actions are either JavaScript files or Docker containers. If 814 | // the action you're using is a Docker container you must run the 815 | // job in a Linux virtual environment. For more details, see 816 | // https://help.github.com/en/articles/virtual-environments-for-github-actions. 817 | uses?: string 818 | 819 | // Runs command-line programs using the operating system's shell. 820 | // If you do not provide a name, the step name will default to 821 | // the text specified in the run command. 822 | // Commands run using non-login shells by default. You can choose 823 | // a different shell and customize the shell used to run 824 | // commands. For more information, see 825 | // https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#using-a-specific-shell. 826 | // Each run keyword represents a new process and shell in the 827 | // virtual environment. When you provide multi-line commands, 828 | // each line runs in the same shell. 829 | run?: string 830 | "working-directory"?: #["working-directory"] 831 | shell?: #shell 832 | 833 | // A map of the input parameters defined by the action. Each input 834 | // parameter is a key/value pair. Input parameters are set as 835 | // environment variables. The variable is prefixed with INPUT_ 836 | // and converted to upper case. 837 | with?: #env & (null | bool | number | string | [...] | { 838 | args?: string 839 | entrypoint?: string 840 | ... 841 | }) 842 | 843 | // Sets environment variables for steps to use in the virtual 844 | // environment. You can also set environment variables for the 845 | // entire workflow or a job. 846 | env?: #env 847 | 848 | // Prevents a job from failing when a step fails. Set to true to 849 | // allow a job to pass when this step fails. 850 | "continue-on-error"?: bool | #expressionSyntax | *false 851 | 852 | // The maximum number of minutes to run the step before killing 853 | // the process. 854 | "timeout-minutes"?: number | #expressionSyntax 855 | }] & [_, ...] 856 | 857 | // The maximum number of minutes to let a workflow run before 858 | // GitHub automatically cancels it. Default: 360 859 | "timeout-minutes"?: number | #expressionSyntax | *360 860 | 861 | // A strategy creates a build matrix for your jobs. You can define 862 | // different variations of an environment to run each job in. 863 | strategy?: { 864 | matrix!: #matrix 865 | 866 | // When set to true, GitHub cancels all in-progress jobs if any 867 | // matrix job fails. Default: true 868 | "fail-fast"?: bool | string | *true 869 | 870 | // The maximum number of jobs that can run simultaneously when 871 | // using a matrix job strategy. By default, GitHub will maximize 872 | // the number of jobs run in parallel depending on the available 873 | // runners on GitHub-hosted virtual machines. 874 | "max-parallel"?: number | string 875 | } 876 | 877 | // Prevents a workflow run from failing when a job fails. Set to 878 | // true to allow a workflow run to pass when this job fails. 879 | "continue-on-error"?: bool | #expressionSyntax 880 | 881 | // A container to run any steps in a job that don't already 882 | // specify a container. If you have steps that use both script 883 | // and container actions, the container actions will run as 884 | // sibling containers on the same network with the same volume 885 | // mounts. 886 | // If you do not set a container, all steps will run directly on 887 | // the host specified by runs-on unless a step refers to an 888 | // action configured to run in a container. 889 | container?: string | #container 890 | 891 | // Additional containers to host services for a job in a workflow. 892 | // These are useful for creating databases or cache services like 893 | // redis. The runner on the virtual machine will automatically 894 | // create a network and manage the life cycle of the service 895 | // containers. 896 | // When you use a service container for a job or your step uses 897 | // container actions, you don't need to set port information to 898 | // access the service. Docker automatically exposes all ports 899 | // between containers on the same network. 900 | // When both the job and the action run in a container, you can 901 | // directly reference the container by its hostname. The hostname 902 | // is automatically mapped to the service name. 903 | // When a step does not use a container action, you must access 904 | // the service using localhost and bind the ports. 905 | services?: { 906 | [string]: #container 907 | } 908 | 909 | // Concurrency ensures that only a single job or workflow using 910 | // the same concurrency group will run at a time. A concurrency 911 | // group can be any string or expression. The expression can use 912 | // any context except for the secrets context. 913 | // You can also specify concurrency at the workflow level. 914 | // When a concurrent job or workflow is queued, if another job or 915 | // workflow using the same concurrency group in the repository is 916 | // in progress, the queued job or workflow will be pending. Any 917 | // previously pending job or workflow in the concurrency group 918 | // will be canceled. To also cancel any currently running job or 919 | // workflow in the same concurrency group, specify 920 | // cancel-in-progress: true. 921 | concurrency?: string | #concurrency 922 | } 923 | } 924 | -------------------------------------------------------------------------------- /cue.mod/usr/github.com/SchemaStore/schemastore/src/schemas/json/workflow.cue: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | #job: ((#Workflow & {jobs: _}).jobs & {x: _}).x 4 | #step: ((#job & {steps: _}).steps & [_])[0] 5 | 6 | // CUE does not properly encode a JSON Schema oneOf; see 7 | // https://cuelang.org/issue/3165. For now, apply a temporary workaround which 8 | // forces the other option to bottom. 9 | #Workflow: jobs?: [string]: steps?: [...( 10 | { 11 | uses?: _|_ 12 | } | { 13 | run?: _|_ 14 | }), 15 | ] 16 | -------------------------------------------------------------------------------- /cue/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | cue: Python bindings to CUE 17 | 18 | CUE is an open source data constraint language which aims to simplify 19 | tasks involving defining and using data. 20 | 21 | This package provide programmatic access to CUE from Python. The 22 | Python API roughly follows the Go API documented at 23 | https://pkg.go.dev/cuelang.org/go/cue, but uses Python idioms when 24 | appropiate. 25 | 26 | A Value represents a CUE value. Values are created from a Context. 27 | Multiple values involved in some operation must use the same Context. 28 | 29 | For more information about the CUE language see https://cuelang.org. 30 | """ 31 | 32 | from .build import ( 33 | BuildOption, 34 | FileName, 35 | ImportPath, 36 | InferBuiltins, 37 | Scope, 38 | ) 39 | from .context import Context 40 | from .error import Error 41 | from .eval import ( 42 | All, 43 | Attributes, 44 | Concrete, 45 | Definitions, 46 | DisallowCycles, 47 | Docs, 48 | ErrorsAsValues, 49 | EvalOption, 50 | Final, 51 | Hidden, 52 | InlineImports, 53 | Optionals, 54 | Raw, 55 | Schema, 56 | ) 57 | from .kind import Kind 58 | from .result import ( 59 | Err, 60 | Ok, 61 | Result, 62 | ) 63 | from .value import Value 64 | 65 | __all__ = [ 66 | 'All', 67 | 'Attributes', 68 | 'BuildOption', 69 | 'Concrete', 70 | 'Context', 71 | 'Definitions', 72 | 'DisallowCycles', 73 | 'Docs', 74 | 'Err', 75 | 'Error', 76 | 'ErrorsAsValues', 77 | 'EvalOption', 78 | 'FileName', 79 | 'Final', 80 | 'Hidden', 81 | 'ImportPath', 82 | 'InferBuiltins', 83 | 'InlineImports', 84 | 'Kind', 85 | 'Ok', 86 | 'Optionals', 87 | 'Raw', 88 | 'Result', 89 | 'Schema', 90 | 'Scope', 91 | 'Value', 92 | ] 93 | -------------------------------------------------------------------------------- /cue/build.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Encode build options. 17 | 18 | Corresponding Go functionality is documented at: 19 | https://pkg.go.dev/cuelang.org/go/cue#BuildOption 20 | """ 21 | 22 | from typing import Optional, assert_never 23 | from dataclasses import dataclass 24 | from cffi import FFI 25 | from cue.value import Value 26 | import libcue 27 | 28 | @dataclass 29 | class FileName: 30 | """ 31 | Assign a file name to parsed content. 32 | 33 | Pass an instance of this class to functions which take a 34 | BuildOption, such as as compile. 35 | 36 | Corresponding Go functionality is documented at: 37 | https://pkg.go.dev/cuelang.org/go/cue#Filename. 38 | 39 | Args: 40 | name: file name to assign. 41 | """ 42 | name: str 43 | 44 | @dataclass 45 | class ImportPath: 46 | """ 47 | Define the import path used when building CUE. 48 | 49 | Pass an instance of this class to functions which take a 50 | BuildOption, such as as compile. 51 | 52 | Corresponding Go functionality is documented at: 53 | https://pkg.go.dev/cuelang.org/go/cue#ImportPath. 54 | 55 | Args: 56 | path: CUE import path. 57 | """ 58 | path: str 59 | 60 | @dataclass 61 | class InferBuiltins: 62 | """ 63 | Interpret unresolved identifiers as builtins. 64 | 65 | Pass an instance of this class to functions which take a 66 | BuildOption, such as as compile. 67 | 68 | Corresponding Go functionality is documented at: 69 | https://pkg.go.dev/cuelang.org/go/cue#InferBuiltins 70 | 71 | Args: 72 | infer: True IFF inferring builtins. 73 | """ 74 | infer: bool 75 | 76 | @dataclass 77 | class Scope: 78 | """ 79 | Resolve identifiers in specific scope. 80 | 81 | Pass an instance of this class to functions which take a 82 | BuildOption, such as as compile. 83 | 84 | Corresponding Go functionality is documented at: 85 | https://pkg.go.dev/cuelang.org/go/cue#Scope. 86 | 87 | Args: 88 | scope: Value to resolve identifiers relative to. 89 | """ 90 | scope: Value 91 | 92 | BuildOption = FileName | ImportPath | InferBuiltins | Scope 93 | 94 | def encode_build_opts(*opts: BuildOption) -> Optional[FFI.CData]: 95 | if len(opts) == 0: 96 | return None 97 | 98 | a = _alloc_bopt_array(len(opts)) 99 | for i, opt in enumerate(opts): 100 | match opt: 101 | case FileName(name): 102 | c_str = libcue.ffi.new("char[]", name.encode("utf-8")) 103 | a[i].tag = libcue.BUILD_FILENAME 104 | a[i].str = c_str 105 | case ImportPath(path): 106 | c_str = libcue.ffi.new("char[]", path.encode("utf-8")) 107 | a[i].tag = libcue.BUILD_IMPORT_PATH 108 | a[i].str = c_str 109 | case InferBuiltins(b): 110 | a[i].tag = libcue.BUILD_INFER_BUILTINS 111 | a[i].b = b 112 | case Scope(scope): 113 | a[i].tag = libcue.BUILD_SCOPE 114 | a[i].value = scope._val 115 | case _: 116 | # use `assert_never` on `_` to enable 117 | # exhaustiveness matching. 118 | assert_never(opt) 119 | return a 120 | 121 | 122 | def _alloc_bopt_array(num: int) -> FFI.CData: 123 | opts = libcue.ffi.new("cue_bopt[]", num + 1) 124 | opts[num].tag = libcue.BUILD_NONE 125 | return opts 126 | -------------------------------------------------------------------------------- /cue/compile.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Compile CUE code. 17 | """ 18 | 19 | from cue.build import BuildOption, encode_build_opts 20 | from cue.value import Value 21 | from cue.error import Error 22 | import libcue 23 | 24 | from typing import TYPE_CHECKING 25 | if TYPE_CHECKING: 26 | from cue.context import Context 27 | 28 | def compile(ctx: 'Context', s: str, *opts: BuildOption) -> Value: 29 | val_ptr = libcue.ffi.new("cue_value*") 30 | buf = libcue.ffi.new("char[]", s.encode("utf-8")) 31 | 32 | build_opts = encode_build_opts(*opts) 33 | err = libcue.compile_string(ctx._res(), buf, build_opts, val_ptr) 34 | if err != 0: 35 | raise Error(err) 36 | return Value(ctx, val_ptr[0]) 37 | 38 | def compile_bytes(ctx: 'Context', buf: bytes, *opts: BuildOption) -> Value: 39 | val_ptr = libcue.ffi.new("cue_value*") 40 | buf_ptr = libcue.ffi.from_buffer(buf) 41 | 42 | build_opts = encode_build_opts(*opts) 43 | err = libcue.compile_bytes(ctx._res(), buf_ptr, len(buf), build_opts, val_ptr) 44 | if err != 0: 45 | raise Error(err) 46 | return Value(ctx, val_ptr[0]) 47 | -------------------------------------------------------------------------------- /cue/context.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Create CUE values. 17 | """ 18 | 19 | from functools import singledispatchmethod 20 | from typing import final 21 | from cue.value import Value 22 | from cue.build import BuildOption 23 | from cue.compile import compile, compile_bytes 24 | from cue.res import _Resource 25 | import libcue 26 | 27 | @final 28 | class Context: 29 | """ 30 | Create new CUE values. 31 | 32 | Any operation that involves two values should originate from 33 | the same Context. 34 | 35 | Corresponding Go functionality is documented at: 36 | https://pkg.go.dev/cuelang.org/go/cue#Context 37 | """ 38 | 39 | _ctx: _Resource 40 | 41 | def __init__(self): 42 | self._ctx = _Resource(libcue.newctx()) 43 | 44 | def _res(self) -> int: 45 | return self._ctx.res() 46 | 47 | @singledispatchmethod 48 | def compile(self, s, *opts: BuildOption) -> Value: 49 | """ 50 | Compile CUE code returning corresponding Value. 51 | 52 | Corresponding Go functionality is documented at: 53 | https://pkg.go.dev/cuelang.org/go/cue#Context.CompileString and 54 | https://pkg.go.dev/cuelang.org/go/cue#Context.CompileBytes 55 | 56 | Args: 57 | s: CUE code to compile, can be or type str or bytes. 58 | *opts: build options to use. 59 | 60 | Returns: 61 | Value: the CUE value corresponding to s. 62 | 63 | Raises: 64 | Error: if any error occurred. 65 | """ 66 | raise NotImplementedError 67 | 68 | @compile.register 69 | def _(self, s: str, *opts: BuildOption) -> Value: 70 | return compile(self, s, *opts) 71 | 72 | @compile.register 73 | def _(self, b: bytes, *opts: BuildOption) -> Value: 74 | return compile_bytes(self, b, *opts) 75 | 76 | def top(self) -> Value: 77 | """ 78 | Return an instance of CUE `_`. 79 | """ 80 | return Value(self, libcue.top(self._res())) 81 | 82 | def bottom(self) -> Value: 83 | """ 84 | Return an instance of CUE `_|_`. 85 | """ 86 | return Value(self, libcue.bottom(self._res())) 87 | 88 | @singledispatchmethod 89 | def to_value(self, arg) -> Value: 90 | """ 91 | Convert Python value to CUE value. 92 | 93 | Args: 94 | arg: a Python bool, int, float, str, or bytes. 95 | 96 | Returns: 97 | Value: the CUE value denoting arg. 98 | """ 99 | raise NotImplementedError 100 | 101 | @to_value.register 102 | def _(self, arg: int): 103 | return Value(self, libcue.from_int64(self._res(), arg)) 104 | 105 | @to_value.register 106 | def _(self, arg: bool): 107 | return Value(self, libcue.from_bool(self._res(), arg)) 108 | 109 | @to_value.register 110 | def _(self, arg: float): 111 | return Value(self, libcue.from_double(self._res(), arg)) 112 | 113 | @to_value.register 114 | def _(self, arg: str): 115 | c_str = libcue.ffi.new("char[]", arg.encode("utf-8")) 116 | return Value(self, libcue.from_string(self._res(), c_str)) 117 | 118 | @to_value.register 119 | def _(self, arg: bytes): 120 | c_buf = libcue.ffi.from_buffer(arg) 121 | return Value(self, libcue.from_bytes(self._res(), c_buf, len(arg))) 122 | 123 | def to_value_from_unsigned(self, arg: int) -> Value: 124 | """ 125 | Convert Python int to unsigned CUE value. 126 | 127 | Interpret arg as an unsigned integer and convert it to CUE. 128 | 129 | Args: 130 | arg: Python value holding an unsigned integer. 131 | 132 | Return: 133 | Value: the CUE value denoting arg. 134 | """ 135 | return Value(self, libcue.from_uint64(self._res(), arg)) 136 | -------------------------------------------------------------------------------- /cue/error.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Handle CUE errors. 17 | """ 18 | 19 | from typing import final 20 | from cue.res import _Resource 21 | import libcue 22 | 23 | @final 24 | class Error(Exception): 25 | """ 26 | CUE evaluation error. 27 | """ 28 | 29 | _err: _Resource 30 | 31 | def _res(self): 32 | return self._err.res() 33 | 34 | def __init__(self, err: int): 35 | self._err = _Resource(err) 36 | 37 | def __str__(self): 38 | c_str = libcue.error_string(self._res()) 39 | s = libcue.ffi.string(c_str).decode("utf-8") 40 | libcue.libc_free(c_str) 41 | return s 42 | -------------------------------------------------------------------------------- /cue/eval.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Encode evaluation options. 17 | 18 | Corresponding Go functionality is documented at: 19 | https://pkg.go.dev/cuelang.org/go/cue#Option 20 | """ 21 | 22 | from typing import Optional, assert_never 23 | from dataclasses import dataclass 24 | from cffi import FFI 25 | import libcue 26 | 27 | @dataclass 28 | class All: 29 | """ 30 | Indicate that all fields and values should be included in 31 | processing even if they can be elided or omitted. 32 | 33 | Corresponding Go functionality is documented at: 34 | https://pkg.go.dev/cuelang.org/go/cue#All 35 | """ 36 | pass 37 | 38 | @dataclass 39 | class Attributes: 40 | """ 41 | Indicate whether attributes should be included. 42 | 43 | Corresponding Go functionality is documented at: 44 | https://pkg.go.dev/cuelang.org/go/cue#Attributes 45 | 46 | Args: 47 | attrs: True IFF attributes should be included. 48 | """ 49 | attrs: bool 50 | 51 | @dataclass 52 | class Concrete: 53 | """ 54 | Indicate whether non-concrete values are interpreted as errors. 55 | 56 | Corresponding Go functionality is documented at: 57 | https://pkg.go.dev/cuelang.org/go/cue#Concrete 58 | 59 | Args: 60 | concrete: True IFF encountered non-concrete values are an error. 61 | """ 62 | concrete: bool 63 | 64 | @dataclass 65 | class Definitions: 66 | """ 67 | Indicate whether definitions should be included. 68 | 69 | Corresponding Go functionality is documented at: 70 | https://pkg.go.dev/cuelang.org/go/cue#Definitions 71 | 72 | Args: 73 | defs: True IFF definitions should be included. 74 | """ 75 | defs: bool 76 | 77 | @dataclass 78 | class DisallowCycles: 79 | """ 80 | Force validation in the presence of cycles. 81 | 82 | Force validation in the presence of cycles, even if non-concrete values are allowed. This is implied by Concrete. 83 | 84 | Corresponding Go functionality is documented at: 85 | https://pkg.go.dev/cuelang.org/go/cue#DisallowCycles 86 | 87 | Args: 88 | disallow_cycles: True IFF cycles are not allowed. 89 | """ 90 | disallow_cycles: bool 91 | 92 | @dataclass 93 | class Docs: 94 | """ 95 | Indicate whether docs should be included. 96 | 97 | Corresponding Go functionality is documented at: 98 | https://pkg.go.dev/cuelang.org/go/cue#Docs 99 | 100 | Args: 101 | docs: True IFF docs should be included. 102 | """ 103 | docs: bool 104 | 105 | @dataclass 106 | class ErrorsAsValues: 107 | """ 108 | Treat errors as regular values. 109 | 110 | Treat errors as a regular value, including them at the location 111 | in the tree where they occur, instead of interpreting them as 112 | a configuration-wide failure that is returned instead of root 113 | value. 114 | 115 | Corresponding Go functionality is documented at: 116 | https://pkg.go.dev/cuelang.org/go/cue#ErrorsAsValues 117 | 118 | Args: 119 | err_as_val: True IFF treating errors as value 120 | """ 121 | err_as_val: bool 122 | 123 | @dataclass 124 | class Final: 125 | """ 126 | Indicate a value is final. 127 | 128 | Implicitly close all structs and lists in a value and select defaults. 129 | 130 | Corresponding Go functionality is documented at: 131 | https://pkg.go.dev/cuelang.org/go/cue#Final 132 | """ 133 | pass 134 | 135 | @dataclass 136 | class Hidden: 137 | """ 138 | Indicate whether definitions and hidden fields should be included. 139 | 140 | Corresponding Go functionality is documented at: 141 | https://pkg.go.dev/cuelang.org/go/cue#Hidden 142 | 143 | Args: 144 | hidden: True IFF definitions and hidden fields are included. 145 | """ 146 | hidden: bool 147 | 148 | @dataclass 149 | class InlineImports: 150 | """ 151 | Inline imported references. 152 | 153 | Inline references to values within imported packages. References 154 | to builtin packages are not inlined. 155 | 156 | Corresponding Go functionality is documented at: 157 | https://pkg.go.dev/cuelang.org/go/cue#InlineImports 158 | 159 | Args: 160 | inline_imports: True IFF imported references should be inlined 161 | """ 162 | inline_imports: bool 163 | 164 | @dataclass 165 | class Optionals: 166 | """ 167 | Indicate whether optional fields should be included. 168 | 169 | Corresponding Go functionality is documented at: 170 | https://pkg.go.dev/cuelang.org/go/cue#Optional 171 | 172 | Args: 173 | optionals: True IFF optional fields are included. 174 | """ 175 | optionals: bool 176 | 177 | @dataclass 178 | class Raw: 179 | """ 180 | Generate value without simplification. 181 | 182 | Corresponding Go functionality is documented at: 183 | https://pkg.go.dev/cuelang.org/go/cue#Raw 184 | """ 185 | pass 186 | 187 | @dataclass 188 | class Schema: 189 | """ 190 | Specify input to be a schema. 191 | 192 | Corresponding Go functionality is documented at: 193 | https://pkg.go.dev/cuelang.org/go/cue#Schema 194 | """ 195 | pass 196 | 197 | EvalOption = All | Attributes | Concrete | Definitions | DisallowCycles | Docs | ErrorsAsValues | Final | Hidden | InlineImports | Optionals | Raw | Schema 198 | 199 | 200 | def _alloc_eopt_array(num: int) -> FFI.CData: 201 | opts = libcue.ffi.new("cue_eopt[]", num + 1) 202 | opts[num].tag = libcue.OPT_NONE 203 | return opts 204 | 205 | def encode_eval_opts(*opts: EvalOption) -> Optional[FFI.CData]: 206 | if len(opts) == 0: 207 | return None 208 | 209 | a = _alloc_eopt_array(len(opts)) 210 | for i, opt in enumerate(opts): 211 | match opt: 212 | case All(): 213 | a[i].tag = libcue.OPT_ALL 214 | case Attributes(attrs): 215 | a[i].tag = libcue.OPT_ATTR 216 | a[i].value = attrs 217 | case Concrete(concrete): 218 | a[i].tag = libcue.OPT_CONCRETE 219 | a[i].value = concrete 220 | case Definitions(defs): 221 | a[i].tag = libcue.OPT_DEFS 222 | a[i].value = defs 223 | case DisallowCycles(disallow_cycles): 224 | a[i].tag = libcue.OPT_DISALLOW_CYCLES 225 | a[i].value = disallow_cycles 226 | case Docs(docs): 227 | a[i].tag = libcue.OPT_DOCS 228 | a[i].value = docs 229 | case ErrorsAsValues(err_as_val): 230 | a[i].tag = libcue.OPT_ERRORS_AS_VALUES 231 | a[i].value = err_as_val 232 | case Final(): 233 | a[i].tag = libcue.OPT_FINAL 234 | case Hidden(hidden): 235 | a[i].tag = libcue.OPT_HIDDEN 236 | a[i].value = hidden 237 | case InlineImports(inline_imports): 238 | a[i].tag = libcue.OPT_INLINE_IMPORTS 239 | a[i].value = inline_imports 240 | case Optionals(optionals): 241 | a[i].tag = libcue.OPT_OPTIONALS 242 | a[i].value = optionals 243 | case Raw(): 244 | a[i].tag = libcue.OPT_RAW 245 | case Schema(): 246 | a[i].tag = libcue.OPT_SCHEMA 247 | case _: 248 | # use `assert_never` on `_` to enable 249 | # exhaustiveness matching. 250 | assert_never(opt) 251 | 252 | return a 253 | -------------------------------------------------------------------------------- /cue/kind.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Classify CUE values. 17 | """ 18 | 19 | from enum import Enum 20 | from typing import Dict 21 | import libcue 22 | 23 | class Kind(Enum): 24 | """ 25 | The type of a Value. 26 | 27 | Corresponding Go functionality is documented at: 28 | https://pkg.go.dev/cuelang.org/go/cue#Kind. 29 | 30 | Attributes: 31 | BOTTOM: The bottom value. 32 | NULL: The null value. 33 | BOOL: A boolean. 34 | INT: An integral number. 35 | FLOAT: A decimal floating point number. 36 | STRING: A string. 37 | BYTES: A blob of data. 38 | STRUCT: A key-value map. 39 | LIST: A list of values. 40 | NUMBER: Any kind of number. 41 | TOP: The top value. 42 | """ 43 | BOTTOM = libcue.KIND_BOTTOM 44 | NULL = libcue.KIND_NULL 45 | BOOL = libcue.KIND_BOOL 46 | INT = libcue.KIND_INT 47 | FLOAT = libcue.KIND_FLOAT 48 | STRING = libcue.KIND_STRING 49 | BYTES = libcue.KIND_BYTES 50 | STRUCT = libcue.KIND_STRUCT 51 | LIST = libcue.KIND_LIST 52 | NUMBER = libcue.KIND_NUMBER 53 | TOP = libcue.KIND_TOP 54 | 55 | to_kind: Dict[int, Kind] = { 56 | libcue.KIND_BOTTOM: Kind.BOTTOM, 57 | libcue.KIND_NULL: Kind.NULL, 58 | libcue.KIND_BOOL: Kind.BOOL, 59 | libcue.KIND_INT: Kind.INT, 60 | libcue.KIND_FLOAT: Kind.FLOAT, 61 | libcue.KIND_STRING: Kind.STRING, 62 | libcue.KIND_BYTES: Kind.BYTES, 63 | libcue.KIND_STRUCT: Kind.STRUCT, 64 | libcue.KIND_LIST: Kind.LIST, 65 | libcue.KIND_NUMBER: Kind.NUMBER, 66 | libcue.KIND_TOP: Kind.TOP, 67 | } 68 | -------------------------------------------------------------------------------- /cue/res.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Manage lifecycle of CUE resources coming from the CUE Go API. 17 | """ 18 | 19 | from typing import Any, Callable, final 20 | import libcue 21 | 22 | @final 23 | class _Resource: 24 | """ 25 | A CUE resource. 26 | 27 | Resource holds any external value that is managed by the CUE Go API. 28 | """ 29 | 30 | _val: int 31 | 32 | """ 33 | Returns the underlying Go resoure handle. 34 | """ 35 | def res(self) -> int: 36 | if self._val != 0: 37 | return self._val 38 | else: 39 | raise RuntimeError("Fatal: use of invalid resource.") 40 | 41 | def __init__(self, v: int): 42 | self._val = v 43 | 44 | def close(self): 45 | if self._val != 0: 46 | self.__release() 47 | 48 | def __release(self): 49 | libcue.free(self._val) 50 | self._val = 0 51 | 52 | def __del__(self): 53 | self.close() 54 | -------------------------------------------------------------------------------- /cue/result.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Return results or errors. 17 | """ 18 | 19 | from typing import Generic, TypeVar, Union 20 | from dataclasses import dataclass 21 | 22 | T = TypeVar('T') 23 | E = TypeVar('E') 24 | 25 | @dataclass 26 | class Ok(Generic[T]): 27 | """ 28 | Encode success. 29 | 30 | Args: 31 | value: result to encode 32 | """ 33 | value: T 34 | 35 | @dataclass 36 | class Err(Generic[E]): 37 | """ 38 | Encode failure. 39 | 40 | Args: 41 | err: error to encode 42 | """ 43 | err: E 44 | 45 | Result = Union[Ok[T], Err[E]] 46 | -------------------------------------------------------------------------------- /cue/value.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Perform operations on CUE values. 17 | """ 18 | 19 | from typing import Any, Optional, final 20 | from cue.error import Error 21 | from cue.eval import EvalOption, encode_eval_opts 22 | from cue.kind import Kind, to_kind 23 | from cue.res import _Resource 24 | from cue.result import Result, Ok, Err 25 | import libcue 26 | 27 | from typing import TYPE_CHECKING 28 | if TYPE_CHECKING: 29 | from cue.context import Context 30 | 31 | @final 32 | class Value: 33 | """ 34 | A CUE value. 35 | 36 | Value holds any value that can be encoded by CUE. 37 | 38 | Corresponding Go functionality is documented at: 39 | https://pkg.go.dev/cuelang.org/go/cue#Value. 40 | """ 41 | 42 | _ctx: 'Context' 43 | _val: _Resource 44 | 45 | def __init__(self, ctx: 'Context', v: int): 46 | self._ctx = ctx 47 | self._val = _Resource(v) 48 | 49 | def _res(self) -> int: 50 | return self._val.res() 51 | 52 | def __eq__(self, other: Any) -> bool: 53 | """ 54 | Check whether two CUE values are equal. 55 | 56 | Reports whether two values are equal, ignoring optional 57 | fields. The result is undefined for incomplete values. 58 | 59 | Corresponding Go functionality is documented at: 60 | https://pkg.go.dev/cuelang.org/go/cue#Value.Equals 61 | 62 | Args: 63 | other: the other value to compare with. 64 | 65 | Returns: 66 | bool: True if the two values are complete and equal CUE values, False otherwise. 67 | """ 68 | if isinstance(other, Value): 69 | return libcue.is_equal(self._res(), other._res()) 70 | return False 71 | 72 | def context(self) -> 'Context': 73 | """The Context that created this Value.""" 74 | return self._ctx 75 | 76 | def unify(self, other: 'Value') -> 'Value': 77 | """ 78 | Compute the greatest lower bound of two CUE values. 79 | 80 | Corresponding Go functionality is documented at: 81 | https://pkg.go.dev/cuelang.org/go/cue#Value.Unify 82 | """ 83 | v = libcue.unify(self._res(), other._res()) 84 | return Value(self._ctx, v) 85 | 86 | def lookup(self, path: str) -> 'Value': 87 | """ 88 | Return the CUE value at path. 89 | 90 | Args: 91 | path: CUE path relative to self. 92 | 93 | Returns: 94 | Value: the value reached at path, starting from self. 95 | """ 96 | return _lookup(self, path) 97 | 98 | def to_int(self) -> int: 99 | """ 100 | Convert CUE value to integer. 101 | 102 | Convert the underlying value, which must be a CUE int64, 103 | to a Python int. 104 | 105 | Corresponding Go functionality is documented at: 106 | https://pkg.go.dev/cuelang.org/go/cue#Value.Int64 107 | 108 | Returns: 109 | int: a Python int denoting the same CUE value. 110 | 111 | Raises: 112 | Error: if the CUE value is not an int64. 113 | """ 114 | return _to_int(self) 115 | 116 | def to_unsigned(self) -> int: 117 | """ 118 | Convert unsigned CUE value to integer. 119 | 120 | Convert the underlying value, which must be a CUE uint64, 121 | to a Python int. 122 | 123 | Corresponding Go functionality is documented at: 124 | https://pkg.go.dev/cuelang.org/go/cue#Value.Uint64 125 | 126 | Returns: 127 | int: a Python int denoting the same CUE value. 128 | 129 | Raises: 130 | Error: if the CUE value is not an uint64. 131 | """ 132 | return _to_unsigned(self) 133 | 134 | def to_bool(self) -> bool: 135 | """ 136 | Convert bool CUE value to bool. 137 | 138 | Convert the underlying value, which must be a CUE bool, 139 | to a Python bool. 140 | 141 | Corresponding Go functionality is documented at: 142 | https://pkg.go.dev/cuelang.org/go/cue#Value.Bool 143 | 144 | Returns: 145 | bool: a Python bool denoting the same CUE value. 146 | 147 | Raises: 148 | Error: if the CUE value is not a bool. 149 | """ 150 | return _to_bool(self) 151 | 152 | def to_float(self) -> float: 153 | """ 154 | Convert float64 CUE value to float. 155 | 156 | Convert the underlying value, which must be a CUE float64, 157 | to a Python float. 158 | 159 | Corresponding Go functionality is documented at: 160 | https://pkg.go.dev/cuelang.org/go/cue#Value.Float64 161 | 162 | Returns: 163 | float: a Python float denoting the same CUE value. 164 | 165 | Raises: 166 | Error: if the CUE value is not a float64. 167 | """ 168 | return _to_float(self) 169 | 170 | def to_str(self) -> str: 171 | """ 172 | Convert string CUE value to str. 173 | 174 | Convert the underlying value, which must be a CUE string, 175 | to a Python string. 176 | 177 | Corresponding Go functionality is documented at: 178 | https://pkg.go.dev/cuelang.org/go/cue#Value.String 179 | 180 | Returns: 181 | str: a Python string denoting the same CUE value. 182 | 183 | Raises: 184 | Error: if the CUE value is not a string. 185 | """ 186 | return _to_str(self) 187 | 188 | def to_bytes(self) -> bytes: 189 | """ 190 | Convert bytes CUE value to bytes. 191 | 192 | Convert the underlying value, which must be a CUE bytes, 193 | to a Python bytes. 194 | 195 | Corresponding Go functionality is documented at: 196 | https://pkg.go.dev/cuelang.org/go/cue#Value.Bytes 197 | 198 | Returns: 199 | bytes: a Python bytes denoting the same CUE value. 200 | 201 | Raises: 202 | Error: if the CUE value is not a bytes. 203 | """ 204 | return _to_bytes(self) 205 | 206 | def to_json(self) -> str: 207 | """ 208 | Marshall CUE value to JSON. 209 | 210 | Corresponding Go functionality is documented at: 211 | https://pkg.go.dev/cuelang.org/go/cue#Value.MarshalJSON 212 | 213 | Returns: 214 | str: the JSON encoding of the value 215 | 216 | Raises: 217 | Error: if the CUE value can not be mashalled to JSON. 218 | """ 219 | 220 | return _to_json(self) 221 | 222 | def default(self) -> Optional['Value']: 223 | """ 224 | Return default value. 225 | 226 | Returns: 227 | Optional[Value]: the default value, if it exists, or None otherwise. 228 | """ 229 | return _default(self) 230 | 231 | def kind(self) -> Kind: 232 | """ 233 | Return the type (kind) of a concrete CUE value. 234 | 235 | Corresponding Go functionality is documented at: 236 | https://pkg.go.dev/cuelang.org/go/cue#Value.Kind 237 | """ 238 | return to_kind[libcue.concrete_kind(self._res())] 239 | 240 | def incomplete_kind(self) -> Kind: 241 | """ 242 | Return the type (kind) of any CUE value. 243 | 244 | Corresponding Go functionality is documented at: 245 | https://pkg.go.dev/cuelang.org/go/cue#Value.IncompleteKind 246 | """ 247 | return to_kind[libcue.incomplete_kind(self._res())] 248 | 249 | def error(self) -> Result['Value', str]: 250 | """ 251 | Extract the potential error from a value. 252 | 253 | Returns: 254 | Result['Value', str]: the value itself, if there is no error, or the CUE error as a string if there is one. 255 | """ 256 | err = libcue.value_error(self._res()) 257 | if err != 0: 258 | c_str = libcue.error_string(err) 259 | 260 | dec = libcue.ffi.string(c_str) 261 | if not isinstance(dec, bytes): 262 | raise TypeError 263 | 264 | s = dec.decode("utf-8") 265 | libcue.libc_free(c_str) 266 | return Err(s) 267 | return Ok(self) 268 | 269 | def check_schema(self, schema: 'Value', *opts: EvalOption) -> None: 270 | """ 271 | Ensure a value conforms to a schema. 272 | 273 | Args: 274 | schema: CUE schema to check against. 275 | *opts: evaluation options. 276 | 277 | Raises: 278 | Error: if the value does not conform to the schema. 279 | 280 | Corresponding Go functionality is documented at: 281 | https://pkg.go.dev/cuelang.org/go/cue#Value.Subsume 282 | """ 283 | 284 | eval_opts = encode_eval_opts(*opts) 285 | err = libcue.instance_of(self._res(), schema._res(), eval_opts) 286 | if err != 0: 287 | raise Error(err) 288 | 289 | def validate(self, *opts: EvalOption) -> None: 290 | """ 291 | Ensure the value does not contain errors. 292 | 293 | Corresponding Go functionality is documented at: 294 | https://pkg.go.dev/cuelang.org/go/cue#Value.Validate 295 | 296 | Args: 297 | *opts: evaluation options. 298 | 299 | Raises: 300 | Error: if the value contains errors. 301 | """ 302 | eval_opts = encode_eval_opts(*opts) 303 | err = libcue.validate(self._res(), eval_opts) 304 | if err != 0: 305 | raise Error(err) 306 | 307 | def _to_int(val: Value) -> int: 308 | ptr = libcue.ffi.new("int64_t*") 309 | err = libcue.dec_int64(val._res(), ptr) 310 | if err != 0: 311 | raise Error(err) 312 | return ptr[0] 313 | 314 | def _to_unsigned(val: Value) -> int: 315 | ptr = libcue.ffi.new("uint64_t*") 316 | err = libcue.dec_uint64(val._res(), ptr) 317 | if err != 0: 318 | raise Error(err) 319 | return ptr[0] 320 | 321 | def _to_bool(val: Value) -> bool: 322 | ptr = libcue.ffi.new("bool*") 323 | err = libcue.dec_bool(val._res(), ptr) 324 | if err != 0: 325 | raise Error(err) 326 | return ptr[0] 327 | 328 | def _to_float(val: Value) -> float: 329 | ptr = libcue.ffi.new("double*") 330 | err = libcue.dec_double(val._res(), ptr) 331 | if err != 0: 332 | raise Error(err) 333 | return ptr[0] 334 | 335 | def _to_str(val: Value) -> str: 336 | ptr = libcue.ffi.new("char**") 337 | err = libcue.dec_string(val._res(), ptr) 338 | if err != 0: 339 | raise Error(err) 340 | 341 | dec = libcue.ffi.string(ptr[0]) 342 | if not isinstance(dec, bytes): 343 | raise TypeError 344 | 345 | s = dec.decode("utf-8") 346 | libcue.libc_free(ptr[0]) 347 | return s 348 | 349 | def _to_bytes(val: Value) -> bytes: 350 | buf_ptr = libcue.ffi.new("uint8_t**") 351 | len_ptr = libcue.ffi.new("size_t*") 352 | 353 | err = libcue.dec_bytes(val._res(), buf_ptr, len_ptr) 354 | if err != 0: 355 | raise Error(err) 356 | 357 | dec = libcue.ffi.buffer(buf_ptr[0], len_ptr[0]) 358 | 359 | # we slice the buffer to get a Python bytes instead of converting 360 | # to bytes directly to work around the lack of precise type information 361 | # in types-cffi. 362 | b = dec[:] 363 | 364 | libcue.libc_free(buf_ptr[0]) 365 | return b 366 | 367 | def _to_json(val: Value) -> str: 368 | buf_ptr = libcue.ffi.new("uint8_t**") 369 | len_ptr = libcue.ffi.new("size_t*") 370 | 371 | err = libcue.dec_json(val._res(), buf_ptr, len_ptr) 372 | if err != 0: 373 | raise Error(err) 374 | 375 | dec = libcue.ffi.string(buf_ptr[0], len_ptr[0]) 376 | if not isinstance(dec, bytes): 377 | raise TypeError 378 | 379 | s = dec.decode("utf-8") 380 | libcue.libc_free(buf_ptr[0]) 381 | return s 382 | 383 | def _lookup(val: Value, path: str) -> Value: 384 | val_ptr = libcue.ffi.new("cue_value*") 385 | path_ptr = libcue.ffi.new("char[]", path.encode("utf-8")) 386 | 387 | err = libcue.lookup_string(val._res(), path_ptr, val_ptr) 388 | if err != 0: 389 | raise Error(err) 390 | return Value(val._ctx, val_ptr[0]) 391 | 392 | def _default(val: Value) -> Optional[Value]: 393 | ok_ptr = libcue.ffi.new("bool*") 394 | res = libcue.default(val._res(), ok_ptr) 395 | if ok_ptr[0] == 1: 396 | return Value(val._ctx, res) 397 | return None 398 | -------------------------------------------------------------------------------- /internal/ci/base/base.cue: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The CUE Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // package base is a collection of workflows, jobs, stes etc that are common to 16 | // CUE projects and the workflows they specify. The package itself needs to be 17 | // instantiated to parameterise many of the exported definitions. 18 | // 19 | // For example a package using base would do something like this: 20 | // 21 | // package workflows 22 | // 23 | // import "cuelang.org/go/internal/ci/base" 24 | // 25 | // // Create an instance of base 26 | // _base: base & core { params: { 27 | // // any values you need to set that are outside of core 28 | // ... 29 | // }} 30 | // 31 | package base 32 | 33 | import ( 34 | "strings" 35 | ) 36 | 37 | // Package parameters 38 | githubRepositoryPath: *(URLPath & {#url: githubRepositoryURL, _}) | string 39 | githubRepositoryURL: *("https://github.com/" + githubRepositoryPath) | string 40 | gerritHubHostname: "review.gerrithub.io" 41 | gerritHubRepositoryURL: *("https://\(gerritHubHostname)/a/" + githubRepositoryPath) | string 42 | trybotRepositoryPath: *(githubRepositoryPath + "-" + trybot.key) | string 43 | trybotRepositoryURL: *("https://github.com/" + trybotRepositoryPath) | string 44 | 45 | defaultBranch: *"master" | string 46 | testDefaultBranch: *"ci/test" | _ 47 | protectedBranchPatterns: *[defaultBranch] | [...string] 48 | releaseTagPrefix: *"v" | string 49 | releaseTagPattern: *(releaseTagPrefix + "*") | string 50 | 51 | botGitHubUser: string 52 | botGitHubUserTokenSecretsKey: *(strings.ToUpper(botGitHubUser) + "_GITHUB_PAT") | string 53 | botGitHubUserEmail: string 54 | botGerritHubUser: *botGitHubUser | string 55 | botGerritHubUserPasswordSecretsKey: *(strings.ToUpper(botGitHubUser) + "_GERRITHUB_PASSWORD") | string 56 | botGerritHubUserEmail: *botGitHubUserEmail | string 57 | 58 | workflowFileExtension: ".yaml" 59 | 60 | linuxMachine: string 61 | 62 | codeReview: #codeReview & { 63 | github: githubRepositoryURL 64 | gerrit: gerritHubRepositoryURL 65 | } 66 | 67 | // Define some shared keys and human-readable names. 68 | // 69 | // trybot.key and unity.key are shared with 70 | // github.com/cue-lang/contrib-tools/cmd/cueckoo. The keys are used across various CUE 71 | // workflows and their consistency in those various locations is therefore 72 | // crucial. As such, we assert specific values for the keys here rather than 73 | // just deriving values from the human-readable names. 74 | // 75 | // trybot.name is by the trybot GitHub workflow and by gerritstatusupdater as 76 | // an identifier in the status updates that are posted as reviews for this 77 | // workflows, but also as the result label key, e.g. "TryBot-Result" would be 78 | // the result label key for the "TryBot" workflow. This name also shows up in 79 | // the CI badge in the top-level README. 80 | trybot: { 81 | key: "trybot" & strings.ToLower(name) 82 | name: "TryBot" 83 | } 84 | 85 | unity: { 86 | key: "unity" & strings.ToLower(name) 87 | name: "Unity" 88 | } 89 | -------------------------------------------------------------------------------- /internal/ci/base/codereview.cue: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | // This file contains aspects principally related to git-codereview 4 | // configuration. 5 | 6 | import ( 7 | "strings" 8 | ) 9 | 10 | // #codeReview defines the schema of a codereview.cfg file that 11 | // sits at the root of a repository. codereview.cfg is the configuration 12 | // file that drives golang.org/x/review/git-codereview. This config 13 | // file is also used by github.com/cue-lang/contrib-tools/cmd/cueckoo. 14 | #codeReview: { 15 | gerrit?: string 16 | github?: string 17 | "cue-unity"?: string 18 | } 19 | 20 | // #toCodeReviewCfg converts a #codeReview instance to 21 | // the key: value 22 | toCodeReviewCfg: { 23 | #input: #codeReview 24 | let parts = [for k, v in #input {k + ": " + v}] 25 | 26 | // Per https://pkg.go.dev/golang.org/x/review/git-codereview#hdr-Configuration 27 | strings.Join(parts, "\n") 28 | } 29 | -------------------------------------------------------------------------------- /internal/ci/base/gerrithub.cue: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | // This file contains gerrithub related definitions etc 4 | 5 | import ( 6 | encjson "encoding/json" 7 | "strings" 8 | 9 | "github.com/SchemaStore/schemastore/src/schemas/json" 10 | ) 11 | 12 | // trybotWorkflows is a template for trybot-based repos 13 | trybotWorkflows: { 14 | (trybot.key): json.#Workflow & { 15 | on: workflow_dispatch: {} 16 | } 17 | "\(trybot.key)_dispatch": trybotDispatchWorkflow 18 | "push_tip_to_\(trybot.key)": pushTipToTrybotWorkflow 19 | "evict_caches": evictCaches 20 | } 21 | 22 | #dispatch: { 23 | type: string 24 | CL: int 25 | patchset: int 26 | targetBranch: *defaultBranch | string 27 | 28 | let p = strings.Split("\(CL)", "") 29 | let rightMostTwo = p[len(p)-2] + p[len(p)-1] 30 | ref: *"refs/changes/\(rightMostTwo)/\(CL)/\(patchset)" | string 31 | } 32 | 33 | trybotDispatchWorkflow: bashWorkflow & { 34 | #dummyDispatch?: #dispatch 35 | name: "Dispatch \(trybot.key)" 36 | on: { 37 | repository_dispatch: {} 38 | push: { 39 | // To enable testing of the dispatch itself 40 | branches: [testDefaultBranch] 41 | } 42 | } 43 | jobs: [string]: defaults: run: shell: "bash" 44 | jobs: { 45 | (trybot.key): { 46 | "runs-on": linuxMachine 47 | 48 | let goodDummyData = [if encjson.Marshal(#dummyDispatch) != _|_ {true}, false][0] 49 | 50 | // We set the "on" conditions above, but this would otherwise mean we 51 | // run for all dispatch events. 52 | if: "${{ (\(isTestDefaultBranch) && \(goodDummyData)) || github.event.client_payload.type == '\(trybot.key)' }}" 53 | 54 | // See the comment below about the need for cases 55 | let cases = [ 56 | { 57 | condition: "!=" 58 | expr: "fromJSON(steps.payload.outputs.value)" 59 | nameSuffix: "fake data" 60 | }, 61 | { 62 | condition: "==" 63 | expr: "github.event.client_payload" 64 | nameSuffix: "repository_dispatch payload" 65 | }, 66 | ] 67 | 68 | steps: [ 69 | writeNetrcFile, 70 | 71 | json.#step & { 72 | name: "Write fake payload" 73 | id: "payload" 74 | if: "github.repository == '\(githubRepositoryPath)' && \(isTestDefaultBranch)" 75 | 76 | // Use bash heredocs so that JSON's use of double quotes does 77 | // not get interpreted as shell. Both in the running of the 78 | // command itself, which itself is the echo-ing of a command to 79 | // $GITHUB_OUTPUT. 80 | run: #""" 81 | cat <> $GITHUB_OUTPUT 82 | value< ~/.netrc 350 | machine \(gerritHubHostname) 351 | login \(botGerritHubUser) 352 | password ${{ secrets.\(botGerritHubUserPasswordSecretsKey) }} 353 | EOD 354 | chmod 600 ~/.netrc 355 | """ 356 | } 357 | -------------------------------------------------------------------------------- /internal/ci/base/github.cue: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | // This file contains aspects principally related to GitHub workflows 4 | 5 | import ( 6 | encjson "encoding/json" 7 | "list" 8 | "strings" 9 | "strconv" 10 | 11 | "github.com/SchemaStore/schemastore/src/schemas/json" 12 | ) 13 | 14 | bashWorkflow: json.#Workflow & { 15 | jobs: [string]: defaults: run: shell: "bash" 16 | } 17 | 18 | installGo: { 19 | #setupGo: json.#step & { 20 | name: "Install Go" 21 | uses: "actions/setup-go@v5" 22 | with: { 23 | // We do our own caching in setupGoActionsCaches. 24 | cache: false 25 | "go-version": string 26 | } 27 | } 28 | 29 | // Why set GOTOOLCHAIN here? As opposed to an environment variable 30 | // elsewhere? No perfect answer to this question but here is the thinking: 31 | // 32 | // Setting the variable here localises it with the installation of Go. Doing 33 | // it elsewhere creates distance between the two steps which are 34 | // intrinsically related. And it's also hard to do: "when we use this step, 35 | // also ensure that we establish an environment variable in the job for 36 | // GOTOOLCHAIN". 37 | // 38 | // Environment variables can only be set at a workflow, job or step level. 39 | // Given we currently use a matrix strategy which varies the Go version, 40 | // that rules out using an environment variable based approach, because the 41 | // Go version is only available at runtime via GitHub actions provided 42 | // context. Whether we should instead be templating multiple workflows (i.e. 43 | // exploding the matrix ourselves) is a different question, but one that 44 | // has performance implications. 45 | // 46 | // So as clumsy as it is to use a step "template" that includes more than 47 | // one step, it's the best option available to us for now. 48 | [ 49 | #setupGo, 50 | 51 | { 52 | json.#step & { 53 | name: "Set common go env vars" 54 | run: """ 55 | go env -w GOTOOLCHAIN=local 56 | 57 | # Dump env for good measure 58 | go env 59 | """ 60 | } 61 | }, 62 | ] 63 | } 64 | 65 | checkoutCode: { 66 | #actionsCheckout: json.#step & { 67 | name: "Checkout code" 68 | uses: "actions/checkout@v4" 69 | 70 | // "pull_request" builds will by default use a merge commit, 71 | // testing the PR's HEAD merged on top of the master branch. 72 | // For consistency with Gerrit, avoid that merge commit entirely. 73 | // This doesn't affect builds by other events like "push", 74 | // since github.event.pull_request is unset so ref remains empty. 75 | with: { 76 | ref: "${{ github.event.pull_request.head.sha }}" 77 | "fetch-depth": 0 // see the docs below 78 | } 79 | } 80 | 81 | [ 82 | #actionsCheckout, 83 | 84 | // Restore modified times to work around https://go.dev/issues/58571, 85 | // as otherwise we would get lots of unnecessary Go test cache misses. 86 | // Note that this action requires actions/checkout to use a fetch-depth of 0. 87 | // Since this is a third-party action which runs arbitrary code, 88 | // we pin a commit hash for v2 to be in control of code updates. 89 | // Also note that git-restore-mtime does not update all directories, 90 | // per the bug report at https://github.com/MestreLion/git-tools/issues/47, 91 | // so we first reset all directory timestamps to a static time as a fallback. 92 | // TODO(mvdan): May be unnecessary once the Go bug above is fixed. 93 | json.#step & { 94 | name: "Reset git directory modification times" 95 | run: "touch -t 202211302355 $(find * -type d)" 96 | }, 97 | json.#step & { 98 | name: "Restore git file modification times" 99 | uses: "chetan/git-restore-mtime-action@075f9bc9d159805603419d50f794bd9f33252ebe" 100 | }, 101 | 102 | { 103 | json.#step & { 104 | name: "Try to extract \(dispatchTrailer)" 105 | id: dispatchTrailerStepID 106 | run: """ 107 | x="$(git log -1 --pretty='%(trailers:key=\(dispatchTrailer),valueonly)')" 108 | if [[ "$x" == "" ]] 109 | then 110 | # Some steps rely on the presence or otherwise of the Dispatch-Trailer. 111 | # We know that we don't have a Dispatch-Trailer in this situation, 112 | # hence we use the JSON value null in order to represent that state. 113 | # This means that GitHub expressions can determine whether a Dispatch-Trailer 114 | # is present or not by checking whether the fromJSON() result of the 115 | # output from this step is the JSON value null or not. 116 | x=null 117 | fi 118 | echo "\(_dispatchTrailerDecodeStepOutputVar)<> $GITHUB_OUTPUT 119 | echo "$x" >> $GITHUB_OUTPUT 120 | echo "EOD" >> $GITHUB_OUTPUT 121 | """ 122 | } 123 | }, 124 | 125 | // Safety nets to flag if we ever have a Dispatch-Trailer slip through the 126 | // net and make it to master 127 | json.#step & { 128 | name: "Check we don't have \(dispatchTrailer) on a protected branch" 129 | if: "\(isProtectedBranch) && \(containsDispatchTrailer)" 130 | run: """ 131 | echo "\(_dispatchTrailerVariable) contains \(dispatchTrailer) but we are on a protected branch" 132 | false 133 | """ 134 | }, 135 | ] 136 | } 137 | 138 | earlyChecks: json.#step & { 139 | name: "Early git and code sanity checks" 140 | run: *"go run cuelang.org/go/internal/ci/checks@v0.11.0-0.dev.0.20240903133435-46fb300df650" | string 141 | } 142 | 143 | curlGitHubAPI: { 144 | #tokenSecretsKey: *botGitHubUserTokenSecretsKey | string 145 | 146 | #""" 147 | curl -s -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.\#(#tokenSecretsKey) }}" -H "X-GitHub-Api-Version: 2022-11-28" 148 | """# 149 | } 150 | 151 | setupGoActionsCaches: { 152 | // #readonly determines whether we ever want to write the cache back. The 153 | // writing of a cache back (for any given cache key) should only happen on a 154 | // protected branch. But running a workflow on a protected branch often 155 | // implies that we want to skip the cache to ensure we catch flakes early. 156 | // Hence the concept of clearing the testcache to ensure we catch flakes 157 | // early can be defaulted based on #readonly. In the general case the two 158 | // concepts are orthogonal, hence they are kept as two parameters, even 159 | // though in our case we could get away with a single parameter that 160 | // encapsulates our needs. 161 | #readonly: *false | bool 162 | #cleanTestCache: *!#readonly | bool 163 | #goVersion: string 164 | #additionalCacheDirs: [...string] 165 | #os: string 166 | 167 | let goModCacheDirID = "go-mod-cache-dir" 168 | let goCacheDirID = "go-cache-dir" 169 | 170 | // cacheDirs is a convenience variable that includes 171 | // GitHub expressions that represent the directories 172 | // that participate in Go caching. 173 | let cacheDirs = list.Concat([[ 174 | "${{ steps.\(goModCacheDirID).outputs.dir }}/cache/download", 175 | "${{ steps.\(goCacheDirID).outputs.dir }}", 176 | ], #additionalCacheDirs]) 177 | 178 | let cacheRestoreKeys = "\(#os)-\(#goVersion)" 179 | 180 | let cacheStep = json.#step & { 181 | with: { 182 | path: strings.Join(cacheDirs, "\n") 183 | 184 | // GitHub actions caches are immutable. Therefore, use a key which is 185 | // unique, but allow the restore to fallback to the most recent cache. 186 | // The result is then saved under the new key which will benefit the 187 | // next build. Restore keys are only set if the step is restore. 188 | key: "\(cacheRestoreKeys)-${{ github.run_id }}" 189 | "restore-keys": cacheRestoreKeys 190 | } 191 | } 192 | 193 | let readWriteCacheExpr = "(\(isProtectedBranch) || \(isTestDefaultBranch))" 194 | 195 | // pre is the list of steps required to establish and initialise the correct 196 | // caches for Go-based workflows. 197 | [ 198 | // TODO: once https://github.com/actions/setup-go/issues/54 is fixed, 199 | // we could use `go env` outputs from the setup-go step. 200 | json.#step & { 201 | name: "Get go mod cache directory" 202 | id: goModCacheDirID 203 | run: #"echo "dir=$(go env GOMODCACHE)" >> ${GITHUB_OUTPUT}"# 204 | }, 205 | json.#step & { 206 | name: "Get go build/test cache directory" 207 | id: goCacheDirID 208 | run: #"echo "dir=$(go env GOCACHE)" >> ${GITHUB_OUTPUT}"# 209 | }, 210 | 211 | // Only if we are not running in readonly mode do we want a step that 212 | // uses actions/cache (read and write). Even then, the use of the write 213 | // step should be predicated on us running on a protected branch. Because 214 | // it's impossible for anything else to write such a cache. 215 | if !#readonly { 216 | cacheStep & { 217 | if: readWriteCacheExpr 218 | uses: "actions/cache@v4" 219 | } 220 | }, 221 | 222 | cacheStep & { 223 | // If we are readonly, there is no condition on when we run this step. 224 | // It should always be run, becase there is no alternative. But if we 225 | // are not readonly, then we need to predicate this step on us not 226 | // being on a protected branch. 227 | if !#readonly { 228 | if: "! \(readWriteCacheExpr)" 229 | } 230 | 231 | uses: "actions/cache/restore@v4" 232 | }, 233 | 234 | if #cleanTestCache { 235 | // All tests on protected branches should skip the test cache. The 236 | // canonical way to do this is with -count=1. However, we want the 237 | // resulting test cache to be valid and current so that subsequent CLs 238 | // in the trybot repo can leverage the updated cache. Therefore, we 239 | // instead perform a clean of the testcache. 240 | // 241 | // Critically we only want to do this in the main repo, not the trybot 242 | // repo. 243 | json.#step & { 244 | if: "github.repository == '\(githubRepositoryPath)' && (\(isProtectedBranch) || github.ref == 'refs/heads/\(testDefaultBranch)')" 245 | run: "go clean -testcache" 246 | } 247 | }, 248 | ] 249 | } 250 | 251 | // isProtectedBranch is an expression that evaluates to true if the 252 | // job is running as a result of pushing to one of protectedBranchPatterns. 253 | // It would be nice to use the "contains" builtin for simplicity, 254 | // but array literals are not yet supported in expressions. 255 | isProtectedBranch: { 256 | #trailers: [...string] 257 | "((" + strings.Join([for branch in protectedBranchPatterns { 258 | (_matchPattern & {variable: "github.ref", pattern: "refs/heads/\(branch)"}).expr 259 | }], " || ") + ") && (! \(containsDispatchTrailer)))" 260 | } 261 | 262 | // isTestDefaultBranch is an expression that evaluates to true if 263 | // the job is running on the testDefaultBranch 264 | isTestDefaultBranch: "(github.ref == 'refs/heads/\(testDefaultBranch)')" 265 | 266 | // #isReleaseTag creates a GitHub expression, based on the given release tag 267 | // pattern, that evaluates to true if called in the context of a workflow that 268 | // is part of a release. 269 | isReleaseTag: { 270 | (_matchPattern & {variable: "github.ref", pattern: "refs/tags/\(releaseTagPattern)"}).expr 271 | } 272 | 273 | checkGitClean: json.#step & { 274 | name: "Check that git is clean at the end of the job" 275 | if: "always()" 276 | run: "test -z \"$(git status --porcelain)\" || (git status; git diff; false)" 277 | } 278 | 279 | repositoryDispatch: json.#step & { 280 | #githubRepositoryPath: *githubRepositoryPath | string 281 | #botGitHubUserTokenSecretsKey: *botGitHubUserTokenSecretsKey | string 282 | #arg: _ 283 | 284 | _curlGitHubAPI: curlGitHubAPI & {#tokenSecretsKey: #botGitHubUserTokenSecretsKey, _} 285 | 286 | name: string 287 | run: #""" 288 | \#(_curlGitHubAPI) --fail --request POST --data-binary \#(strconv.Quote(encjson.Marshal(#arg))) https://api.github.com/repos/\#(#githubRepositoryPath)/dispatches 289 | """# 290 | } 291 | 292 | // dispatchTrailer is the trailer that we use to pass information in a commit 293 | // when triggering workflow events in other GitHub repos. 294 | // 295 | // NOTE: keep this consistent with gerritstatusupdater parsing logic. 296 | dispatchTrailer: "Dispatch-Trailer" 297 | 298 | // dispatchTrailerStepID is the ID of the step that attempts 299 | // to extract a Dispatch-Trailer value from the commit at HEAD 300 | dispatchTrailerStepID: strings.Replace(dispatchTrailer, "-", "", -1) 301 | 302 | // _dispatchTrailerDecodeStepOutputVar is the name of the output 303 | // variable int he dispatchTrailerStepID step 304 | _dispatchTrailerDecodeStepOutputVar: "value" 305 | 306 | // dispatchTrailerExpr is a GitHub expression that can be dereferenced 307 | // to get values from the JSON-decded Dispatch-Trailer value that 308 | // is extracted during the dispatchTrailerStepID step. 309 | dispatchTrailerExpr: "fromJSON(steps.\(dispatchTrailerStepID).outputs.\(_dispatchTrailerDecodeStepOutputVar))" 310 | 311 | // containsDispatchTrailer returns a GitHub expression that looks at the commit 312 | // message of the head commit of the event that triggered the workflow, an 313 | // expression that returns true if the commit message associated with that head 314 | // commit contains dispatchTrailer. 315 | // 316 | // Note that this logic does not 100% match the answer that would be returned by: 317 | // 318 | // git log --pretty=%(trailers:key=Dispatch-Trailer,valueonly) 319 | // 320 | // GitHub expressions are incredibly limited in their capabilities: 321 | // 322 | // https://docs.github.com/en/actions/learn-github-actions/expressions 323 | // 324 | // There is not even a regular expression matcher. Hence the logic is a best-efforts 325 | // approximation of the logic employed by git log. 326 | containsDispatchTrailer: { 327 | #type?: string 328 | 329 | // If we have a value for #type, then match against that value. 330 | // Otherwise the best we can do is match against: 331 | // 332 | // Dispatch-Trailer: {"type:} 333 | // 334 | let _typeCheck = [if #type != _|_ {#type + "\""}, ""][0] 335 | """ 336 | (contains(\(_dispatchTrailerVariable), '\n\(dispatchTrailer): {"type":"\(_typeCheck)')) 337 | """ 338 | } 339 | 340 | containsTrybotTrailer: containsDispatchTrailer & { 341 | #type: trybot.key 342 | _ 343 | } 344 | 345 | containsUnityTrailer: containsDispatchTrailer & { 346 | #type: unity.key 347 | _ 348 | } 349 | 350 | _dispatchTrailerVariable: "github.event.head_commit.message" 351 | -------------------------------------------------------------------------------- /internal/ci/base/helpers.cue: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | // This file contains everything else 4 | 5 | import ( 6 | "list" 7 | "strings" 8 | ) 9 | 10 | // _matchPattern returns a GitHub Actions expression which evaluates whether a 11 | // variable matches a globbing pattern. For literal patterns it uses "==", 12 | // and for suffix patterns it uses "startsWith". 13 | // See https://docs.github.com/en/actions/learn-github-actions/expressions. 14 | _matchPattern: { 15 | variable: string 16 | pattern: string 17 | expr: [ 18 | if strings.HasSuffix(pattern, "*") { 19 | let prefix = strings.TrimSuffix(pattern, "*") 20 | "startsWith(\(variable), '\(prefix)')" 21 | }, 22 | { 23 | "\(variable) == '\(pattern)'" 24 | }, 25 | ][0] 26 | } 27 | 28 | doNotEditMessage: { 29 | #generatedBy: string 30 | "Code generated \(#generatedBy); DO NOT EDIT." 31 | } 32 | 33 | // #URLPath is a temporary measure to derive the path part of a URL. 34 | // 35 | // TODO: remove when cuelang.org/issue/1433 lands. 36 | URLPath: { 37 | #url: string 38 | let parts = strings.Split(#url, "/") 39 | strings.Join(list.Slice(parts, 3, len(parts)), "/") 40 | } 41 | -------------------------------------------------------------------------------- /internal/ci/ci_tool.cue: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The CUE Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ci 16 | 17 | import ( 18 | "path" 19 | "encoding/yaml" 20 | "tool/file" 21 | 22 | "github.com/cue-lang/cue-py/internal/ci/repo" 23 | "github.com/cue-lang/cue-py/internal/ci/github" 24 | ) 25 | 26 | // For the commands below, note we use simple yet hacky path resolution, rather 27 | // than anything that might derive the module root using go list or similar, in 28 | // order that we have zero dependencies. This is important because this CUE 29 | // package is "vendored" to an external dependency so that that unity can 30 | // re-run and verify these steps as part of a the test suite that runs against 31 | // new CUE versions. 32 | 33 | _goos: string @tag(os,var=os) 34 | 35 | // gen.workflows regenerates the GitHub workflow Yaml definitions. 36 | // 37 | // See internal/ci/gen.go for details on how this step fits into the sequence 38 | // of generating our CI workflow definitions, and updating various txtar tests 39 | // with files from that process. 40 | command: gen: { 41 | _dir: path.FromSlash("../../.github/workflows", path.Unix) 42 | 43 | workflows: { 44 | remove: { 45 | glob: file.Glob & { 46 | glob: path.Join([_dir, "*.yml"], _goos) 47 | files: [...string] 48 | } 49 | for _, _filename in glob.files { 50 | "delete \(_filename)": file.RemoveAll & { 51 | path: _filename 52 | } 53 | } 54 | } 55 | for _workflowName, _workflow in github.workflows { 56 | let _filename = _workflowName + repo.workflowFileExtension 57 | "generate \(_filename)": file.Create & { 58 | $after: [for v in remove {v}] 59 | filename: path.Join([_dir, _filename], _goos) 60 | let donotedit = repo.doNotEditMessage & {#generatedBy: "internal/ci/ci_tool.cue", _} 61 | contents: "# \(donotedit)\n\n\(yaml.Marshal(_workflow))" 62 | } 63 | } 64 | } 65 | } 66 | 67 | command: gen: codereviewcfg: file.Create & { 68 | _dir: path.FromSlash("../../", path.Unix) 69 | filename: path.Join([_dir, "codereview.cfg"], _goos) 70 | let res = repo.toCodeReviewCfg & {#input: repo.codeReview, _} 71 | let donotedit = repo.doNotEditMessage & {#generatedBy: "internal/ci/ci_tool.cue", _} 72 | contents: "# \(donotedit)\n\n\(res)\n" 73 | } 74 | -------------------------------------------------------------------------------- /internal/ci/github/repo.cue: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The CUE Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package github 16 | 17 | // This file exists to provide a single point of importing 18 | // the repo package. The pattern of using base and repo 19 | // is replicated across a number of CUE repos, and as such 20 | // the import path of repo varies between them. This makes 21 | // spotting differences and applying changes between the 22 | // github/*.cue files noisy. Instead, import the repo package 23 | // in a single file, and that keeps the different in import 24 | // path down to a single file. 25 | 26 | import repo "github.com/cue-lang/cue-py/internal/ci/repo" 27 | 28 | _repo: repo 29 | -------------------------------------------------------------------------------- /internal/ci/github/trybot.cue: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The CUE Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package github 16 | 17 | import ( 18 | "list" 19 | 20 | "github.com/SchemaStore/schemastore/src/schemas/json" 21 | ) 22 | 23 | // The trybot workflow. 24 | workflows: trybot: _repo.bashWorkflow & { 25 | name: _repo.trybot.name 26 | 27 | on: { 28 | push: { 29 | branches: list.Concat([[_repo.testDefaultBranch], _repo.protectedBranchPatterns]) // do not run PR branches 30 | } 31 | pull_request: {} 32 | } 33 | 34 | jobs: { 35 | test: { 36 | strategy: _testStrategy 37 | "runs-on": "${{ matrix.runner }}" 38 | 39 | let _setupGoActionsCaches = _repo.setupGoActionsCaches & { 40 | #goVersion: goVersionVal 41 | #os: runnerOSVal 42 | _ 43 | } 44 | 45 | // Only run the trybot workflow if we have the trybot trailer, or 46 | // if we have no special trailers. Note this condition applies 47 | // after and in addition to the "on" condition above. 48 | if: "\(_repo.containsTrybotTrailer) || ! \(_repo.containsDispatchTrailer)" 49 | 50 | steps: [ 51 | for v in _repo.checkoutCode {v}, 52 | 53 | json.#step & { 54 | uses: "cue-lang/setup-cue@v1.0.0" 55 | with: version: "v0.10.0" 56 | }, 57 | 58 | for v in _installGo {v}, 59 | _repo.earlyChecks, 60 | 61 | json.#step & { 62 | name: "Re-generate CI" 63 | run: """ 64 | cue cmd importjsonschema ./vendor 65 | cue cmd gen 66 | """ 67 | "working-directory": "./internal/ci" 68 | }, 69 | 70 | _repo.checkGitClean, 71 | 72 | _installPython, 73 | 74 | // cachePre must come after installing Go, 75 | // because the cache locations 76 | // are established by running each tool. 77 | for v in _setupGoActionsCaches {v}, 78 | 79 | _runPip, 80 | 81 | _checkoutLibcue, 82 | _buildLibcue, 83 | 84 | _mypy, 85 | _addLibcueToPath, 86 | _pytest, 87 | 88 | _repo.checkGitClean, 89 | ] 90 | } 91 | } 92 | 93 | let runnerOS = "runner.os" 94 | let runnerOSVal = "${{ \(runnerOS) }}" 95 | let goVersion = "matrix.go-version" 96 | let goVersionVal = "${{ \(goVersion) }}" 97 | let pythonVersion = "matrix.python-version" 98 | let pythonVersionVal = "${{ \(pythonVersion) }}" 99 | 100 | _testStrategy: { 101 | "fail-fast": false 102 | matrix: { 103 | "go-version": [_repo.latestStableGo] 104 | "python-version": [_repo.latestStablePython] 105 | 106 | // TODO: Windows doesn't work yet, see issue #3253 107 | runner: [_repo.linuxMachine, _repo.macosMachine] 108 | } 109 | } 110 | 111 | _installGo: _repo.installGo & { 112 | #setupGo: with: "go-version": goVersionVal 113 | _ 114 | } 115 | 116 | _installPython: json.#step & { 117 | name: "Install Python" 118 | uses: "actions/setup-python@v5" 119 | with: { 120 | "python-version": pythonVersionVal 121 | cache: "pip" 122 | } 123 | } 124 | 125 | _runPip: json.#step & { 126 | name: "pip install" 127 | run: "pip install -r requirements.txt" 128 | } 129 | 130 | _checkoutLibcue: json.#step & { 131 | name: "Checkout libcue" 132 | uses: "actions/checkout@v4" 133 | with: { 134 | repository: "cue-lang/libcue" 135 | path: "libcue-checkout" 136 | } 137 | } 138 | 139 | _buildLibcue: json.#step & { 140 | name: "Build libcue" 141 | "working-directory": "libcue-checkout" 142 | // The name of the shared library is target-dependent. 143 | // Build libcue with all possible names so we're covered 144 | // in all cases. 145 | run: """ 146 | go build -o libcue.so -buildmode=c-shared 147 | cp libcue.so libcue.dylib 148 | cp libcue.so cue.dll 149 | """ 150 | } 151 | 152 | _mypy: json.#step & { 153 | name: "mypy" 154 | run: "mypy ." 155 | } 156 | 157 | _addLibcueToPath: json.#step & { 158 | name: "Add libcue to PATH" 159 | if: "runner.os == 'Windows'" 160 | // On Windows LoadLibrary loads DLLs from PATH. GitHub 161 | // actions doesn't allow setting PATH via `env`, 162 | // rather we need to append to the file pointed to by 163 | // `$GITHUB_PATH`. This will only affect future steps, 164 | // so we do it before running `pytest`. 165 | run: """ 166 | echo '${{ github.workspace }}/libcue-checkout' >> $GITHUB_PATH 167 | """ 168 | } 169 | 170 | _pytest: json.#step & { 171 | name: "pytest" 172 | env: LD_LIBRARY_PATH: "${{ github.workspace }}/libcue-checkout" 173 | env: DYLD_LIBRARY_PATH: "${{ github.workspace }}/libcue-checkout" 174 | run: "pytest" 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /internal/ci/github/workflows.cue: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The CUE Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // package github declares the workflows for this project. 16 | package github 17 | 18 | // Note: the name of the workflows (and hence the corresponding .yml filenames) 19 | // correspond to the environment variable names for gerritstatusupdater. 20 | // Therefore, this filename must only be change in combination with also 21 | // updating the environment in which gerritstatusupdater is running for this 22 | // repository. 23 | // 24 | // This name is also used by the CI badge in the top-level README. 25 | // 26 | // This name is also used in the evict_caches lookups. 27 | // 28 | // i.e. don't change the names of workflows! 29 | // 30 | // In addition to separately declaring the workflows themselves, we define the 31 | // shape of #workflows here as a cross-check that we don't accidentally change 32 | // the name of workflows without reading this comment. 33 | // 34 | // We explicitly use close() here instead of a definition in order that we can 35 | // cue export the github package as a test. 36 | workflows: close({ 37 | // Adding this constraint here, whilst clear for the reader, 38 | // blows out evaluation time. This will be fixed as part of 39 | // the performance work which is covered under various issues. 40 | // [string]: json.#Workflow 41 | 42 | _repo.trybotWorkflows 43 | trybot_dispatch: #dummyDispatch: dummyDispatch 44 | }) 45 | 46 | dummyDispatch: _repo.#dispatch & { 47 | type: _repo.trybot.key 48 | } 49 | -------------------------------------------------------------------------------- /internal/ci/repo/repo.cue: -------------------------------------------------------------------------------- 1 | // package repo contains data values that are common to all CUE configurations 2 | // in this repo. The list of configurations includes GitHub workflows, but also 3 | // things like gerrit configuration etc. 4 | package repo 5 | 6 | import ( 7 | "github.com/cue-lang/cue-py/internal/ci/base" 8 | ) 9 | 10 | base 11 | 12 | githubRepositoryPath: "cue-lang/cue-py" 13 | 14 | botGitHubUser: "cueckoo" 15 | botGitHubUserEmail: "cueckoo@gmail.com" 16 | 17 | defaultBranch: "main" 18 | 19 | linuxMachine: "ubuntu-22.04" 20 | macosMachine: "macos-14" 21 | windowsMachine: "windows-2022" 22 | 23 | // libcue requires Go 1.22+, so we can't test on any earlier version. 24 | latestStableGo: "1.23.x" 25 | 26 | latestStablePython: "3.12" 27 | -------------------------------------------------------------------------------- /internal/ci/vendor/vendor_tool.cue: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The CUE Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package vendor 16 | 17 | import ( 18 | "path" 19 | 20 | "tool/exec" 21 | "tool/http" 22 | ) 23 | 24 | // _cueCmd defines the command that is run to run cmd/cue. 25 | // This is factored out in order that the cue-github-actions 26 | // project which "vendors" the various workflow-related 27 | // packages can specify "cue" as the value so that unity 28 | // tests can specify the cmd/cue binary to use. 29 | _cueCmd: string | *"cue" @tag(cue_cmd) 30 | 31 | // For the commands below, note we use simple yet hacky path resolution, rather 32 | // than anything that might derive the module root using go list or similar, in 33 | // order that we have zero dependencies. 34 | 35 | // importjsonschema vendors a CUE-imported version of the JSONSchema that 36 | // defines GitHub workflows into the main module's cue.mod/pkg. 37 | command: importjsonschema: { 38 | getJSONSchema: http.Get & { 39 | request: body: "" 40 | 41 | // Tip link for humans: 42 | // https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/github-workflow.json 43 | url: "https://raw.githubusercontent.com/SchemaStore/schemastore/88d26ad0d451cbd5ebc70218062850aa905bdf18/src/schemas/json/github-workflow.json" 44 | } 45 | import: exec.Run & { 46 | _outpath: path.FromSlash("../../cue.mod/pkg/github.com/SchemaStore/schemastore/src/schemas/json/github-workflow.cue", "unix") 47 | stdin: getJSONSchema.response.body 48 | cmd: "\(_cueCmd) import -f -p json -l #Workflow: -o \(_outpath) jsonschema: -" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /libcue/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Low-level bindings to libcue. 17 | """ 18 | 19 | from .api import * 20 | -------------------------------------------------------------------------------- /libcue/api.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | This module implements low-level bindings to libcue. 17 | """ 18 | 19 | # In ABI mode the bindings from libcue are generated at runtime. 20 | # Silence the type checker about missing attributes (symbols) not 21 | # known at type-check time. 22 | # mypy: disable-error-code="attr-defined" 23 | 24 | from typing import Optional 25 | from cffi import FFI 26 | import sys 27 | 28 | ffi = FFI() 29 | 30 | # We're mixing tabs and spaces because we want to be able to copy-paste 31 | # these declarations from libcue/cue.h. 32 | ffi.cdef(""" 33 | typedef uintptr_t cue_ctx; 34 | typedef uintptr_t cue_value; 35 | typedef uintptr_t cue_error; 36 | 37 | typedef enum { 38 | CUE_KIND_BOTTOM, 39 | CUE_KIND_NULL, 40 | CUE_KIND_BOOL, 41 | CUE_KIND_INT, 42 | CUE_KIND_FLOAT, 43 | CUE_KIND_STRING, 44 | CUE_KIND_BYTES, 45 | CUE_KIND_STRUCT, 46 | CUE_KIND_LIST, 47 | CUE_KIND_NUMBER, 48 | CUE_KIND_TOP, 49 | } cue_kind; 50 | 51 | typedef enum { 52 | CUE_BUILD_NONE, 53 | CUE_BUILD_FILENAME, 54 | CUE_BUILD_IMPORT_PATH, 55 | CUE_BUILD_INFER_BUILTINS, 56 | CUE_BUILD_SCOPE, 57 | } cue_bopt_tag; 58 | 59 | typedef struct { 60 | cue_bopt_tag tag; 61 | cue_value value; 62 | char *str; 63 | bool b; 64 | } cue_bopt; 65 | 66 | typedef enum { 67 | CUE_OPT_NONE, 68 | CUE_OPT_ALL, 69 | CUE_OPT_ATTR, 70 | CUE_OPT_CONCRETE, 71 | CUE_OPT_DEFS, 72 | CUE_OPT_DISALLOW_CYCLES, 73 | CUE_OPT_DOCS, 74 | CUE_OPT_ERRORS_AS_VALUES, 75 | CUE_OPT_FINAL, 76 | CUE_OPT_HIDDEN, 77 | CUE_OPT_INLINE_IMPORTS, 78 | CUE_OPT_OPTIONALS, 79 | CUE_OPT_RAW, 80 | CUE_OPT_SCHEMA, 81 | } cue_eopt_tag; 82 | 83 | typedef struct { 84 | cue_eopt_tag tag; 85 | bool value; 86 | } cue_eopt; 87 | 88 | cue_ctx cue_newctx(void); 89 | char* cue_error_string(cue_error); 90 | 91 | cue_error cue_compile_string(cue_ctx, char*, void*, cue_value*); 92 | cue_error cue_compile_bytes(cue_ctx, void*, size_t, void*, cue_value*); 93 | 94 | cue_value cue_top(cue_ctx); 95 | cue_value cue_bottom(cue_ctx); 96 | cue_value cue_unify(cue_value, cue_value); 97 | cue_error cue_instance_of(cue_value, cue_value, void*); 98 | cue_error cue_lookup_string(cue_value, char*, cue_value*); 99 | cue_value cue_from_int64(cue_ctx, int64_t); 100 | cue_value cue_from_uint64(cue_ctx, uint64_t); 101 | cue_value cue_from_bool(cue_ctx, bool); 102 | cue_value cue_from_double(cue_ctx, double); 103 | cue_value cue_from_string(cue_ctx, char*); 104 | cue_value cue_from_bytes(cue_ctx, void*, size_t); 105 | cue_error cue_dec_int64(cue_value, int64_t*); 106 | cue_error cue_dec_uint64(cue_value, uint64_t*); 107 | cue_error cue_dec_bool(cue_value, bool*); 108 | cue_error cue_dec_double(cue_value, double*); 109 | cue_error cue_dec_string(cue_value, char**); 110 | cue_error cue_dec_bytes(cue_value, uint8_t**, size_t*); 111 | cue_error cue_dec_json(cue_value, uint8_t**, size_t*); 112 | cue_error cue_validate(cue_value, void*); 113 | cue_value cue_default(cue_value, bool*); 114 | cue_kind cue_concrete_kind(cue_value); 115 | cue_kind cue_incomplete_kind(cue_value); 116 | cue_error cue_value_error(cue_value); 117 | bool cue_is_equal(cue_value, cue_value); 118 | 119 | void cue_free(uintptr_t); 120 | void cue_free_all(uintptr_t*); 121 | void libc_free(void*); 122 | """) 123 | 124 | # libcue shared object name on ELF platforms (default). 125 | libcue_so = "libcue.so" 126 | if sys.platform == "darwin": 127 | # macOS is Mach-O, not ELF, shared object extension is dylib. 128 | libcue_so = "libcue.dylib" 129 | elif sys.platform == "win32": 130 | # on Windows shared objects don't use `lib` prefix and end with dll. 131 | libcue_so = "cue.dll" 132 | 133 | if sys.platform != "win32": 134 | # When there are no more references to lib (such as when there 135 | # are no more references to this module), the Python runtime can 136 | # try to unload libcue. Go shared libraries cannot be unloaded. 137 | # Pass RTLD_NODELETE to dlopen to prevent this. 138 | # 139 | # Note that RTLD_NODELETE is Unix-only, so on Windows we do 140 | # something different (see below). 141 | # 142 | # Also note that the Python garbage collector could run at program 143 | # exit and it could determine that there are no more references 144 | # to libcue (because the program is exiting), triggering a 145 | # premature and dangerous dlclose. This prevents it. 146 | lib = ffi.dlopen(libcue_so, ffi.RTLD_NODELETE) 147 | else: 148 | # On Windows we don't have RTLD_NODELETE. Create an artificial 149 | # global reference to lib, so we prevent unloading the shared 150 | # library. 151 | lib = ffi.dlopen(libcue_so) 152 | sys.modules[__name__]._lib_reference = lib 153 | 154 | KIND_BOTTOM = lib.CUE_KIND_BOTTOM 155 | KIND_NULL = lib.CUE_KIND_NULL 156 | KIND_BOOL = lib.CUE_KIND_BOOL 157 | KIND_INT = lib.CUE_KIND_INT 158 | KIND_FLOAT = lib.CUE_KIND_FLOAT 159 | KIND_STRING = lib.CUE_KIND_STRING 160 | KIND_BYTES = lib.CUE_KIND_BYTES 161 | KIND_STRUCT = lib.CUE_KIND_STRUCT 162 | KIND_LIST = lib.CUE_KIND_LIST 163 | KIND_NUMBER = lib.CUE_KIND_NUMBER 164 | KIND_TOP = lib.CUE_KIND_TOP 165 | 166 | BUILD_NONE = lib.CUE_BUILD_NONE 167 | BUILD_FILENAME = lib.CUE_BUILD_FILENAME 168 | BUILD_IMPORT_PATH = lib.CUE_BUILD_IMPORT_PATH 169 | BUILD_INFER_BUILTINS = lib.CUE_BUILD_INFER_BUILTINS 170 | BUILD_SCOPE = lib.CUE_BUILD_SCOPE 171 | 172 | OPT_NONE = lib.CUE_OPT_NONE 173 | OPT_ALL = lib.CUE_OPT_ALL 174 | OPT_ATTR = lib.CUE_OPT_ATTR 175 | OPT_CONCRETE = lib.CUE_OPT_CONCRETE 176 | OPT_DEFS = lib.CUE_OPT_DEFS 177 | OPT_DISALLOW_CYCLES = lib.CUE_OPT_DISALLOW_CYCLES 178 | OPT_DOCS = lib.CUE_OPT_DOCS 179 | OPT_ERRORS_AS_VALUES = lib.CUE_OPT_ERRORS_AS_VALUES 180 | OPT_FINAL = lib.CUE_OPT_FINAL 181 | OPT_HIDDEN = lib.CUE_OPT_HIDDEN 182 | OPT_INLINE_IMPORTS = lib.CUE_OPT_INLINE_IMPORTS 183 | OPT_OPTIONALS = lib.CUE_OPT_OPTIONALS 184 | OPT_RAW = lib.CUE_OPT_RAW 185 | OPT_SCHEMA = lib.CUE_OPT_SCHEMA 186 | 187 | def newctx() -> int: 188 | return lib.cue_newctx() 189 | 190 | def error_string(err: int) -> FFI.CData: 191 | return lib.cue_error_string(err) 192 | 193 | def compile_string(ctx: int, str: FFI.CData, opts: Optional[FFI.CData], val_ptr: FFI.CData) -> int: 194 | if opts == None: 195 | return lib.cue_compile_string(ctx, str, ffi.NULL, val_ptr) 196 | return lib.cue_compile_string(ctx, str, opts, val_ptr) 197 | 198 | def compile_bytes(ctx: int, buf: FFI.CData, len: int, opts: Optional[FFI.CData], val_ptr: FFI.CData) -> int: 199 | if opts == None: 200 | return lib.cue_compile_bytes(ctx, buf, len, ffi.NULL, val_ptr) 201 | return lib.cue_compile_bytes(ctx, buf, len, opts, val_ptr) 202 | 203 | def top(ctx: int) -> int: 204 | return lib.cue_top(ctx) 205 | 206 | def bottom(ctx: int) -> int: 207 | return lib.cue_bottom(ctx) 208 | 209 | def unify(x: int, y: int) -> int: 210 | return lib.cue_unify(x, y) 211 | 212 | def instance_of(v0: int, v1: int, opts: Optional[FFI.CData]) -> int: 213 | if opts == None: 214 | return lib.cue_instance_of(v0, v1, ffi.NULL) 215 | return lib.cue_instance_of(v0, v1, opts) 216 | 217 | def lookup_string(v: int, path: FFI.CData, ptr: FFI.CData) -> int: 218 | return lib.cue_lookup_string(v, path, ptr) 219 | 220 | def from_int64(ctx: int, val: int) -> int: 221 | return lib.cue_from_int64(ctx, val) 222 | 223 | def from_uint64(ctx: int, val: int) -> int: 224 | return lib.cue_from_uint64(ctx, val) 225 | 226 | def from_bool(ctx: int, val: bool) -> int: 227 | return lib.cue_from_bool(ctx, val) 228 | 229 | def from_double(ctx: int, val: float) -> int: 230 | return lib.cue_from_double(ctx, val) 231 | 232 | def from_string(ctx: int, val: FFI.CData) -> int: 233 | return lib.cue_from_string(ctx, val) 234 | 235 | def from_bytes(ctx: int, buf: FFI.CData, len: int) -> int: 236 | return lib.cue_from_bytes(ctx, buf, len) 237 | 238 | def dec_int64(val: int, ptr: FFI.CData) -> int: 239 | return lib.cue_dec_int64(val, ptr) 240 | 241 | def dec_uint64(val: int, ptr: FFI.CData) -> int: 242 | return lib.cue_dec_uint64(val, ptr) 243 | 244 | def dec_bool(val: int, ptr: FFI.CData) -> int: 245 | return lib.cue_dec_bool(val, ptr) 246 | 247 | def dec_double(val: int, ptr: FFI.CData) -> int: 248 | return lib.cue_dec_double(val, ptr) 249 | 250 | def dec_string(val: int, ptr: FFI.CData) -> int: 251 | return lib.cue_dec_string(val, ptr) 252 | 253 | def dec_bytes(val: int, buf_ptr: FFI.CData, len_ptr: FFI.CData) -> int: 254 | return lib.cue_dec_bytes(val, buf_ptr, len_ptr) 255 | 256 | def dec_json(val: int, buf_ptr: FFI.CData, len_ptr: FFI.CData) -> int: 257 | return lib.cue_dec_json(val, buf_ptr, len_ptr) 258 | 259 | def validate(val: int, opts: Optional[FFI.CData]) -> int: 260 | if opts == None: 261 | return lib.cue_validate(val, ffi.NULL) 262 | return lib.cue_validate(val, opts) 263 | 264 | def default(val: int, ok_ptr: FFI.CData) -> int: 265 | return lib.cue_default(val, ok_ptr) 266 | 267 | def concrete_kind(v: int) -> int: 268 | return lib.cue_concrete_kind(v) 269 | 270 | def incomplete_kind(v: int) -> int: 271 | return lib.cue_incomplete_kind(v) 272 | 273 | def value_error(v: int) -> int: 274 | return lib.cue_value_error(v) 275 | 276 | def is_equal(x: int, y: int) -> bool: 277 | return lib.cue_is_equal(x, y) 278 | 279 | def free(x: int) -> None: 280 | lib.cue_free(x) 281 | 282 | def libc_free(ptr: FFI.CData) -> None: 283 | lib.libc_free(ptr) 284 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==23.2.0 2 | cffi==1.16.0 3 | filelock==3.13.4 4 | iniconfig==2.0.0 5 | mypy==1.9.0 6 | mypy-extensions==1.0.0 7 | packaging==24.0 8 | pluggy==1.4.0 9 | pycparser==2.22 10 | pytest==8.1.1 11 | pytest-mypy==0.10.3 12 | types-cffi==1.16.0.20240331 13 | types-setuptools==69.5.0.20240423 14 | typing_extensions==4.11.0 15 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | CUE Python tests. 17 | """ 18 | -------------------------------------------------------------------------------- /tests/test_context.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | cue.Context tests. 17 | """ 18 | 19 | import pytest 20 | import cue 21 | 22 | def test_compile_empty(): 23 | ctx = cue.Context() 24 | 25 | val = ctx.compile("") 26 | assert isinstance(val, cue.Value) 27 | 28 | val = ctx.compile(b"") 29 | assert isinstance(val, cue.Value) 30 | 31 | def test_compile_empty_with_options(): 32 | ctx = cue.Context() 33 | 34 | val = ctx.compile("", cue.FileName("empty.cue"), cue.ImportPath("example.com/foo/bar")) 35 | assert isinstance(val, cue.Value) 36 | 37 | val = ctx.compile(b"", cue.FileName("empty.cue"), cue.ImportPath("example.com/foo/bar")) 38 | assert isinstance(val, cue.Value) 39 | 40 | def test_compile(): 41 | ctx = cue.Context() 42 | 43 | val = ctx.compile("true") 44 | assert isinstance(val, cue.Value) 45 | 46 | val = ctx.compile("42") 47 | assert isinstance(val, cue.Value) 48 | 49 | val = ctx.compile("1.2345") 50 | assert isinstance(val, cue.Value) 51 | 52 | val = ctx.compile('"hello"') 53 | assert isinstance(val, cue.Value) 54 | 55 | val = ctx.compile("'world'") 56 | assert isinstance(val, cue.Value) 57 | 58 | val = ctx.compile("int") 59 | assert isinstance(val, cue.Value) 60 | 61 | val = ctx.compile("{}") 62 | assert isinstance(val, cue.Value) 63 | 64 | val = ctx.compile("x: 42") 65 | assert isinstance(val, cue.Value) 66 | 67 | val = ctx.compile("x: y: z: true") 68 | assert isinstance(val, cue.Value) 69 | 70 | def test_compile_bytes(): 71 | ctx = cue.Context() 72 | 73 | val = ctx.compile(b"true") 74 | assert isinstance(val, cue.Value) 75 | 76 | val = ctx.compile(b"42") 77 | assert isinstance(val, cue.Value) 78 | 79 | val = ctx.compile(b"1.2345") 80 | assert isinstance(val, cue.Value) 81 | 82 | val = ctx.compile(b'"hello"') 83 | assert isinstance(val, cue.Value) 84 | 85 | val = ctx.compile(b"'world'") 86 | assert isinstance(val, cue.Value) 87 | 88 | val = ctx.compile(b"int") 89 | assert isinstance(val, cue.Value) 90 | 91 | val = ctx.compile(b"{}") 92 | assert isinstance(val, cue.Value) 93 | 94 | val = ctx.compile(b"x: 42") 95 | assert isinstance(val, cue.Value) 96 | 97 | val = ctx.compile(b"x: y: z: true") 98 | assert isinstance(val, cue.Value) 99 | 100 | def test_compile_with_options(): 101 | ctx = cue.Context() 102 | 103 | val = ctx.compile("true", cue.FileName("empty.cue")) 104 | assert isinstance(val, cue.Value) 105 | 106 | val = ctx.compile("42", cue.FileName("empty.cue")) 107 | assert isinstance(val, cue.Value) 108 | 109 | val = ctx.compile("1.2345", cue.FileName("empty.cue")) 110 | assert isinstance(val, cue.Value) 111 | 112 | val = ctx.compile('"hello"', cue.FileName("empty.cue")) 113 | assert isinstance(val, cue.Value) 114 | 115 | val = ctx.compile("'world'", cue.FileName("empty.cue")) 116 | assert isinstance(val, cue.Value) 117 | 118 | val = ctx.compile("int", cue.FileName("empty.cue")) 119 | assert isinstance(val, cue.Value) 120 | 121 | val = ctx.compile("{}", cue.FileName("empty.cue")) 122 | assert isinstance(val, cue.Value) 123 | 124 | val = ctx.compile("x: 42", cue.FileName("empty.cue")) 125 | assert isinstance(val, cue.Value) 126 | 127 | val = ctx.compile("x: y: z: true", cue.FileName("empty.cue")) 128 | assert isinstance(val, cue.Value) 129 | 130 | def test_compile_error(): 131 | ctx = cue.Context() 132 | 133 | with pytest.raises(cue.Error): 134 | ctx.compile("<") 135 | 136 | with pytest.raises(cue.Error, match="expected operand, found 'EOF'"): 137 | ctx.compile("a: b: -") 138 | 139 | with pytest.raises(cue.Error, match="expected operand, found 'EOF'"): 140 | ctx.compile(b"a: b: -") 141 | 142 | def test_to_value(): 143 | ctx = cue.Context() 144 | 145 | val = ctx.to_value(1) 146 | assert val == ctx.compile("1") 147 | 148 | val = ctx.to_value(-1) 149 | assert val == ctx.compile("-1") 150 | 151 | val = ctx.to_value(42) 152 | assert val == ctx.compile("42") 153 | 154 | val = ctx.to_value(True) 155 | assert val == ctx.compile("true") 156 | 157 | val = ctx.to_value(False) 158 | assert val == ctx.compile("false") 159 | 160 | val = ctx.to_value(1.2345) 161 | assert val == ctx.compile("1.2345") 162 | 163 | val = ctx.to_value("hello") 164 | assert val == ctx.compile("\"hello\"") 165 | 166 | val = ctx.to_value(b"world") 167 | assert val == ctx.compile("'world'") 168 | 169 | def test_encoding_decoding_equal(): 170 | ctx = cue.Context() 171 | 172 | v = 1 173 | assert v == ctx.to_value(v).to_int() 174 | 175 | v = 0xcafebabe 176 | assert v == ctx.to_value(v).to_unsigned() 177 | 178 | v = True 179 | assert v == ctx.to_value(v).to_bool() 180 | 181 | v = 1.2345 182 | assert v == ctx.to_value(v).to_float() 183 | 184 | v = "hello" 185 | assert v == ctx.to_value(v).to_str() 186 | 187 | v = b"world" 188 | assert v == ctx.to_value(v).to_bytes() 189 | -------------------------------------------------------------------------------- /tests/test_value.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The CUE Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | cue.Value tests. 17 | """ 18 | 19 | import pytest 20 | import cue 21 | 22 | def test_context(): 23 | ctx = cue.Context() 24 | 25 | val = ctx.compile("") 26 | assert val.context() == ctx 27 | 28 | val = ctx.compile("x: 42") 29 | assert val.context() == ctx 30 | 31 | def test_equal(): 32 | ctx = cue.Context() 33 | 34 | val0 = ctx.compile("") 35 | val1 = ctx.compile("") 36 | assert val0 == val1 37 | 38 | val2 = ctx.compile("x: 1") 39 | val3 = ctx.compile("x: 1") 40 | assert val2 == val3 41 | 42 | val4 = ctx.compile("a: b: { x: 42, y: \"hello\" }") 43 | val5 = ctx.compile("a: b: { x: 42, y: \"hello\" }") 44 | assert val4 == val5 45 | 46 | def test_not_equal(): 47 | ctx = cue.Context() 48 | 49 | val0 = ctx.compile("") 50 | val1 = ctx.compile("true") 51 | assert val0 != val1 52 | 53 | val2 = ctx.compile("x: 1") 54 | val3 = ctx.compile("x: 2") 55 | assert val2 != val3 56 | 57 | val4 = ctx.compile("a: b: { x: 42, y: \"hello\" }") 58 | val5 = ctx.compile("a: b: { x: 42, y: \"world\" }") 59 | assert val4 != val5 60 | 61 | val6 = ctx.compile("true") 62 | assert val6 != True 63 | 64 | def test_unify(): 65 | ctx = cue.Context() 66 | 67 | a = ctx.top() 68 | b = ctx.compile("true") 69 | r = ctx.compile("true") 70 | assert r == a.unify(b) 71 | 72 | a = ctx.compile("int") 73 | b = ctx.compile("42") 74 | r = ctx.compile("42") 75 | assert r == a.unify(b) 76 | 77 | a = ctx.compile("<100") 78 | b = ctx.compile("5") 79 | r = ctx.compile("5") 80 | assert r == a.unify(b) 81 | 82 | a = ctx.compile("x: y: string") 83 | b = ctx.compile('x: y: "hello"') 84 | r = ctx.compile('x: y: "hello"') 85 | assert r == a.unify(b) 86 | 87 | def test_unify_error(): 88 | ctx = cue.Context() 89 | 90 | a = ctx.compile("true") 91 | b = ctx.compile("false") 92 | r = ctx.bottom() 93 | assert r == a.unify(b) 94 | 95 | def test_decode(): 96 | ctx = cue.Context() 97 | 98 | val = ctx.compile("1") 99 | assert val.to_int() == 1 100 | 101 | val = ctx.compile("int32 & 42") 102 | assert val.to_int() == 42 103 | 104 | val = ctx.compile("-1") 105 | assert val.to_int() == -1 106 | 107 | val = ctx.compile("0xcafebabe") 108 | assert val.to_unsigned() == 0xcafebabe 109 | 110 | val = ctx.compile("true") 111 | assert val.to_bool() == True 112 | 113 | val = ctx.compile("false") 114 | assert val.to_bool() == False 115 | 116 | val = ctx.compile("1.2345") 117 | assert val.to_float() == 1.2345 118 | 119 | val = ctx.compile("\"hello\"") 120 | assert val.to_str() == "hello" 121 | 122 | val = ctx.compile("'world'") 123 | assert val.to_bytes() == b"world" 124 | 125 | val = ctx.compile("x: { a: 1, b: true }") 126 | assert val.to_json() == r'{"x":{"a":1,"b":true}}' 127 | 128 | def test_decode_error(): 129 | ctx = cue.Context() 130 | 131 | with pytest.raises(cue.Error): 132 | ctx.compile("1").to_bool() 133 | 134 | with pytest.raises(cue.Error): 135 | ctx.compile("-1").to_str() 136 | 137 | with pytest.raises(cue.Error): 138 | ctx.compile("true").to_float() 139 | 140 | with pytest.raises(cue.Error): 141 | ctx.compile("false").to_unsigned() 142 | 143 | def test_lookup(): 144 | ctx = cue.Context() 145 | 146 | val = ctx.compile("x: true") 147 | assert val.lookup("x").to_bool() == True 148 | 149 | val = ctx.compile(r'x: y: { a: 1, b: "hello"}') 150 | assert val.lookup("x").lookup("y").lookup("b").to_str() == "hello" 151 | assert val.lookup("x.y.a").to_int() == 1 152 | 153 | def test_default(): 154 | ctx = cue.Context() 155 | 156 | v = ctx.compile("int") 157 | assert v.default() == None 158 | 159 | v = ctx.compile("1") 160 | assert v.default() == None 161 | 162 | v = ctx.compile("int | *1") 163 | assert v.default() == ctx.to_value(1) 164 | 165 | v = ctx.compile(r'string | *"hello"') 166 | assert v.default() == ctx.to_value("hello") 167 | 168 | v = ctx.compile(r'(int | *1) & 2') 169 | assert v.default() == None 170 | 171 | def test_check_schema(): 172 | ctx = cue.Context() 173 | 174 | s = ctx.compile("bool") 175 | v = ctx.compile("true") 176 | v.check_schema(s) 177 | 178 | s = ctx.compile("int") 179 | v = ctx.compile("42") 180 | v.check_schema(s) 181 | 182 | s = ctx.compile("number") 183 | v = ctx.compile("1.2345") 184 | v.check_schema(s) 185 | 186 | s = ctx.compile("string") 187 | v = ctx.compile(r'"hello"') 188 | v.check_schema(s) 189 | 190 | s = ctx.compile("x: int8") 191 | v = ctx.compile("x: 1") 192 | v.check_schema(s) 193 | 194 | s = ctx.compile(r'{ x: bool, y: { a: int, b!: string} }') 195 | v = ctx.compile(r'{ x: false, y: { a: 1, b: "hello"} }') 196 | v.check_schema(s) 197 | 198 | def test_check_schema_error(): 199 | ctx = cue.Context() 200 | 201 | s = ctx.compile("bool") 202 | v = ctx.compile("1") 203 | with pytest.raises(cue.Error): 204 | v.check_schema(s) 205 | 206 | s = ctx.compile("int") 207 | v = ctx.compile("true") 208 | with pytest.raises(cue.Error): 209 | v.check_schema(s) 210 | 211 | s = ctx.compile("number") 212 | v = ctx.compile("false") 213 | with pytest.raises(cue.Error): 214 | v.check_schema(s) 215 | 216 | s = ctx.compile("string") 217 | v = ctx.compile("42") 218 | with pytest.raises(cue.Error): 219 | v.check_schema(s) 220 | 221 | s = ctx.compile("x: int8") 222 | v = ctx.compile("x: 78942372348") 223 | with pytest.raises(cue.Error): 224 | v.check_schema(s) 225 | 226 | s = ctx.compile(r'{ x: bool, y: { a: int, b!: string} }') 227 | v = ctx.compile(r'{ x: 1, y: { a: true, b: 1.2345} }') 228 | with pytest.raises(cue.Error): 229 | v.check_schema(s) 230 | 231 | def test_validate(): 232 | ctx = cue.Context() 233 | 234 | ctx.compile("1").validate() 235 | ctx.compile("{ x: 42 }").validate() 236 | 237 | with pytest.raises(cue.Error): 238 | ctx.compile("int").validate(cue.Concrete(True)) 239 | 240 | def test_kind(): 241 | ctx = cue.Context() 242 | 243 | val = ctx.compile("null") 244 | assert cue.Kind.NULL == val.kind() 245 | 246 | val = ctx.compile("true") 247 | assert cue.Kind.BOOL == val.kind() 248 | 249 | val = ctx.compile("42") 250 | assert cue.Kind.INT == val.kind() 251 | 252 | val = ctx.compile("1.2345") 253 | assert cue.Kind.FLOAT == val.kind() 254 | 255 | val = ctx.compile('"hello"') 256 | assert cue.Kind.STRING == val.kind() 257 | 258 | val = ctx.compile("'world'") 259 | assert cue.Kind.BYTES == val.kind() 260 | 261 | val = ctx.compile("{ x: 42 }") 262 | assert cue.Kind.STRUCT == val.kind() 263 | 264 | val = ctx.compile('[ 1, "two", { x: 3 } ]') 265 | assert cue.Kind.LIST == val.kind() 266 | 267 | def test_incomplete_kind(): 268 | ctx = cue.Context() 269 | 270 | val = ctx.bottom() 271 | assert cue.Kind.BOTTOM == val.incomplete_kind() 272 | 273 | val = ctx.compile("bool") 274 | assert cue.Kind.BOOL == val.incomplete_kind() 275 | 276 | val = ctx.compile("int") 277 | assert cue.Kind.INT == val.incomplete_kind() 278 | 279 | val = ctx.compile("float") 280 | assert cue.Kind.FLOAT == val.incomplete_kind() 281 | 282 | val = ctx.compile('"string"') 283 | assert cue.Kind.STRING == val.incomplete_kind() 284 | 285 | val = ctx.compile("bytes") 286 | assert cue.Kind.BYTES == val.incomplete_kind() 287 | 288 | val = ctx.compile("{ x: int }") 289 | assert cue.Kind.STRUCT == val.incomplete_kind() 290 | 291 | val = ctx.compile('[int, float, string]') 292 | assert cue.Kind.LIST == val.incomplete_kind() 293 | 294 | val = ctx.top() 295 | assert cue.Kind.TOP == val.incomplete_kind() 296 | 297 | def test_error(): 298 | ctx = cue.Context() 299 | 300 | a = ctx.compile("int") 301 | b = ctx.compile("42") 302 | c = ctx.compile("true") 303 | 304 | v = a.unify(b) 305 | assert isinstance(v.error(), cue.Ok) 306 | 307 | err = b.unify(c).error() 308 | assert isinstance(err, cue.Err) and err.err == "conflicting values 42 and true (mismatched types int and bool)" 309 | --------------------------------------------------------------------------------