Showing preview only (2,980K chars total). Download the full file or copy to clipboard to get everything.
Repository: simonw/datasette
Branch: main
Commit: 4fcf474088fa
Files: 268
Total size: 2.8 MB
Directory structure:
gitextract_10flwja5/
├── .coveragerc
├── .dockerignore
├── .git-blame-ignore-revs
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── deploy-branch-preview.yml
│ ├── deploy-latest.yml
│ ├── documentation-links.yml
│ ├── prettier.yml
│ ├── publish.yml
│ ├── push_docker_tag.yml
│ ├── spellcheck.yml
│ ├── stable-docs.yml
│ ├── test-coverage.yml
│ ├── test-pyodide.yml
│ ├── test-sqlite-support.yml
│ ├── test.yml
│ ├── tmate-mac.yml
│ └── tmate.yml
├── .gitignore
├── .isort.cfg
├── .prettierrc
├── .readthedocs.yaml
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── Justfile
├── LICENSE
├── MANIFEST.in
├── README.md
├── codecov.yml
├── datasette/
│ ├── __init__.py
│ ├── __main__.py
│ ├── actor_auth_cookie.py
│ ├── app.py
│ ├── blob_renderer.py
│ ├── cli.py
│ ├── column_types.py
│ ├── database.py
│ ├── default_actions.py
│ ├── default_column_types.py
│ ├── default_magic_parameters.py
│ ├── default_menu_links.py
│ ├── default_permissions/
│ │ ├── __init__.py
│ │ ├── config.py
│ │ ├── defaults.py
│ │ ├── helpers.py
│ │ ├── restrictions.py
│ │ ├── root.py
│ │ └── tokens.py
│ ├── events.py
│ ├── facets.py
│ ├── filters.py
│ ├── forbidden.py
│ ├── handle_exception.py
│ ├── hookspecs.py
│ ├── inspect.py
│ ├── permissions.py
│ ├── plugins.py
│ ├── publish/
│ │ ├── __init__.py
│ │ ├── cloudrun.py
│ │ ├── common.py
│ │ └── heroku.py
│ ├── renderer.py
│ ├── resources.py
│ ├── sql_functions.py
│ ├── static/
│ │ ├── app.css
│ │ ├── cm-editor-6.0.1.bundle.js
│ │ ├── cm-editor-6.0.1.js
│ │ ├── column-chooser.js
│ │ ├── datasette-manager.js
│ │ ├── json-format-highlight-1.0.1.js
│ │ ├── mobile-column-actions.js
│ │ ├── navigation-search.js
│ │ └── table.js
│ ├── templates/
│ │ ├── _action_menu.html
│ │ ├── _close_open_menus.html
│ │ ├── _codemirror.html
│ │ ├── _codemirror_foot.html
│ │ ├── _crumbs.html
│ │ ├── _debug_common_functions.html
│ │ ├── _description_source_license.html
│ │ ├── _facet_results.html
│ │ ├── _footer.html
│ │ ├── _permission_ui_styles.html
│ │ ├── _permissions_debug_tabs.html
│ │ ├── _suggested_facets.html
│ │ ├── _table.html
│ │ ├── allow_debug.html
│ │ ├── api_explorer.html
│ │ ├── base.html
│ │ ├── create_token.html
│ │ ├── csrf_error.html
│ │ ├── database.html
│ │ ├── debug_actions.html
│ │ ├── debug_allowed.html
│ │ ├── debug_check.html
│ │ ├── debug_permissions_playground.html
│ │ ├── debug_rules.html
│ │ ├── error.html
│ │ ├── index.html
│ │ ├── logout.html
│ │ ├── messages_debug.html
│ │ ├── patterns.html
│ │ ├── query.html
│ │ ├── row.html
│ │ ├── schema.html
│ │ ├── show_json.html
│ │ └── table.html
│ ├── tokens.py
│ ├── tracer.py
│ ├── url_builder.py
│ ├── utils/
│ │ ├── __init__.py
│ │ ├── actions_sql.py
│ │ ├── asgi.py
│ │ ├── baseconv.py
│ │ ├── check_callable.py
│ │ ├── internal_db.py
│ │ ├── multipart.py
│ │ ├── permissions.py
│ │ ├── shutil_backport.py
│ │ ├── sqlite.py
│ │ └── testing.py
│ ├── version.py
│ └── views/
│ ├── __init__.py
│ ├── base.py
│ ├── database.py
│ ├── index.py
│ ├── row.py
│ ├── special.py
│ └── table.py
├── demos/
│ ├── apache-proxy/
│ │ ├── 000-default.conf
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── deploy-to-cloud-run.sh
│ │ └── fly.toml
│ └── plugins/
│ ├── example_js_manager_plugins.py
│ └── static/
│ └── table-example-plugins.js
├── docs/
│ ├── .gitignore
│ ├── Makefile
│ ├── _static/
│ │ ├── css/
│ │ │ └── custom.css
│ │ └── js/
│ │ └── custom.js
│ ├── _templates/
│ │ ├── base.html
│ │ └── sidebar/
│ │ ├── brand.html
│ │ └── navigation.html
│ ├── authentication.rst
│ ├── auto-build.sh
│ ├── binary_data.rst
│ ├── changelog.rst
│ ├── cli-reference.rst
│ ├── codespell-ignore-words.txt
│ ├── conf.py
│ ├── configuration.rst
│ ├── contributing.rst
│ ├── csv_export.rst
│ ├── custom_templates.rst
│ ├── deploying.rst
│ ├── ecosystem.rst
│ ├── events.md
│ ├── facets.rst
│ ├── full_text_search.rst
│ ├── getting_started.rst
│ ├── index.rst
│ ├── installation.rst
│ ├── internals.rst
│ ├── introspection.rst
│ ├── javascript_plugins.rst
│ ├── json_api.rst
│ ├── metadata.rst
│ ├── metadata_doc.py
│ ├── pages.rst
│ ├── performance.rst
│ ├── plugin_hooks.rst
│ ├── plugins.rst
│ ├── publish.rst
│ ├── settings.rst
│ ├── spatialite.rst
│ ├── sql_queries.rst
│ ├── testing_plugins.rst
│ ├── upgrade-1.0a20.md
│ ├── upgrade_guide.md
│ └── writing_plugins.rst
├── package.json
├── pyproject.toml
├── pytest.ini
├── ruff.toml
├── setup.cfg
├── test-in-pyodide-with-shot-scraper.sh
└── tests/
├── __init__.py
├── build_small_spatialite_db.py
├── conftest.py
├── ext.c
├── fixtures.py
├── plugins/
│ ├── messages_output_renderer.py
│ ├── my_plugin.py
│ ├── my_plugin_2.py
│ ├── register_output_renderer.py
│ ├── sleep_sql_function.py
│ └── view_name.py
├── test-datasette-load-plugins.sh
├── test_actions_sql.py
├── test_actor_restriction_bug.py
├── test_allowed_resources.py
├── test_api.py
├── test_api_write.py
├── test_auth.py
├── test_base_view.py
├── test_canned_queries.py
├── test_cli.py
├── test_cli_serve_get.py
├── test_cli_serve_server.py
├── test_column_types.py
├── test_config_dir.py
├── test_config_permission_rules.py
├── test_crossdb.py
├── test_csv.py
├── test_custom_pages.py
├── test_datasette_https_server.sh
├── test_default_deny.py
├── test_docs.py
├── test_docs_plugins.py
├── test_facets.py
├── test_filters.py
├── test_html.py
├── test_internal_db.py
├── test_internals_database.py
├── test_internals_datasette.py
├── test_internals_datasette_client.py
├── test_internals_request.py
├── test_internals_response.py
├── test_internals_urls.py
├── test_label_column_for_table.py
├── test_load_extensions.py
├── test_messages.py
├── test_multipart.py
├── test_package.py
├── test_permission_endpoints.py
├── test_permissions.py
├── test_plugins.py
├── test_publish_cloudrun.py
├── test_publish_heroku.py
├── test_restriction_sql.py
├── test_routes.py
├── test_schema_endpoints.py
├── test_search_tables.py
├── test_spatialite.py
├── test_table_api.py
├── test_table_html.py
├── test_templates/
│ ├── _table.html
│ ├── pages/
│ │ ├── 202.html
│ │ ├── about.html
│ │ ├── atom.html
│ │ ├── headers.html
│ │ ├── nested/
│ │ │ └── nest.html
│ │ ├── redirect.html
│ │ ├── redirect2.html
│ │ ├── request.html
│ │ ├── route_{name}.html
│ │ ├── topic_{topic}/
│ │ │ └── {slug}.html
│ │ └── topic_{topic}.html
│ └── show_json.html
├── test_token_handler.py
├── test_tracer.py
├── test_utils.py
├── test_utils_check_callable.py
├── test_utils_permissions.py
├── test_write_wrapper.py
└── utils.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .coveragerc
================================================
[run]
omit = datasette/_version.py, datasette/utils/shutil_backport.py
================================================
FILE: .dockerignore
================================================
.DS_Store
.cache
.eggs
.gitignore
.ipynb_checkpoints
build
*.spec
*.egg-info
dist
scratchpad
venv
*.db
*.sqlite
================================================
FILE: .git-blame-ignore-revs
================================================
# Applying Black
35d6ee2790e41e96f243c1ff58be0c9c0519a8ce
368638555160fb9ac78f462d0f79b1394163fa30
2b344f6a34d2adaa305996a1a580ece06397f6e4
================================================
FILE: .gitattributes
================================================
datasette/static/codemirror-* linguist-vendored
================================================
FILE: .github/FUNDING.yml
================================================
github: [simonw]
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
time: "13:00"
groups:
python-packages:
patterns:
- "*"
================================================
FILE: .github/workflows/deploy-branch-preview.yml
================================================
name: Deploy a Datasette branch preview to Vercel
on:
workflow_dispatch:
inputs:
branch:
description: "Branch to deploy"
required: true
type: string
jobs:
deploy-branch-preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.11
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install datasette-publish-vercel
- name: Deploy the preview
env:
VERCEL_TOKEN: ${{ secrets.BRANCH_PREVIEW_VERCEL_TOKEN }}
run: |
export BRANCH="${{ github.event.inputs.branch }}"
wget https://latest.datasette.io/fixtures.db
datasette publish vercel fixtures.db \
--branch $BRANCH \
--project "datasette-preview-$BRANCH" \
--token $VERCEL_TOKEN \
--scope datasette \
--about "Preview of $BRANCH" \
--about_url "https://github.com/simonw/datasette/tree/$BRANCH"
================================================
FILE: .github/workflows/deploy-latest.yml
================================================
name: Deploy latest.datasette.io
on:
workflow_dispatch:
push:
branches:
- main
# - 1.0-dev
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out datasette
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
cache: pip
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install . --group dev
python -m pip install sphinx-to-sqlite==0.1a1
- name: Run tests
if: ${{ github.ref == 'refs/heads/main' }}
run: |
pytest -n auto -m "not serial"
pytest -m "serial"
- name: Build fixtures.db and other files needed to deploy the demo
run: |-
python tests/fixtures.py \
fixtures.db \
fixtures-config.json \
fixtures-metadata.json \
plugins \
--extra-db-filename extra_database.db
- name: Build docs.db
if: ${{ github.ref == 'refs/heads/main' }}
run: |-
cd docs
DISABLE_SPHINX_INLINE_TABS=1 sphinx-build -b xml . _build
sphinx-to-sqlite ../docs.db _build
cd ..
- name: Set up the alternate-route demo
run: |
echo '
from datasette import hookimpl
@hookimpl
def startup(datasette):
db = datasette.get_database("fixtures2")
db.route = "alternative-route"
' > plugins/alternative_route.py
cp fixtures.db fixtures2.db
- name: And the counters writable canned query demo
run: |
cat > plugins/counters.py <<EOF
from datasette import hookimpl
@hookimpl
def startup(datasette):
db = datasette.add_memory_database("counters")
async def inner():
await db.execute_write("create table if not exists counters (name text primary key, value integer)")
await db.execute_write("insert or ignore into counters (name, value) values ('counter_a', 0)")
await db.execute_write("insert or ignore into counters (name, value) values ('counter_b', 0)")
await db.execute_write("insert or ignore into counters (name, value) values ('counter_c', 0)")
return inner
@hookimpl
def canned_queries(database):
if database == "counters":
queries = {}
for name in ("counter_a", "counter_b", "counter_c"):
queries["increment_{}".format(name)] = {
"sql": "update counters set value = value + 1 where name = '{}'".format(name),
"on_success_message_sql": "select 'Counter {name} incremented to ' || value from counters where name = '{name}'".format(name=name),
"write": True,
}
queries["decrement_{}".format(name)] = {
"sql": "update counters set value = value - 1 where name = '{}'".format(name),
"on_success_message_sql": "select 'Counter {name} decremented to ' || value from counters where name = '{name}'".format(name=name),
"write": True,
}
return queries
EOF
# - name: Make some modifications to metadata.json
# run: |
# cat fixtures.json | \
# jq '.databases |= . + {"ephemeral": {"allow": {"id": "*"}}}' | \
# jq '.plugins |= . + {"datasette-ephemeral-tables": {"table_ttl": 900}}' \
# > metadata.json
# cat metadata.json
- id: auth
name: Authenticate to Google Cloud
uses: google-github-actions/auth@v3
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v3
- name: Deploy to Cloud Run
env:
LATEST_DATASETTE_SECRET: ${{ secrets.LATEST_DATASETTE_SECRET }}
run: |-
gcloud config set run/region us-central1
gcloud config set project datasette-222320
export SUFFIX="-${GITHUB_REF#refs/heads/}"
export SUFFIX=${SUFFIX#-main}
# Replace 1.0 with one-dot-zero in SUFFIX
export SUFFIX=${SUFFIX//1.0/one-dot-zero}
datasette publish cloudrun fixtures.db fixtures2.db extra_database.db \
-m fixtures-metadata.json \
--plugins-dir=plugins \
--branch=$GITHUB_SHA \
--version-note=$GITHUB_SHA \
--extra-options="--setting template_debug 1 --setting trace_debug 1 --crossdb" \
--install 'datasette-ephemeral-tables>=0.2.2' \
--service "datasette-latest$SUFFIX" \
--secret $LATEST_DATASETTE_SECRET
- name: Deploy to docs as well (only for main)
if: ${{ github.ref == 'refs/heads/main' }}
run: |-
# Deploy docs.db to a different service
datasette publish cloudrun docs.db \
--branch=$GITHUB_SHA \
--version-note=$GITHUB_SHA \
--extra-options="--setting template_debug 1" \
--service=datasette-docs-latest
================================================
FILE: .github/workflows/documentation-links.yml
================================================
name: Read the Docs Pull Request Preview
on:
pull_request_target:
types:
- opened
permissions:
pull-requests: write
jobs:
documentation-links:
runs-on: ubuntu-latest
steps:
- uses: readthedocs/actions/preview@v1
with:
project-slug: "datasette"
================================================
FILE: .github/workflows/prettier.yml
================================================
name: Check JavaScript for conformance with Prettier
on: [push]
permissions:
contents: read
jobs:
prettier:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v4
- uses: actions/cache@v4
name: Configure npm caching
with:
path: ~/.npm
key: ${{ runner.OS }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.OS }}-npm-
- name: Install dependencies
run: npm ci
- name: Run prettier
run: |-
npm run prettier -- --check
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish Python Package
on:
release:
types: [created]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml
- name: Install dependencies
run: |
pip install . --group dev
- name: Run tests
run: |
pytest
deploy:
runs-on: ubuntu-latest
needs: [test]
environment: release
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.13'
cache: pip
cache-dependency-path: pyproject.toml
- name: Install dependencies
run: |
pip install setuptools wheel build
- name: Build
run: |
python -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@release/v1
deploy_static_docs:
runs-on: ubuntu-latest
needs: [deploy]
if: "!github.event.release.prerelease"
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.10'
cache: pip
cache-dependency-path: pyproject.toml
- name: Install dependencies
run: |
python -m pip install . --group dev
python -m pip install sphinx-to-sqlite==0.1a1
- name: Build docs.db
run: |-
cd docs
DISABLE_SPHINX_INLINE_TABS=1 sphinx-build -b xml . _build
sphinx-to-sqlite ../docs.db _build
cd ..
- id: auth
name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v3
- name: Deploy stable-docs.datasette.io to Cloud Run
run: |-
gcloud config set run/region us-central1
gcloud config set project datasette-222320
datasette publish cloudrun docs.db \
--service=datasette-docs-stable
deploy_docker:
runs-on: ubuntu-latest
needs: [deploy]
if: "!github.event.release.prerelease"
steps:
- uses: actions/checkout@v4
- name: Build and push to Docker Hub
env:
DOCKER_USER: ${{ secrets.DOCKER_USER }}
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
run: |-
sleep 60 # Give PyPI time to make the new release available
docker login -u $DOCKER_USER -p $DOCKER_PASS
export REPO=datasetteproject/datasette
docker build -f Dockerfile \
-t $REPO:${GITHUB_REF#refs/tags/} \
--build-arg VERSION=${GITHUB_REF#refs/tags/} .
docker tag $REPO:${GITHUB_REF#refs/tags/} $REPO:latest
docker push $REPO:${GITHUB_REF#refs/tags/}
docker push $REPO:latest
================================================
FILE: .github/workflows/push_docker_tag.yml
================================================
name: Push specific Docker tag
on:
workflow_dispatch:
inputs:
version_tag:
description: Tag to build and push
permissions:
contents: read
jobs:
deploy_docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build and push to Docker Hub
env:
DOCKER_USER: ${{ secrets.DOCKER_USER }}
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
VERSION_TAG: ${{ github.event.inputs.version_tag }}
run: |-
docker login -u $DOCKER_USER -p $DOCKER_PASS
export REPO=datasetteproject/datasette
docker build -f Dockerfile \
-t $REPO:${VERSION_TAG} \
--build-arg VERSION=${VERSION_TAG} .
docker push $REPO:${VERSION_TAG}
================================================
FILE: .github/workflows/spellcheck.yml
================================================
name: Check spelling in documentation
on: [push, pull_request]
permissions:
contents: read
jobs:
spellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
- name: Install dependencies
run: |
pip install . --group dev
- name: Check spelling
run: |
codespell README.md --ignore-words docs/codespell-ignore-words.txt
codespell docs/*.rst --ignore-words docs/codespell-ignore-words.txt
codespell datasette -S datasette/static --ignore-words docs/codespell-ignore-words.txt
codespell tests --ignore-words docs/codespell-ignore-words.txt
================================================
FILE: .github/workflows/stable-docs.yml
================================================
name: Update Stable Docs
on:
release:
types: [published]
push:
branches:
- main
permissions:
contents: write
jobs:
update_stable_docs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0 # We need all commits to find docs/ changes
- name: Set up Git user
run: |
git config user.name "Automated"
git config user.email "actions@users.noreply.github.com"
- name: Create stable branch if it does not yet exist
run: |
if ! git ls-remote --heads origin stable | grep -qE '\bstable\b'; then
# Make sure we have all tags locally
git fetch --tags --quiet
# Latest tag that is just numbers and dots (optionally prefixed with 'v')
# e.g., 0.65.2 or v0.65.2 — excludes 1.0a20, 1.0-rc1, etc.
LATEST_RELEASE=$(
git tag -l --sort=-v:refname \
| grep -E '^v?[0-9]+(\.[0-9]+){1,3}$' \
| head -n1
)
git checkout -b stable
# If there are any stable releases, copy docs/ from the most recent
if [ -n "$LATEST_RELEASE" ]; then
rm -rf docs/
git checkout "$LATEST_RELEASE" -- docs/ || true
fi
git commit -m "Populate docs/ from $LATEST_RELEASE" || echo "No changes"
git push -u origin stable
fi
- name: Handle Release
if: github.event_name == 'release' && !github.event.release.prerelease
run: |
git fetch --all
git checkout stable
git reset --hard ${GITHUB_REF#refs/tags/}
git push origin stable --force
- name: Handle Commit to Main
if: contains(github.event.head_commit.message, '!stable-docs')
run: |
git fetch origin
git checkout -b stable origin/stable
# Get the list of modified files in docs/ from the current commit
FILES=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} -- docs/)
# Check if the list of files is non-empty
if [[ -n "$FILES" ]]; then
# Checkout those files to the stable branch to over-write with their contents
for FILE in $FILES; do
git checkout ${{ github.sha }} -- $FILE
done
git add docs/
git commit -m "Doc changes from ${{ github.sha }}"
git push origin stable
else
echo "No changes to docs/ in this commit."
exit 0
fi
================================================
FILE: .github/workflows/test-coverage.yml
================================================
name: Calculate test coverage
on:
push:
branches:
- main
pull_request:
branches:
- main
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out datasette
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install . --group dev
python -m pip install pytest-cov
- name: Run tests
run: |-
ls -lah
cat .coveragerc
pytest -m "not serial" --cov=datasette --cov-config=.coveragerc --cov-report xml:coverage.xml --cov-report term -x
ls -lah
- name: Upload coverage report
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: coverage.xml
================================================
FILE: .github/workflows/test-pyodide.yml
================================================
name: Test in Pyodide with shot-scraper
on:
push:
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v6
with:
python-version: "3.10"
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright/
key: ${{ runner.os }}-browsers
- name: Install Playwright dependencies
run: |
pip install shot-scraper build
shot-scraper install
- name: Run test
run: |
./test-in-pyodide-with-shot-scraper.sh
================================================
FILE: .github/workflows/test-sqlite-support.yml
================================================
name: Test SQLite versions
on: [push, pull_request]
permissions:
contents: read
jobs:
test:
runs-on: ${{ matrix.platform }}
continue-on-error: true
strategy:
matrix:
platform: [ubuntu-latest]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
sqlite-version: [
#"3", # latest version
"3.46",
#"3.45",
#"3.27",
#"3.26",
"3.25",
#"3.25.3", # 2018-09-25, window functions breaks test_upsert for some reason on 3.10, skip for now
#"3.24", # 2018-06-04, added UPSERT support
#"3.23.1" # 2018-04-10, before UPSERT
]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: pip
cache-dependency-path: pyproject.toml
- name: Set up SQLite ${{ matrix.sqlite-version }}
uses: asg017/sqlite-versions@71ea0de37ae739c33e447af91ba71dda8fcf22e6
with:
version: ${{ matrix.sqlite-version }}
cflags: "-DSQLITE_ENABLE_DESERIALIZE -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_JSON1"
- run: python3 -c "import sqlite3; print(sqlite3.sqlite_version)"
- run: echo $LD_LIBRARY_PATH
- name: Build extension for --load-extension test
run: |-
(cd tests && gcc ext.c -fPIC -shared -o ext.so)
- name: Install dependencies
run: |
pip install . --group dev
pip freeze
- name: Run tests
run: |
pytest -n auto -m "not serial"
pytest -m "serial"
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on: [push, pull_request]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: pip
cache-dependency-path: pyproject.toml
- name: Build extension for --load-extension test
run: |-
(cd tests && gcc ext.c -fPIC -shared -o ext.so)
- name: Install dependencies
run: |
pip install . --group dev
pip freeze
- name: Run tests
run: |
pytest -n auto -m "not serial"
pytest -m "serial"
# And the test that exceeds a localhost HTTPS server
tests/test_datasette_https_server.sh
- name: Black
run: |
black --version
black --check .
- name: Ruff
run: ruff check datasette tests
- name: Check if cog needs to be run
run: |
cog --check docs/*.rst
- name: Check if blacken-docs needs to be run
run: |
# This fails on syntax errors, or a diff was applied
blacken-docs -l 60 docs/*.rst
- name: Test DATASETTE_LOAD_PLUGINS
run: |
pip install datasette-init datasette-json-html
tests/test-datasette-load-plugins.sh
================================================
FILE: .github/workflows/tmate-mac.yml
================================================
name: tmate session mac
on:
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
================================================
FILE: .github/workflows/tmate.yml
================================================
name: tmate session
on:
workflow_dispatch:
permissions:
contents: read
models: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
build-metadata.json
datasets.json
scratchpad
.vscode
uv.lock
data.db
# test databases
*.db
# We don't use Pipfile, so ignore them
Pipfile
Pipfile.lock
fixtures.db
*test.db
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# dotenv
.env
# virtualenv
.venv
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# macOS files
.DS_Store
node_modules
.*.swp
# In case someone compiled tests/ext.c for test_load_extensions, don't
# include it in source control.
tests/*.dylib
tests/*.so
tests/*.dll
.idea
================================================
FILE: .isort.cfg
================================================
[settings]
multi_line_output=3
================================================
FILE: .prettierrc
================================================
{
"tabWidth": 2,
"useTabs": false
}
================================================
FILE: .readthedocs.yaml
================================================
version: 2
sphinx:
configuration: docs/conf.py
build:
os: ubuntu-24.04
tools:
python: "3.13"
jobs:
install:
- pip install --upgrade pip
- pip install . --group dev
formats:
- pdf
- epub
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
`swillison+datasette-code-of-conduct@gmail.com`.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: Dockerfile
================================================
FROM python:3.11.0-slim-bullseye as build
# Version of Datasette to install, e.g. 0.55
# docker build . -t datasette --build-arg VERSION=0.55
ARG VERSION
RUN apt-get update && \
apt-get install -y --no-install-recommends libsqlite3-mod-spatialite && \
apt clean && \
rm -rf /var/lib/apt && \
rm -rf /var/lib/dpkg/info/*
RUN pip install https://github.com/simonw/datasette/archive/refs/tags/${VERSION}.zip && \
find /usr/local/lib -name '__pycache__' | xargs rm -r && \
rm -rf /root/.cache/pip
EXPOSE 8001
CMD ["datasette"]
================================================
FILE: Justfile
================================================
export DATASETTE_SECRET := "not_a_secret"
# Run tests and linters
@default: test lint
# Setup project
@init:
uv sync
# Run pytest with supplied options
@test *options: init
uv run pytest -n auto {{options}}
@codespell:
uv run codespell README.md --ignore-words docs/codespell-ignore-words.txt
uv run codespell docs/*.rst --ignore-words docs/codespell-ignore-words.txt
uv run codespell datasette -S datasette/static --ignore-words docs/codespell-ignore-words.txt
uv run codespell tests --ignore-words docs/codespell-ignore-words.txt
# Run linters: black, ruff, cog
@lint: codespell
uv run black datasette tests --check
uv run ruff check datasette tests
uv run cog --check README.md docs/*.rst
# Apply ruff fixes
@fix:
uv run ruff check --fix datasette tests
# Rebuild docs with cog
@cog:
uv run cog -r README.md docs/*.rst
# Serve live docs on localhost:8000
@docs: cog blacken-docs
uv run make -C docs livehtml
# Build docs as static HTML
@docs-build: cog blacken-docs
rm -rf docs/_build && cd docs && uv run make html
# Apply Black
@black:
uv run black datasette tests
# Apply blacken-docs
@blacken-docs:
uv run blacken-docs -l 60 docs/*.rst
# Apply prettier
@prettier:
npm run fix
# Format code with both black and prettier
@format: black prettier blacken-docs
@serve *options:
uv run sqlite-utils create-database data.db
uv run sqlite-utils create-table data.db docs id integer title text --pk id --ignore
uv run python -m datasette data.db --root --reload {{options}}
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: MANIFEST.in
================================================
recursive-include datasette/static *
recursive-include datasette/templates *
include versioneer.py
include datasette/_version.py
include LICENSE
================================================
FILE: README.md
================================================
<img src="https://datasette.io/static/datasette-logo.svg" alt="Datasette">
[](https://pypi.org/project/datasette/)
[](https://docs.datasette.io/en/latest/changelog.html)
[](https://pypi.org/project/datasette/)
[](https://github.com/simonw/datasette/actions?query=workflow%3ATest)
[](https://docs.datasette.io/en/latest/?badge=latest)
[](https://github.com/simonw/datasette/blob/main/LICENSE)
[](https://hub.docker.com/r/datasetteproject/datasette)
[](https://datasette.io/discord)
*An open source multi-tool for exploring and publishing data*
Datasette is a tool for exploring and publishing data. It helps people take data of any shape or size and publish that as an interactive, explorable website and accompanying API.
Datasette is aimed at data journalists, museum curators, archivists, local governments, scientists, researchers and anyone else who has data that they wish to share with the world.
[Explore a demo](https://datasette.io/global-power-plants/global-power-plants), watch [a video about the project](https://simonwillison.net/2021/Feb/7/video/) or try it out [on GitHub Codespaces](https://github.com/datasette/datasette-studio).
* [datasette.io](https://datasette.io/) is the official project website
* Latest [Datasette News](https://datasette.io/news)
* Comprehensive documentation: https://docs.datasette.io/
* Examples: https://datasette.io/examples
* Live demo of current `main` branch: https://latest.datasette.io/
* Questions, feedback or want to talk about the project? Join our [Discord](https://datasette.io/discord)
Want to stay up-to-date with the project? Subscribe to the [Datasette newsletter](https://datasette.substack.com/) for tips, tricks and news on what's new in the Datasette ecosystem.
## Installation
If you are on a Mac, [Homebrew](https://brew.sh/) is the easiest way to install Datasette:
brew install datasette
You can also install it using `pip` or `pipx`:
pip install datasette
Datasette requires Python 3.8 or higher. We also have [detailed installation instructions](https://docs.datasette.io/en/stable/installation.html) covering other options such as Docker.
## Basic usage
datasette serve path/to/database.db
This will start a web server on port 8001 - visit http://localhost:8001/ to access the web interface.
`serve` is the default subcommand, you can omit it if you like.
Use Chrome on OS X? You can run datasette against your browser history like so:
datasette ~/Library/Application\ Support/Google/Chrome/Default/History --nolock
Now visiting http://localhost:8001/History/downloads will show you a web interface to browse your downloads data:

## metadata.json
If you want to include licensing and source information in the generated datasette website you can do so using a JSON file that looks something like this:
{
"title": "Five Thirty Eight",
"license": "CC Attribution 4.0 License",
"license_url": "http://creativecommons.org/licenses/by/4.0/",
"source": "fivethirtyeight/data on GitHub",
"source_url": "https://github.com/fivethirtyeight/data"
}
Save this in `metadata.json` and run Datasette like so:
datasette serve fivethirtyeight.db -m metadata.json
The license and source information will be displayed on the index page and in the footer. They will also be included in the JSON produced by the API.
## datasette publish
If you have [Heroku](https://heroku.com/) or [Google Cloud Run](https://cloud.google.com/run/) configured, Datasette can deploy one or more SQLite databases to the internet with a single command:
datasette publish heroku database.db
Or:
datasette publish cloudrun database.db
This will create a docker image containing both the datasette application and the specified SQLite database files. It will then deploy that image to Heroku or Cloud Run and give you a URL to access the resulting website and API.
See [Publishing data](https://docs.datasette.io/en/stable/publish.html) in the documentation for more details.
## Datasette Lite
[Datasette Lite](https://lite.datasette.io/) is Datasette packaged using WebAssembly so that it runs entirely in your browser, no Python web application server required. Read more about that in the [Datasette Lite documentation](https://github.com/simonw/datasette-lite/blob/main/README.md).
================================================
FILE: codecov.yml
================================================
coverage:
status:
project:
default:
informational: true
patch:
default:
informational: true
================================================
FILE: datasette/__init__.py
================================================
from datasette.permissions import Permission # noqa
from datasette.version import __version_info__, __version__ # noqa
from datasette.events import Event # noqa
from datasette.tokens import TokenHandler, TokenRestrictions # noqa
from datasette.utils.asgi import Forbidden, NotFound, Request, Response # noqa
from datasette.utils import actor_matches_allow # noqa
from datasette.views import Context # noqa
from .hookspecs import hookimpl # noqa
from .hookspecs import hookspec # noqa
================================================
FILE: datasette/__main__.py
================================================
from datasette.cli import cli
if __name__ == "__main__":
cli()
================================================
FILE: datasette/actor_auth_cookie.py
================================================
from datasette import hookimpl
from itsdangerous import BadSignature
from datasette.utils import baseconv
import time
@hookimpl
def actor_from_request(datasette, request):
if "ds_actor" not in request.cookies:
return None
try:
decoded = datasette.unsign(request.cookies["ds_actor"], "actor")
# If it has "e" and "a" keys process the "e" expiry
if not isinstance(decoded, dict) or "a" not in decoded:
return None
expires_at = decoded.get("e")
if expires_at:
timestamp = int(baseconv.base62.decode(expires_at))
if time.time() > timestamp:
return None
return decoded["a"]
except BadSignature:
return None
================================================
FILE: datasette/app.py
================================================
from __future__ import annotations
from asgi_csrf import Errors
import asyncio
import contextvars
from typing import TYPE_CHECKING, Any, Dict, Iterable, List
if TYPE_CHECKING:
from datasette.permissions import Resource
from datasette.tokens import TokenRestrictions
import asgi_csrf
import collections
import dataclasses
import datetime
import functools
import glob
import hashlib
import httpx
import importlib.metadata
import inspect
from itsdangerous import BadSignature
import json
import os
import re
import secrets
import sys
import threading
import time
import types
import urllib.parse
from concurrent import futures
from pathlib import Path
from markupsafe import Markup, escape
from itsdangerous import URLSafeSerializer
from jinja2 import (
ChoiceLoader,
Environment,
FileSystemLoader,
PrefixLoader,
)
from jinja2.environment import Template
from jinja2.exceptions import TemplateNotFound
from .events import Event
from .column_types import SQLiteType
from .views import Context
from .views.database import database_download, DatabaseView, TableCreateView, QueryView
from .views.index import IndexView
from .views.special import (
JsonDataView,
PatternPortfolioView,
AuthTokenView,
ApiExplorerView,
CreateTokenView,
LogoutView,
AllowDebugView,
PermissionsDebugView,
MessagesDebugView,
AllowedResourcesView,
PermissionRulesView,
PermissionCheckView,
TablesView,
InstanceSchemaView,
DatabaseSchemaView,
TableSchemaView,
)
from .views.table import (
TableInsertView,
TableUpsertView,
TableSetColumnTypeView,
TableDropView,
table_view,
)
from .views.row import RowView, RowDeleteView, RowUpdateView
from .renderer import json_renderer
from .url_builder import Urls
from .database import Database, QueryInterrupted
from .utils import (
PaginatedResources,
PrefixedUrlString,
SPATIALITE_FUNCTIONS,
StartupError,
async_call_with_supported_arguments,
await_me_maybe,
baseconv,
call_with_supported_arguments,
detect_json1,
display_actor,
escape_css_string,
escape_sqlite,
find_spatialite,
format_bytes,
module_from_path,
move_plugins_and_allow,
move_table_config,
parse_metadata,
resolve_env_secrets,
resolve_routes,
tilde_decode,
tilde_encode,
to_css_class,
urlsafe_components,
redact_keys,
row_sql_params_pks,
)
from .utils.asgi import (
AsgiLifespan,
Forbidden,
NotFound,
DatabaseNotFound,
TableNotFound,
RowNotFound,
Request,
Response,
AsgiRunOnFirstRequest,
asgi_static,
asgi_send,
asgi_send_file,
asgi_send_redirect,
)
from .utils.internal_db import init_internal_db, populate_schema_tables
from .utils.sqlite import (
sqlite3,
using_pysqlite3,
)
from .tracer import AsgiTracer
from .plugins import pm, DEFAULT_PLUGINS, get_plugins
from .version import __version__
from .resources import DatabaseResource, TableResource
app_root = Path(__file__).parent.parent
# Context variable to track when code is executing within a datasette.client request
_in_datasette_client = contextvars.ContextVar("in_datasette_client", default=False)
class _DatasetteClientContext:
"""Context manager to mark code as executing within a datasette.client request."""
def __enter__(self):
self.token = _in_datasette_client.set(True)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
_in_datasette_client.reset(self.token)
return False
@dataclasses.dataclass
class PermissionCheck:
"""Represents a logged permission check for debugging purposes."""
when: str
actor: Dict[str, Any] | None
action: str
parent: str | None
child: str | None
result: bool
# https://github.com/simonw/datasette/issues/283#issuecomment-781591015
SQLITE_LIMIT_ATTACHED = 10
INTERNAL_DB_NAME = "__INTERNAL__"
Setting = collections.namedtuple("Setting", ("name", "default", "help"))
SETTINGS = (
Setting("default_page_size", 100, "Default page size for the table view"),
Setting(
"max_returned_rows",
1000,
"Maximum rows that can be returned from a table or custom query",
),
Setting(
"max_insert_rows",
100,
"Maximum rows that can be inserted at a time using the bulk insert API",
),
Setting(
"num_sql_threads",
3,
"Number of threads in the thread pool for executing SQLite queries",
),
Setting("sql_time_limit_ms", 1000, "Time limit for a SQL query in milliseconds"),
Setting(
"default_facet_size", 30, "Number of values to return for requested facets"
),
Setting("facet_time_limit_ms", 200, "Time limit for calculating a requested facet"),
Setting(
"facet_suggest_time_limit_ms",
50,
"Time limit for calculating a suggested facet",
),
Setting(
"allow_facet",
True,
"Allow users to specify columns to facet using ?_facet= parameter",
),
Setting(
"allow_download",
True,
"Allow users to download the original SQLite database files",
),
Setting(
"allow_signed_tokens",
True,
"Allow users to create and use signed API tokens",
),
Setting(
"default_allow_sql",
True,
"Allow anyone to run arbitrary SQL queries",
),
Setting(
"max_signed_tokens_ttl",
0,
"Maximum allowed expiry time for signed API tokens",
),
Setting("suggest_facets", True, "Calculate and display suggested facets"),
Setting(
"default_cache_ttl",
5,
"Default HTTP cache TTL (used in Cache-Control: max-age= header)",
),
Setting("cache_size_kb", 0, "SQLite cache size in KB (0 == use SQLite default)"),
Setting(
"allow_csv_stream",
True,
"Allow .csv?_stream=1 to download all rows (ignoring max_returned_rows)",
),
Setting(
"max_csv_mb",
100,
"Maximum size allowed for CSV export in MB - set 0 to disable this limit",
),
Setting(
"truncate_cells_html",
2048,
"Truncate cells longer than this in HTML table view - set 0 to disable",
),
Setting(
"force_https_urls",
False,
"Force URLs in API output to always use https:// protocol",
),
Setting(
"template_debug",
False,
"Allow display of template debug information with ?_context=1",
),
Setting(
"trace_debug",
False,
"Allow display of SQL trace debug information with ?_trace=1",
),
Setting("base_url", "/", "Datasette URLs should use this base path"),
)
_HASH_URLS_REMOVED = "The hash_urls setting has been removed, try the datasette-hashed-urls plugin instead"
OBSOLETE_SETTINGS = {
"hash_urls": _HASH_URLS_REMOVED,
"default_cache_ttl_hashed": _HASH_URLS_REMOVED,
}
DEFAULT_SETTINGS = {option.name: option.default for option in SETTINGS}
FAVICON_PATH = app_root / "datasette" / "static" / "favicon.png"
DEFAULT_NOT_SET = object()
ResourcesSQL = collections.namedtuple("ResourcesSQL", ("sql", "params"))
async def favicon(request, send):
await asgi_send_file(
send,
str(FAVICON_PATH),
content_type="image/png",
headers={"Cache-Control": "max-age=3600, immutable, public"},
)
ResolvedTable = collections.namedtuple("ResolvedTable", ("db", "table", "is_view"))
ResolvedRow = collections.namedtuple(
"ResolvedRow", ("db", "table", "sql", "params", "pks", "pk_values", "row")
)
def _to_string(value):
if isinstance(value, str):
return value
else:
return json.dumps(value, default=str)
class Datasette:
# Message constants:
INFO = 1
WARNING = 2
ERROR = 3
def __init__(
self,
files=None,
immutables=None,
cache_headers=True,
cors=False,
inspect_data=None,
config=None,
metadata=None,
sqlite_extensions=None,
template_dir=None,
plugins_dir=None,
static_mounts=None,
memory=False,
settings=None,
secret=None,
version_note=None,
config_dir=None,
pdb=False,
crossdb=False,
nolock=False,
internal=None,
default_deny=False,
):
self._startup_invoked = False
assert config_dir is None or isinstance(
config_dir, Path
), "config_dir= should be a pathlib.Path"
self.config_dir = config_dir
self.pdb = pdb
self._secret = secret or secrets.token_hex(32)
if files is not None and isinstance(files, str):
raise ValueError("files= must be a list of paths, not a string")
self.files = tuple(files or []) + tuple(immutables or [])
if config_dir:
db_files = []
for ext in ("db", "sqlite", "sqlite3"):
db_files.extend(config_dir.glob("*.{}".format(ext)))
self.files += tuple(str(f) for f in db_files)
if (
config_dir
and (config_dir / "inspect-data.json").exists()
and not inspect_data
):
inspect_data = json.loads((config_dir / "inspect-data.json").read_text())
if not immutables:
immutable_filenames = [i["file"] for i in inspect_data.values()]
immutables = [
f for f in self.files if Path(f).name in immutable_filenames
]
self.inspect_data = inspect_data
self.immutables = set(immutables or [])
self.databases = collections.OrderedDict()
self.actions = {} # .invoke_startup() will populate this
self._column_types = {} # .invoke_startup() will populate this
try:
self._refresh_schemas_lock = asyncio.Lock()
except RuntimeError as rex:
# Workaround for intermittent test failure, see:
# https://github.com/simonw/datasette/issues/1802
if "There is no current event loop in thread" in str(rex):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
self._refresh_schemas_lock = asyncio.Lock()
else:
raise
self.crossdb = crossdb
self.nolock = nolock
if memory or crossdb or not self.files:
self.add_database(
Database(self, is_mutable=False, is_memory=True), name="_memory"
)
for file in self.files:
self.add_database(
Database(self, file, is_mutable=file not in self.immutables)
)
self.internal_db_created = False
if internal is None:
self._internal_database = Database(self, memory_name=secrets.token_hex())
else:
self._internal_database = Database(self, path=internal, mode="rwc")
self._internal_database.name = INTERNAL_DB_NAME
self.cache_headers = cache_headers
self.cors = cors
config_files = []
metadata_files = []
if config_dir:
metadata_files = [
config_dir / filename
for filename in ("metadata.json", "metadata.yaml", "metadata.yml")
if (config_dir / filename).exists()
]
config_files = [
config_dir / filename
for filename in ("datasette.json", "datasette.yaml", "datasette.yml")
if (config_dir / filename).exists()
]
if config_dir and metadata_files and not metadata:
with metadata_files[0].open() as fp:
metadata = parse_metadata(fp.read())
if config_dir and config_files and not config:
with config_files[0].open() as fp:
config = parse_metadata(fp.read())
# Move any "plugins" and "allow" settings from metadata to config - updates them in place
metadata = metadata or {}
config = config or {}
metadata, config = move_plugins_and_allow(metadata, config)
# Now migrate any known table configuration settings over as well
metadata, config = move_table_config(metadata, config)
self._metadata_local = metadata or {}
self.sqlite_extensions = []
for extension in sqlite_extensions or []:
# Resolve spatialite, if requested
if extension == "spatialite":
# Could raise SpatialiteNotFound
self.sqlite_extensions.append(find_spatialite())
else:
self.sqlite_extensions.append(extension)
if config_dir and (config_dir / "templates").is_dir() and not template_dir:
template_dir = str((config_dir / "templates").resolve())
self.template_dir = template_dir
if config_dir and (config_dir / "plugins").is_dir() and not plugins_dir:
plugins_dir = str((config_dir / "plugins").resolve())
self.plugins_dir = plugins_dir
if config_dir and (config_dir / "static").is_dir() and not static_mounts:
static_mounts = [("static", str((config_dir / "static").resolve()))]
self.static_mounts = static_mounts or []
if config_dir and (config_dir / "datasette.json").exists() and not config:
config = json.loads((config_dir / "datasette.json").read_text())
config = config or {}
config_settings = config.get("settings") or {}
# Validate settings from config file
for key, value in config_settings.items():
if key not in DEFAULT_SETTINGS:
raise StartupError(f"Invalid setting '{key}' in config file")
# Validate type matches expected type from DEFAULT_SETTINGS
if value is not None: # Allow None/null values
expected_type = type(DEFAULT_SETTINGS[key])
actual_type = type(value)
if actual_type != expected_type:
raise StartupError(
f"Setting '{key}' in config file has incorrect type. "
f"Expected {expected_type.__name__}, got {actual_type.__name__}. "
f"Value: {value!r}. "
f"Hint: In YAML/JSON config files, remove quotes from boolean and integer values."
)
# Validate settings from constructor parameter
if settings:
for key, value in settings.items():
if key not in DEFAULT_SETTINGS:
raise StartupError(f"Invalid setting '{key}' in settings parameter")
if value is not None:
expected_type = type(DEFAULT_SETTINGS[key])
actual_type = type(value)
if actual_type != expected_type:
raise StartupError(
f"Setting '{key}' in settings parameter has incorrect type. "
f"Expected {expected_type.__name__}, got {actual_type.__name__}. "
f"Value: {value!r}"
)
self.config = config
# CLI settings should overwrite datasette.json settings
self._settings = dict(DEFAULT_SETTINGS, **(config_settings), **(settings or {}))
self.renderers = {} # File extension -> (renderer, can_render) functions
self.version_note = version_note
if self.setting("num_sql_threads") == 0:
self.executor = None
else:
self.executor = futures.ThreadPoolExecutor(
max_workers=self.setting("num_sql_threads")
)
self.max_returned_rows = self.setting("max_returned_rows")
self.sql_time_limit_ms = self.setting("sql_time_limit_ms")
self.page_size = self.setting("default_page_size")
# Execute plugins in constructor, to ensure they are available
# when the rest of `datasette inspect` executes
if self.plugins_dir:
for filepath in glob.glob(os.path.join(self.plugins_dir, "*.py")):
if not os.path.isfile(filepath):
continue
mod = module_from_path(filepath, name=os.path.basename(filepath))
try:
pm.register(mod)
except ValueError:
# Plugin already registered
pass
# Configure Jinja
default_templates = str(app_root / "datasette" / "templates")
template_paths = []
if self.template_dir:
template_paths.append(self.template_dir)
plugin_template_paths = [
plugin["templates_path"]
for plugin in get_plugins()
if plugin["templates_path"]
]
template_paths.extend(plugin_template_paths)
template_paths.append(default_templates)
template_loader = ChoiceLoader(
[
FileSystemLoader(template_paths),
# Support {% extends "default:table.html" %}:
PrefixLoader(
{"default": FileSystemLoader(default_templates)}, delimiter=":"
),
]
)
environment = Environment(
loader=template_loader,
autoescape=True,
enable_async=True,
# undefined=StrictUndefined,
)
environment.filters["escape_css_string"] = escape_css_string
environment.filters["quote_plus"] = urllib.parse.quote_plus
self._jinja_env = environment
environment.filters["escape_sqlite"] = escape_sqlite
environment.filters["to_css_class"] = to_css_class
self._register_renderers()
self._permission_checks = collections.deque(maxlen=200)
self._root_token = secrets.token_hex(32)
self.root_enabled = False
self.default_deny = default_deny
self.client = DatasetteClient(self)
async def apply_metadata_json(self):
# Apply any metadata entries from metadata.json to the internal tables
# step 1: top-level metadata
for key in self._metadata_local or {}:
if key == "databases":
continue
value = self._metadata_local[key]
await self.set_instance_metadata(key, _to_string(value))
# step 2: database-level metadata
for dbname, db in self._metadata_local.get("databases", {}).items():
for key, value in db.items():
if key in ("tables", "queries"):
continue
await self.set_database_metadata(dbname, key, _to_string(value))
# step 3: table-level metadata
for tablename, table in db.get("tables", {}).items():
for key, value in table.items():
if key == "columns":
continue
await self.set_resource_metadata(
dbname, tablename, key, _to_string(value)
)
# step 4: column-level metadata (only descriptions in metadata.json)
for columnname, column_description in table.get("columns", {}).items():
await self.set_column_metadata(
dbname, tablename, columnname, "description", column_description
)
# TODO(alex) is metadata.json was loaded in, and --internal is not memory, then log
# a warning to user that they should delete their metadata.json file
def get_jinja_environment(self, request: Request = None) -> Environment:
environment = self._jinja_env
if request:
for environment in pm.hook.jinja2_environment_from_request(
datasette=self, request=request, env=environment
):
pass
return environment
def get_action(self, name_or_abbr: str):
"""
Returns an Action object for the given name or abbreviation. Returns None if not found.
"""
if name_or_abbr in self.actions:
return self.actions[name_or_abbr]
# Try abbreviation
for action in self.actions.values():
if action.abbr == name_or_abbr:
return action
return None
async def refresh_schemas(self):
# Throttle schema refreshes to at most once per second
if time.monotonic() - getattr(self, "_last_schema_refresh", 0) < 1.0:
return
self._last_schema_refresh = time.monotonic()
if self._refresh_schemas_lock.locked():
return
async with self._refresh_schemas_lock:
await self._refresh_schemas()
async def _refresh_schemas(self):
internal_db = self.get_internal_database()
if not self.internal_db_created:
await init_internal_db(internal_db)
await self.apply_metadata_json()
self.internal_db_created = True
current_schema_versions = {
row["database_name"]: row["schema_version"]
for row in await internal_db.execute(
"select database_name, schema_version from catalog_databases"
)
}
# Delete stale entries for databases that are no longer attached
stale_databases = set(current_schema_versions.keys()) - set(
self.databases.keys()
)
for stale_db_name in stale_databases:
await internal_db.execute_write(
"DELETE FROM catalog_databases WHERE database_name = ?",
[stale_db_name],
)
for database_name, db in self.databases.items():
schema_version = (await db.execute("PRAGMA schema_version")).first()[0]
# Compare schema versions to see if we should skip it
if schema_version == current_schema_versions.get(database_name):
continue
placeholders = "(?, ?, ?, ?)"
values = [database_name, str(db.path), db.is_memory, schema_version]
if db.path is None:
placeholders = "(?, null, ?, ?)"
values = [database_name, db.is_memory, schema_version]
await internal_db.execute_write(
"""
INSERT OR REPLACE INTO catalog_databases (database_name, path, is_memory, schema_version)
VALUES {}
""".format(placeholders),
values,
)
await populate_schema_tables(internal_db, db)
@property
def urls(self):
return Urls(self)
@property
def pm(self):
"""
Return the global plugin manager instance.
This provides access to the pluggy PluginManager that manages all
Datasette plugins and hooks. Use datasette.pm.hook.hook_name() to
call plugin hooks.
"""
return pm
async def invoke_startup(self):
# This must be called for Datasette to be in a usable state
if self._startup_invoked:
return
# Register event classes
event_classes = []
for hook in pm.hook.register_events(datasette=self):
extra_classes = await await_me_maybe(hook)
if extra_classes:
event_classes.extend(extra_classes)
self.event_classes = tuple(event_classes)
# Register actions, but watch out for duplicate name/abbr
action_names = {}
action_abbrs = {}
for hook in pm.hook.register_actions(datasette=self):
if hook:
for action in hook:
if (
action.name in action_names
and action != action_names[action.name]
):
raise StartupError(
"Duplicate action name: {}".format(action.name)
)
if (
action.abbr
and action.abbr in action_abbrs
and action != action_abbrs[action.abbr]
):
raise StartupError(
"Duplicate action abbr: {}".format(action.abbr)
)
action_names[action.name] = action
if action.abbr:
action_abbrs[action.abbr] = action
self.actions[action.name] = action
# Register column types (classes, not instances)
self._column_types = {}
for hook in pm.hook.register_column_types(datasette=self):
if hook:
for ct_cls in hook:
if ct_cls.name in self._column_types:
raise StartupError(f"Duplicate column type name: {ct_cls.name}")
self._column_types[ct_cls.name] = ct_cls
for hook in pm.hook.prepare_jinja2_environment(
env=self._jinja_env, datasette=self
):
await await_me_maybe(hook)
# Ensure internal tables and metadata are populated before startup hooks
await self._refresh_schemas()
# Load column_types from config into internal DB
await self._apply_column_types_config()
for hook in pm.hook.startup(datasette=self):
await await_me_maybe(hook)
self._startup_invoked = True
def sign(self, value, namespace="default"):
return URLSafeSerializer(self._secret, namespace).dumps(value)
def unsign(self, signed, namespace="default"):
return URLSafeSerializer(self._secret, namespace).loads(signed)
def in_client(self) -> bool:
"""Check if the current code is executing within a datasette.client request.
Returns:
bool: True if currently executing within a datasette.client request, False otherwise.
"""
return _in_datasette_client.get()
def _token_handlers(self):
"""Collect all registered token handlers from plugins."""
from datasette.tokens import TokenHandler
handlers = []
for result in pm.hook.register_token_handler(datasette=self):
if isinstance(result, TokenHandler):
handlers.append(result)
elif isinstance(result, list):
handlers.extend(h for h in result if isinstance(h, TokenHandler))
return handlers
async def create_token(
self,
actor_id: str,
*,
expires_after: int | None = None,
restrictions: "TokenRestrictions | None" = None,
handler: str | None = None,
) -> str:
"""
Create an API token for the given actor.
Uses the first registered token handler by default, or a specific
handler if ``handler`` is provided (matched by handler name).
Pass a :class:`TokenRestrictions` to limit which actions the token
can perform.
"""
handlers = self._token_handlers()
if not handlers:
raise RuntimeError("No token handlers are registered")
if handler is not None:
matched = [h for h in handlers if h.name == handler]
if not matched:
available = [h.name for h in handlers]
raise ValueError(
f"Token handler {handler!r} not found. "
f"Available handlers: {available}"
)
chosen = matched[0]
else:
chosen = handlers[0]
return await chosen.create_token(
self,
actor_id,
expires_after=expires_after,
restrictions=restrictions,
)
async def verify_token(self, token: str) -> dict | None:
"""
Verify an API token by trying all registered token handlers.
Returns an actor dict from the first handler that recognizes the
token, or None if no handler accepts it.
"""
for token_handler in self._token_handlers():
result = await token_handler.verify_token(self, token)
if result is not None:
return result
return None
def get_database(self, name=None, route=None):
if route is not None:
matches = [db for db in self.databases.values() if db.route == route]
if not matches:
raise KeyError
return matches[0]
if name is None:
name = [key for key in self.databases.keys()][0]
return self.databases[name]
def add_database(self, db, name=None, route=None):
new_databases = self.databases.copy()
if name is None:
# Pick a unique name for this database
suggestion = db.suggest_name()
name = suggestion
else:
suggestion = name
i = 2
while name in self.databases:
name = "{}_{}".format(suggestion, i)
i += 1
db.name = name
db.route = route or name
new_databases[name] = db
# don't mutate! that causes race conditions with live import
self.databases = new_databases
return db
def add_memory_database(self, memory_name, name=None, route=None):
return self.add_database(
Database(self, memory_name=memory_name), name=name, route=route
)
def remove_database(self, name):
self.get_database(name).close()
new_databases = self.databases.copy()
new_databases.pop(name)
self.databases = new_databases
def setting(self, key):
return self._settings.get(key, None)
def settings_dict(self):
# Returns a fully resolved settings dictionary, useful for templates
return {option.name: self.setting(option.name) for option in SETTINGS}
def _metadata_recursive_update(self, orig, updated):
if not isinstance(orig, dict) or not isinstance(updated, dict):
return orig
for key, upd_value in updated.items():
if isinstance(upd_value, dict) and isinstance(orig.get(key), dict):
orig[key] = self._metadata_recursive_update(orig[key], upd_value)
else:
orig[key] = upd_value
return orig
async def get_instance_metadata(self):
rows = await self.get_internal_database().execute("""
SELECT
key,
value
FROM metadata_instance
""")
return dict(rows)
async def get_database_metadata(self, database_name: str):
rows = await self.get_internal_database().execute(
"""
SELECT
key,
value
FROM metadata_databases
WHERE database_name = ?
""",
[database_name],
)
return dict(rows)
async def get_resource_metadata(self, database_name: str, resource_name: str):
rows = await self.get_internal_database().execute(
"""
SELECT
key,
value
FROM metadata_resources
WHERE database_name = ?
AND resource_name = ?
""",
[database_name, resource_name],
)
return dict(rows)
async def get_column_metadata(
self, database_name: str, resource_name: str, column_name: str
):
rows = await self.get_internal_database().execute(
"""
SELECT
key,
value
FROM metadata_columns
WHERE database_name = ?
AND resource_name = ?
AND column_name = ?
""",
[database_name, resource_name, column_name],
)
return dict(rows)
async def set_instance_metadata(self, key: str, value: str):
# TODO upsert only supported on SQLite 3.24.0 (2018-06-04)
await self.get_internal_database().execute_write(
"""
INSERT INTO metadata_instance(key, value)
VALUES(?, ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value;
""",
[key, value],
)
async def set_database_metadata(self, database_name: str, key: str, value: str):
# TODO upsert only supported on SQLite 3.24.0 (2018-06-04)
await self.get_internal_database().execute_write(
"""
INSERT INTO metadata_databases(database_name, key, value)
VALUES(?, ?, ?)
ON CONFLICT(database_name, key) DO UPDATE SET value = excluded.value;
""",
[database_name, key, value],
)
async def set_resource_metadata(
self, database_name: str, resource_name: str, key: str, value: str
):
# TODO upsert only supported on SQLite 3.24.0 (2018-06-04)
await self.get_internal_database().execute_write(
"""
INSERT INTO metadata_resources(database_name, resource_name, key, value)
VALUES(?, ?, ?, ?)
ON CONFLICT(database_name, resource_name, key) DO UPDATE SET value = excluded.value;
""",
[database_name, resource_name, key, value],
)
async def set_column_metadata(
self,
database_name: str,
resource_name: str,
column_name: str,
key: str,
value: str,
):
# TODO upsert only supported on SQLite 3.24.0 (2018-06-04)
await self.get_internal_database().execute_write(
"""
INSERT INTO metadata_columns(database_name, resource_name, column_name, key, value)
VALUES(?, ?, ?, ?, ?)
ON CONFLICT(database_name, resource_name, column_name, key) DO UPDATE SET value = excluded.value;
""",
[database_name, resource_name, column_name, key, value],
)
# Column types API
async def _get_resource_column_details(self, database: str, resource: str):
db = self.databases.get(database)
if db is None:
return {}
try:
return {
column.name: column
for column in await db.table_column_details(resource)
}
except sqlite3.OperationalError:
return {}
@staticmethod
def _column_type_is_applicable(ct_cls, column_detail) -> bool:
sqlite_types = getattr(ct_cls, "sqlite_types", None)
if sqlite_types is None:
return True
if column_detail is None:
return False
actual_sqlite_type = SQLiteType.from_declared_type(column_detail.type)
return actual_sqlite_type in sqlite_types
async def _validate_column_type_assignment(
self, database: str, resource: str, column: str, ct_cls
) -> None:
sqlite_types = getattr(ct_cls, "sqlite_types", None)
if sqlite_types is None:
return
column_detail = (
await self._get_resource_column_details(database, resource)
).get(column)
if column_detail is None:
return
actual_sqlite_type = SQLiteType.from_declared_type(column_detail.type)
if actual_sqlite_type in sqlite_types:
return
allowed = ", ".join(sqlite_type.value for sqlite_type in sqlite_types)
actual = (
actual_sqlite_type.value
if actual_sqlite_type is not None
else "unrecognized {!r}".format(column_detail.type)
)
raise ValueError(
"Column type {!r} is only applicable to SQLite types {} but {}.{}.{} "
"has SQLite type {}".format(
ct_cls.name,
allowed,
database,
resource,
column,
actual,
)
)
async def _apply_column_types_config(self):
"""Load column_types from datasette.json config into the internal DB."""
import logging
for db_name, db_conf in (self.config or {}).get("databases", {}).items():
for table_name, table_conf in db_conf.get("tables", {}).items():
for col_name, ct in table_conf.get("column_types", {}).items():
if isinstance(ct, str):
col_type, config = ct, None
else:
col_type = ct["type"]
config = ct.get("config")
if col_type not in self._column_types:
logging.warning(
"column_types config references unknown type %r "
"for %s.%s.%s",
col_type,
db_name,
table_name,
col_name,
)
try:
await self.set_column_type(
db_name, table_name, col_name, col_type, config
)
except ValueError as ex:
logging.warning(str(ex))
async def get_column_type(self, database: str, resource: str, column: str):
"""
Return a ColumnType instance (with config baked in) for a specific
column, or None if no column type is assigned.
"""
row = await self.get_internal_database().execute(
"SELECT column_type, config FROM column_types "
"WHERE database_name = ? AND resource_name = ? AND column_name = ?",
[database, resource, column],
)
rows = row.rows
if not rows:
return None
ct_name, config = rows[0]
ct_cls = self._column_types.get(ct_name)
if ct_cls is None:
return None
column_detail = (
await self._get_resource_column_details(database, resource)
).get(column)
if not self._column_type_is_applicable(ct_cls, column_detail):
return None
return ct_cls(config=json.loads(config) if config else None)
async def get_column_types(self, database: str, resource: str) -> dict:
"""
Return {column_name: ColumnType instance (with config)}
for all columns with assigned types on the given resource.
"""
rows = await self.get_internal_database().execute(
"SELECT column_name, column_type, config FROM column_types "
"WHERE database_name = ? AND resource_name = ?",
[database, resource],
)
column_details = await self._get_resource_column_details(database, resource)
result = {}
for row in rows.rows:
col_name, ct_name, config = row
ct_cls = self._column_types.get(ct_name)
if ct_cls is not None and self._column_type_is_applicable(
ct_cls, column_details.get(col_name)
):
result[col_name] = ct_cls(config=json.loads(config) if config else None)
return result
async def set_column_type(
self,
database: str,
resource: str,
column: str,
column_type: str,
config: dict = None,
) -> None:
"""Assign a column type. Overwrites any existing assignment."""
ct_cls = self._column_types.get(column_type)
if ct_cls is not None:
await self._validate_column_type_assignment(
database, resource, column, ct_cls
)
await self.get_internal_database().execute_write(
"""INSERT OR REPLACE INTO column_types
(database_name, resource_name, column_name, column_type, config)
VALUES (?, ?, ?, ?, ?)""",
[
database,
resource,
column,
column_type,
json.dumps(config) if config else None,
],
)
async def remove_column_type(
self, database: str, resource: str, column: str
) -> None:
"""Remove a column type assignment."""
await self.get_internal_database().execute_write(
"DELETE FROM column_types "
"WHERE database_name = ? AND resource_name = ? AND column_name = ?",
[database, resource, column],
)
def get_internal_database(self):
return self._internal_database
def plugin_config(self, plugin_name, database=None, table=None, fallback=True):
"""Return config for plugin, falling back from specified database/table"""
if database is None and table is None:
config = self._plugin_config_top(plugin_name)
else:
config = self._plugin_config_nested(plugin_name, database, table, fallback)
return resolve_env_secrets(config, os.environ)
def _plugin_config_top(self, plugin_name):
"""Returns any top-level plugin configuration for the specified plugin."""
return ((self.config or {}).get("plugins") or {}).get(plugin_name)
def _plugin_config_nested(self, plugin_name, database, table=None, fallback=True):
"""Returns any database or table-level plugin configuration for the specified plugin."""
db_config = ((self.config or {}).get("databases") or {}).get(database)
# if there's no db-level configuration, then return early, falling back to top-level if needed
if not db_config:
return self._plugin_config_top(plugin_name) if fallback else None
db_plugin_config = (db_config.get("plugins") or {}).get(plugin_name)
if table:
table_plugin_config = (
((db_config.get("tables") or {}).get(table) or {}).get("plugins") or {}
).get(plugin_name)
# fallback to db_config or top-level config, in that order, if needed
if table_plugin_config is None and fallback:
return db_plugin_config or self._plugin_config_top(plugin_name)
return table_plugin_config
# fallback to top-level if needed
if db_plugin_config is None and fallback:
self._plugin_config_top(plugin_name)
return db_plugin_config
def app_css_hash(self):
if not hasattr(self, "_app_css_hash"):
with open(os.path.join(str(app_root), "datasette/static/app.css")) as fp:
self._app_css_hash = hashlib.sha1(fp.read().encode("utf8")).hexdigest()[
:6
]
return self._app_css_hash
async def get_canned_queries(self, database_name, actor):
queries = {}
for more_queries in pm.hook.canned_queries(
datasette=self,
database=database_name,
actor=actor,
):
more_queries = await await_me_maybe(more_queries)
queries.update(more_queries or {})
# Fix any {"name": "select ..."} queries to be {"name": {"sql": "select ..."}}
for key in queries:
if not isinstance(queries[key], dict):
queries[key] = {"sql": queries[key]}
# Also make sure "name" is available:
queries[key]["name"] = key
return queries
async def get_canned_query(self, database_name, query_name, actor):
queries = await self.get_canned_queries(database_name, actor)
query = queries.get(query_name)
if query:
return query
def _prepare_connection(self, conn, database):
conn.row_factory = sqlite3.Row
conn.text_factory = lambda x: str(x, "utf-8", "replace")
if self.sqlite_extensions and database != INTERNAL_DB_NAME:
conn.enable_load_extension(True)
for extension in self.sqlite_extensions:
# "extension" is either a string path to the extension
# or a 2-item tuple that specifies which entrypoint to load.
if isinstance(extension, tuple):
path, entrypoint = extension
conn.execute("SELECT load_extension(?, ?)", [path, entrypoint])
else:
conn.execute("SELECT load_extension(?)", [extension])
if self.setting("cache_size_kb"):
conn.execute(f"PRAGMA cache_size=-{self.setting('cache_size_kb')}")
# pylint: disable=no-member
if database != INTERNAL_DB_NAME:
pm.hook.prepare_connection(conn=conn, database=database, datasette=self)
# If self.crossdb and this is _memory, connect the first SQLITE_LIMIT_ATTACHED databases
if self.crossdb and database == "_memory":
count = 0
for db_name, db in self.databases.items():
if count >= SQLITE_LIMIT_ATTACHED or db.is_memory:
continue
sql = 'ATTACH DATABASE "file:{path}?{qs}" AS [{name}];'.format(
path=db.path,
qs="mode=ro" if db.is_mutable else "immutable=1",
name=db_name,
)
conn.execute(sql)
count += 1
def add_message(self, request, message, type=INFO):
if not hasattr(request, "_messages"):
request._messages = []
request._messages_should_clear = False
request._messages.append((message, type))
def _write_messages_to_response(self, request, response):
if getattr(request, "_messages", None):
# Set those messages
response.set_cookie("ds_messages", self.sign(request._messages, "messages"))
elif getattr(request, "_messages_should_clear", False):
response.set_cookie("ds_messages", "", expires=0, max_age=0)
def _show_messages(self, request):
if getattr(request, "_messages", None):
request._messages_should_clear = True
messages = request._messages
request._messages = []
return messages
else:
return []
async def _crumb_items(self, request, table=None, database=None):
crumbs = []
actor = None
if request:
actor = request.actor
# Top-level link
if await self.allowed(action="view-instance", actor=actor):
crumbs.append({"href": self.urls.instance(), "label": "home"})
# Database link
if database:
if await self.allowed(
action="view-database",
resource=DatabaseResource(database=database),
actor=actor,
):
crumbs.append(
{
"href": self.urls.database(database),
"label": database,
}
)
# Table link
if table:
assert database, "table= requires database="
if await self.allowed(
action="view-table",
resource=TableResource(database=database, table=table),
actor=actor,
):
crumbs.append(
{
"href": self.urls.table(database, table),
"label": table,
}
)
return crumbs
async def actors_from_ids(
self, actor_ids: Iterable[str | int]
) -> Dict[int | str, Dict]:
result = pm.hook.actors_from_ids(datasette=self, actor_ids=actor_ids)
if result is None:
# Do the default thing
return {actor_id: {"id": actor_id} for actor_id in actor_ids}
result = await await_me_maybe(result)
return result
async def track_event(self, event: Event):
assert isinstance(event, self.event_classes), "Invalid event type: {}".format(
type(event)
)
for hook in pm.hook.track_event(datasette=self, event=event):
await await_me_maybe(hook)
def resource_for_action(self, action: str, parent: str | None, child: str | None):
"""
Create a Resource instance for the given action with parent/child values.
Looks up the action's resource_class and instantiates it with the
provided parent and child identifiers.
Args:
action: The action name (e.g., "view-table", "view-query")
parent: The parent resource identifier (e.g., database name)
child: The child resource identifier (e.g., table/query name)
Returns:
A Resource instance of the appropriate subclass
Raises:
ValueError: If the action is unknown
"""
from datasette.permissions import Resource
action_obj = self.actions.get(action)
if not action_obj:
raise ValueError(f"Unknown action: {action}")
resource_class = action_obj.resource_class
instance = object.__new__(resource_class)
Resource.__init__(instance, parent=parent, child=child)
return instance
async def check_visibility(
self,
actor: dict,
action: str,
resource: "Resource" | None = None,
):
"""
Check if actor can see a resource and if it's private.
Returns (visible, private) tuple:
- visible: bool - can the actor see it?
- private: bool - if visible, can anonymous users NOT see it?
"""
from datasette.permissions import Resource
# Validate that resource is a Resource object or None
if resource is not None and not isinstance(resource, Resource):
raise TypeError("resource must be a Resource subclass instance or None.")
# Check if actor can see it
if not await self.allowed(action=action, resource=resource, actor=actor):
return False, False
# Check if anonymous user can see it (for "private" flag)
if not await self.allowed(action=action, resource=resource, actor=None):
# Actor can see it but anonymous cannot - it's private
return True, True
# Both actor and anonymous can see it - it's public
return True, False
async def allowed_resources_sql(
self,
*,
action: str,
actor: dict | None = None,
parent: str | None = None,
include_is_private: bool = False,
) -> ResourcesSQL:
"""
Build SQL query to get all resources the actor can access for the given action.
Args:
action: The action name (e.g., "view-table")
actor: The actor dict (or None for unauthenticated)
parent: Optional parent filter (e.g., database name) to limit results
include_is_private: If True, include is_private column showing if anonymous cannot access
Returns a namedtuple of (query: str, params: dict) that can be executed against the internal database.
The query returns rows with (parent, child, reason) columns, plus is_private if requested.
Example:
query, params = await datasette.allowed_resources_sql(
action="view-table",
actor=actor,
parent="mydb",
include_is_private=True
)
result = await datasette.get_internal_database().execute(query, params)
"""
from datasette.utils.actions_sql import build_allowed_resources_sql
action_obj = self.actions.get(action)
if not action_obj:
raise ValueError(f"Unknown action: {action}")
sql, params = await build_allowed_resources_sql(
self, actor, action, parent=parent, include_is_private=include_is_private
)
return ResourcesSQL(sql, params)
async def allowed_resources(
self,
action: str,
actor: dict | None = None,
*,
parent: str | None = None,
include_is_private: bool = False,
include_reasons: bool = False,
limit: int = 100,
next: str | None = None,
) -> PaginatedResources:
"""
Return paginated resources the actor can access for the given action.
Uses SQL with keyset pagination to efficiently filter resources.
Returns PaginatedResources with list of Resource instances and pagination metadata.
Args:
action: The action name (e.g., "view-table")
actor: The actor dict (or None for unauthenticated)
parent: Optional parent filter (e.g., database name) to limit results
include_is_private: If True, adds a .private attribute to each Resource
include_reasons: If True, adds a .reasons attribute with List[str] of permission reasons
limit: Maximum number of results to return (1-1000, default 100)
next: Keyset token from previous page for pagination
Returns:
PaginatedResources with:
- resources: List of Resource objects for this page
- next: Token for next page (None if no more results)
Example:
# Get first page of tables
page = await datasette.allowed_resources("view-table", actor, limit=50)
for table in page.resources:
print(f"{table.parent}/{table.child}")
# Get next page
if page.next:
next_page = await datasette.allowed_resources(
"view-table", actor, limit=50, next=page.next
)
# With reasons for debugging
page = await datasette.allowed_resources(
"view-table", actor, include_reasons=True
)
for table in page.resources:
print(f"{table.child}: {table.reasons}")
# Iterate through all results with async generator
page = await datasette.allowed_resources("view-table", actor)
async for table in page.all():
print(table.child)
"""
action_obj = self.actions.get(action)
if not action_obj:
raise ValueError(f"Unknown action: {action}")
# Validate and cap limit
limit = min(max(1, limit), 1000)
# Get base SQL query
query, params = await self.allowed_resources_sql(
action=action,
actor=actor,
parent=parent,
include_is_private=include_is_private,
)
# Add keyset pagination WHERE clause if next token provided
if next:
try:
components = urlsafe_components(next)
if len(components) >= 2:
last_parent, last_child = components[0], components[1]
# Keyset condition: (parent > last) OR (parent = last AND child > last)
keyset_where = """
(parent > :keyset_parent OR
(parent = :keyset_parent AND child > :keyset_child))
"""
# Wrap original query and add keyset filter
query = f"SELECT * FROM ({query}) WHERE {keyset_where}"
params["keyset_parent"] = last_parent
params["keyset_child"] = last_child
except (ValueError, KeyError):
# Invalid token - ignore and start from beginning
pass
# Add LIMIT (fetch limit+1 to detect if there are more results)
# Note: query from allowed_resources_sql() already includes ORDER BY parent, child
query = f"{query} LIMIT :limit"
params["limit"] = limit + 1
# Execute query
result = await self.get_internal_database().execute(query, params)
rows = list(result.rows)
# Check if truncated (got more than limit rows)
truncated = len(rows) > limit
if truncated:
rows = rows[:limit] # Remove the extra row
# Build Resource objects with optional attributes
resources = []
for row in rows:
# row[0]=parent, row[1]=child, row[2]=reason, row[3]=is_private (if requested)
resource = self.resource_for_action(action, parent=row[0], child=row[1])
# Add reasons if requested
if include_reasons:
reason_json = row[2]
try:
reasons_array = (
json.loads(reason_json) if isinstance(reason_json, str) else []
)
resource.reasons = [r for r in reasons_array if r is not None]
except (json.JSONDecodeError, TypeError):
resource.reasons = [reason_json] if reason_json else []
# Add private flag if requested
if include_is_private:
resource.private = bool(row[3])
resources.append(resource)
# Generate next token if there are more results
next_token = None
if truncated and resources:
last_resource = resources[-1]
# Use tilde-encoding like table pagination
next_token = "{},{}".format(
tilde_encode(str(last_resource.parent)),
tilde_encode(str(last_resource.child)),
)
return PaginatedResources(
resources=resources,
next=next_token,
_datasette=self,
_action=action,
_actor=actor,
_parent=parent,
_include_is_private=include_is_private,
_include_reasons=include_reasons,
_limit=limit,
)
async def allowed(
self,
*,
action: str,
resource: "Resource" = None,
actor: dict | None = None,
) -> bool:
"""
Check if actor can perform action on specific resource.
Uses SQL to check permission for a single resource without fetching all resources.
This is efficient - it does NOT call allowed_resources() and check membership.
For global actions, resource should be None (or omitted).
Example:
from datasette.resources import TableResource
can_view = await datasette.allowed(
action="view-table",
resource=TableResource(database="analytics", table="users"),
actor=actor
)
# For global actions, resource can be omitted:
can_debug = await datasette.allowed(action="permissions-debug", actor=actor)
"""
from datasette.utils.actions_sql import check_permission_for_resource
# For global actions, resource remains None
# Check if this action has also_requires - if so, check that action first
action_obj = self.actions.get(action)
if action_obj and action_obj.also_requires:
# Must have the required action first
if not await self.allowed(
action=action_obj.also_requires,
resource=resource,
actor=actor,
):
return False
# For global actions, resource is None
parent = resource.parent if resource else None
child = resource.child if resource else None
result = await check_permission_for_resource(
datasette=self,
actor=actor,
action=action,
parent=parent,
child=child,
)
# Log the permission check for debugging
self._permission_checks.append(
PermissionCheck(
when=datetime.datetime.now(datetime.timezone.utc).isoformat(),
actor=actor,
action=action,
parent=parent,
child=child,
result=result,
)
)
return result
async def ensure_permission(
self,
*,
action: str,
resource: "Resource" = None,
actor: dict | None = None,
):
"""
Check if actor can perform action on resource, raising Forbidden if not.
This is a convenience wrapper around allowed() that raises Forbidden
instead of returning False. Use this when you want to enforce a permission
check and halt execution if it fails.
Example:
from datasette.resources import TableResource
# Will raise Forbidden if actor cannot view the table
await datasette.ensure_permission(
action="view-table",
resource=TableResource(database="analytics", table="users"),
actor=request.actor
)
# For instance-level actions, resource can be omitted:
await datasette.ensure_permission(
action="permissions-debug",
actor=request.actor
)
"""
if not await self.allowed(action=action, resource=resource, actor=actor):
raise Forbidden(action)
async def execute(
self,
db_name,
sql,
params=None,
truncate=False,
custom_time_limit=None,
page_size=None,
log_sql_errors=True,
):
return await self.databases[db_name].execute(
sql,
params=params,
truncate=truncate,
custom_time_limit=custom_time_limit,
page_size=page_size,
log_sql_errors=log_sql_errors,
)
async def expand_foreign_keys(self, actor, database, table, column, values):
"""Returns dict mapping (column, value) -> label"""
labeled_fks = {}
db = self.databases[database]
foreign_keys = await db.foreign_keys_for_table(table)
# Find the foreign_key for this column
try:
fk = [
foreign_key
for foreign_key in foreign_keys
if foreign_key["column"] == column
][0]
except IndexError:
return {}
# Ensure user has permission to view the referenced table
from datasette.resources import TableResource
other_table = fk["other_table"]
other_column = fk["other_column"]
visible, _ = await self.check_visibility(
actor,
action="view-table",
resource=TableResource(database=database, table=other_table),
)
if not visible:
return {}
label_column = await db.label_column_for_table(other_table)
if not label_column:
return {(fk["column"], value): str(value) for value in values}
labeled_fks = {}
sql = """
select {other_column}, {label_column}
from {other_table}
where {other_column} in ({placeholders})
""".format(
other_column=escape_sqlite(other_column),
label_column=escape_sqlite(label_column),
other_table=escape_sqlite(other_table),
placeholders=", ".join(["?"] * len(set(values))),
)
try:
results = await self.execute(database, sql, list(set(values)))
except QueryInterrupted:
pass
else:
for id, value in results:
labeled_fks[(fk["column"], id)] = value
return labeled_fks
def absolute_url(self, request, path):
url = urllib.parse.urljoin(request.url, path)
if url.startswith("http://") and self.setting("force_https_urls"):
url = "https://" + url[len("http://") :]
return url
def _connected_databases(self):
return [
{
"name": d.name,
"route": d.route,
"path": d.path,
"size": d.size,
"is_mutable": d.is_mutable,
"is_memory": d.is_memory,
"hash": d.hash,
}
for name, d in self.databases.items()
]
def _versions(self):
conn = sqlite3.connect(":memory:")
self._prepare_connection(conn, "_memory")
sqlite_version = conn.execute("select sqlite_version()").fetchone()[0]
sqlite_extensions = {"json1": detect_json1(conn)}
for extension, testsql, hasversion in (
("spatialite", "SELECT spatialite_version()", True),
):
try:
result = conn.execute(testsql)
if hasversion:
sqlite_extensions[extension] = result.fetchone()[0]
else:
sqlite_extensions[extension] = None
except Exception:
pass
# More details on SpatiaLite
if "spatialite" in sqlite_extensions:
spatialite_details = {}
for fn in SPATIALITE_FUNCTIONS:
try:
result = conn.execute("select {}()".format(fn))
spatialite_details[fn] = result.fetchone()[0]
except Exception as e:
spatialite_details[fn] = {"error": str(e)}
sqlite_extensions["spatialite"] = spatialite_details
# Figure out supported FTS versions
fts_versions = []
for fts in ("FTS5", "FTS4", "FTS3"):
try:
conn.execute(
"CREATE VIRTUAL TABLE v{fts} USING {fts} (data)".format(fts=fts)
)
fts_versions.append(fts)
except sqlite3.OperationalError:
continue
datasette_version = {"version": __version__}
if self.version_note:
datasette_version["note"] = self.version_note
try:
# Optional import to avoid breaking Pyodide
# https://github.com/simonw/datasette/issues/1733#issuecomment-1115268245
import uvicorn
uvicorn_version = uvicorn.__version__
except ImportError:
uvicorn_version = None
info = {
"python": {
"version": ".".join(map(str, sys.version_info[:3])),
"full": sys.version,
},
"datasette": datasette_version,
"asgi": "3.0",
"uvicorn": uvicorn_version,
"sqlite": {
"version": sqlite_version,
"fts_versions": fts_versions,
"extensions": sqlite_extensions,
"compile_options": [
r[0] for r in conn.execute("pragma compile_options;").fetchall()
],
},
}
if using_pysqlite3:
for package in ("pysqlite3", "pysqlite3-binary"):
try:
info["pysqlite3"] = importlib.metadata.version(package)
break
except importlib.metadata.PackageNotFoundError:
pass
return info
def _plugins(self, request=None, all=False):
ps = list(get_plugins())
should_show_all = False
if request is not None:
should_show_all = request.args.get("all")
else:
should_show_all = all
if not should_show_all:
ps = [p for p in ps if p["name"] not in DEFAULT_PLUGINS]
ps.sort(key=lambda p: p["name"])
return [
{
"name": p["name"],
"static": p["static_path"] is not None,
"templates": p["templates_path"] is not None,
"version": p.get("version"),
"hooks": list(sorted(set(p["hooks"]))),
}
for p in ps
]
def _threads(self):
if self.setting("num_sql_threads") == 0:
return {"num_threads": 0, "threads": []}
threads = list(threading.enumerate())
d = {
"num_threads": len(threads),
"threads": [
{"name": t.name, "ident": t.ident, "daemon": t.daemon} for t in threads
],
}
tasks = asyncio.all_tasks()
d.update(
{
"num_tasks": len(tasks),
"tasks": [_cleaner_task_str(t) for t in tasks],
}
)
return d
def _actor(self, request):
return {"actor": request.actor}
def _actions(self):
return [
{
"name": action.name,
"abbr": action.abbr,
"description": action.description,
"takes_parent": action.takes_parent,
"takes_child": action.takes_child,
"resource_class": (
action.resource_class.__name__ if action.resource_class else None
),
"also_requires": action.also_requires,
}
for action in sorted(self.actions.values(), key=lambda a: a.name)
]
async def table_config(self, database: str, table: str) -> dict:
"""Return dictionary of configuration for specified table"""
return (
(self.config or {})
.get("databases", {})
.get(database, {})
.get("tables", {})
.get(table, {})
)
def _register_renderers(self):
"""Register output renderers which output data in custom formats."""
# Built-in renderers
self.renderers["json"] = (json_renderer, lambda: True)
# Hooks
hook_renderers = []
# pylint: disable=no-member
for hook in pm.hook.register_output_renderer(datasette=self):
if type(hook) is list:
hook_renderers += hook
else:
hook_renderers.append(hook)
for renderer in hook_renderers:
self.renderers[renderer["extension"]] = (
# It used to be called "callback" - remove this in Datasette 1.0
renderer.get("render") or renderer["callback"],
renderer.get("can_render") or (lambda: True),
)
async def render_template(
self,
templates: List[str] | str | Template,
context: Dict[str, Any] | Context | None = None,
request: Request | None = None,
view_name: str | None = None,
):
if not self._startup_invoked:
raise Exception("render_template() called before await ds.invoke_startup()")
context = context or {}
if isinstance(templates, Template):
template = templates
else:
if isinstance(templates, str):
templates = [templates]
template = self.get_jinja_environment(request).select_template(templates)
if dataclasses.is_dataclass(context):
context = dataclasses.asdict(context)
body_scripts = []
# pylint: disable=no-member
for extra_script in pm.hook.extra_body_script(
template=template.name,
database=context.get("database"),
table=context.get("table"),
columns=context.get("columns"),
view_name=view_name,
request=request,
datasette=self,
):
extra_script = await await_me_maybe(extra_script)
if isinstance(extra_script, dict):
script = extra_script["script"]
module = bool(extra_script.get("module"))
else:
script = extra_script
module = False
body_scripts.append({"script": Markup(script), "module": module})
extra_template_vars = {}
# pylint: disable=no-member
for extra_vars in pm.hook.extra_template_vars(
template=template.name,
database=context.get("database"),
table=context.get("table"),
columns=context.get("columns"),
view_name=view_name,
request=request,
datasette=self,
):
extra_vars = await await_me_maybe(extra_vars)
assert isinstance(extra_vars, dict), "extra_vars is of type {}".format(
type(extra_vars)
)
extra_template_vars.update(extra_vars)
async def menu_links():
links = []
for hook in pm.hook.menu_links(
datasette=self,
actor=request.actor if request else None,
request=request or None,
):
extra_links = await await_me_maybe(hook)
if extra_links:
links.extend(extra_links)
return links
template_context = {
**context,
**{
"request": request,
"crumb_items": self._crumb_items,
"urls": self.urls,
"actor": request.actor if request else None,
"menu_links": menu_links,
"display_actor": display_actor,
"show_logout": request is not None
and "ds_actor" in request.cookies
and request.actor,
"app_css_hash": self.app_css_hash(),
"zip": zip,
"body_scripts": body_scripts,
"format_bytes": format_bytes,
"show_messages": lambda: self._show_messages(request),
"extra_css_urls": await self._asset_urls(
"extra_css_urls", template, context, request, view_name
),
"extra_js_urls": await self._asset_urls(
"extra_js_urls", template, context, request, view_name
),
"base_url": self.setting("base_url"),
"csrftoken": request.scope["csrftoken"] if request else lambda: "",
"datasette_version": __version__,
},
**extra_template_vars,
}
if request and request.args.get("_context") and self.setting("template_debug"):
return "<pre>{}</pre>".format(
escape(json.dumps(template_context, default=repr, indent=4))
)
return await template.render_async(template_context)
def set_actor_cookie(
self, response: Response, actor: dict, expire_after: int | None = None
):
data = {"a": actor}
if expire_after:
expires_at = int(time.time()) + (24 * 60 * 60)
data["e"] = baseconv.base62.encode(expires_at)
response.set_cookie("ds_actor", self.sign(data, "actor"))
def delete_actor_cookie(self, response: Response):
response.set_cookie("ds_actor", "", expires=0, max_age=0)
async def _asset_urls(self, key, template, context, request, view_name):
# Flatten list-of-lists from plugins:
seen_urls = set()
collected = []
for hook in getattr(pm.hook, key)(
template=template.name,
database=context.get("database"),
table=context.get("table"),
columns=context.get("columns"),
view_name=view_name,
request=request,
datasette=self,
):
hook = await await_me_maybe(hook)
collected.extend(hook)
collected.extend((self.config or {}).get(key) or [])
output = []
for url_or_dict in collected:
if isinstance(url_or_dict, dict):
url = url_or_dict["url"]
sri = url_or_dict.get("sri")
module = bool(url_or_dict.get("module"))
else:
url = url_or_dict
sri = None
module = False
if url in seen_urls:
continue
seen_urls.add(url)
if url.startswith("/"):
# Take base_url into account:
url = self.urls.path(url)
script = {"url": url}
if sri:
script["sri"] = sri
if module:
script["module"] = True
output.append(script)
return output
def _config(self):
return redact_keys(
self.config, ("secret", "key", "password", "token", "hash", "dsn")
)
def _routes(self):
routes = []
for routes_to_add in pm.hook.register_routes(datasette=self):
for regex, view_fn in routes_to_add:
routes.append((regex, wrap_view(view_fn, self)))
def add_route(view, regex):
routes.append((regex, view))
add_route(IndexView.as_view(self), r"/(\.(?P<format>jsono?))?$")
add_route(IndexView.as_view(self), r"/-/(\.(?P<format>jsono?))?$")
add_route(permanent_redirect("/-/"), r"/-$")
# TODO: /favicon.ico and /-/static/ deserve far-future cache expires
add_route(favicon, "/favicon.ico")
add_route(
asgi_static(app_root / "datasette" / "static"), r"/-/static/(?P<path>.*)$"
)
for path, dirname in self.static_mounts:
add_route(asgi_static(dirname), r"/" + path + "/(?P<path>.*)$")
# Mount any plugin static/ directories
for plugin in get_plugins():
if plugin["static_path"]:
add_route(
asgi_static(plugin["static_path"]),
f"/-/static-plugins/{plugin['name']}/(?P<path>.*)$",
)
# Support underscores in name in addition to hyphens, see https://github.com/simonw/datasette/issues/611
add_route(
asgi_static(plugin["static_path"]),
"/-/static-plugins/{}/(?P<path>.*)$".format(
plugin["name"].replace("-", "_")
),
)
add_route(
permanent_redirect(
"/_memory", forward_query_string=True, forward_rest=True
),
r"/:memory:(?P<rest>.*)$",
)
add_route(
JsonDataView.as_view(self, "versions.json", self._versions),
r"/-/versions(\.(?P<format>json))?$",
)
add_route(
JsonDataView.as_view(
self, "plugins.json", self._plugins, needs_request=True
),
r"/-/plugins(\.(?P<format>json))?$",
)
add_route(
JsonDataView.as_view(self, "settings.json", lambda: self._settings),
r"/-/settings(\.(?P<format>json))?$",
)
add_route(
JsonDataView.as_view(self, "config.json", lambda: self._config()),
r"/-/config(\.(?P<format>json))?$",
)
add_route(
JsonDataView.as_view(self, "threads.json", self._threads),
r"/-/threads(\.(?P<format>json))?$",
)
add_route(
JsonDataView.as_view(self, "databases.json", self._connected_databases),
r"/-/databases(\.(?P<format>json))?$",
)
add_route(
JsonDataView.as_view(
self, "actor.json", self._actor, needs_request=True, permission=None
),
r"/-/actor(\.(?P<format>json))?$",
)
add_route(
JsonDataView.as_view(
self,
"actions.json",
self._actions,
template="debug_actions.html",
permission="permissions-debug",
),
r"/-/actions(\.(?P<format>json))?$",
)
add_route(
AuthTokenView.as_view(self),
r"/-/auth-token$",
)
add_route(
CreateTokenView.as_view(self),
r"/-/create-token$",
)
add_route(
ApiExplorerView.as_view(self),
r"/-/api$",
)
add_route(
TablesView.as_view(self),
r"/-/tables(\.(?P<format>json))?$",
)
add_route(
InstanceSchemaView.as_view(self),
r"/-/schema(\.(?P<format>json|md))?$",
)
add_route(
LogoutView.as_view(self),
r"/-/logout$",
)
add_route(
PermissionsDebugView.as_view(self),
r"/-/permissions$",
)
add_route(
AllowedResourcesView.as_view(self),
r"/-/allowed(\.(?P<format>json))?$",
)
add_route(
PermissionRulesView.as_view(self),
r"/-/rules(\.(?P<format>json))?$",
)
add_route(
PermissionCheckView.as_view(self),
r"/-/check(\.(?P<format>json))?$",
)
add_route(
MessagesDebugView.as_view(self),
r"/-/messages$",
)
add_route(
AllowDebugView.as_view(self),
r"/-/allow-debug$",
)
add_route(
wrap_view(PatternPortfolioView, self),
r"/-/patterns$",
)
add_route(
wrap_view(database_download, self),
r"/(?P<database>[^\/\.]+)\.db$",
)
add_route(
wrap_view(DatabaseView, self),
r"/(?P<database>[^\/\.]+)(\.(?P<format>\w+))?$",
)
add_route(TableCreateView.as_view(self), r"/(?P<database>[^\/\.]+)/-/create$")
add_route(
DatabaseSchemaView.as_view(self),
r"/(?P<database>[^\/\.]+)/-/schema(\.(?P<format>json|md))?$",
)
add_route(
wrap_view(QueryView, self),
r"/(?P<database>[^\/\.]+)/-/query(\.(?P<format>\w+))?$",
)
add_route(
wrap_view(table_view, self),
r"/(?P<database>[^\/\.]+)/(?P<table>[^\/\.]+)(\.(?P<format>\w+))?$",
)
add_route(
RowView.as_view(self),
r"/(?P<database>[^\/\.]+)/(?P<table>[^/]+?)/(?P<pks>[^/]+?)(\.(?P<format>\w+))?$",
)
add_route(
TableInsertView.as_view(self),
r"/(?P<database>[^\/\.]+)/(?P<table>[^\/\.]+)/-/insert$",
)
add_route(
TableUpsertView.as_view(self),
r"/(?P<database>[^\/\.]+)/(?P<table>[^\/\.]+)/-/upsert$",
)
add_route(
TableSetColumnTypeView.as_view(self),
r"/(?P<database>[^\/\.]+)/(?P<table>[^\/\.]+)/-/set-column-type$",
)
add_route(
TableDropView.as_view(self),
r"/(?P<database>[^\/\.]+)/(?P<table>[^\/\.]+)/-/drop$",
)
add_route(
TableSchemaView.as_view(self),
r"/(?P<database>[^\/\.]+)/(?P<table>[^\/\.]+)/-/schema(\.(?P<format>json|md))?$",
)
add_route(
RowDeleteView.as_view(self),
r"/(?P<database>[^\/\.]+)/(?P<table>[^/]+?)/(?P<pks>[^/]+?)/-/delete$",
)
add_route(
RowUpdateView.as_view(self),
r"/(?P<database>[^\/\.]+)/(?P<table>[^/]+?)/(?P<pks>[^/]+?)/-/update$",
)
return [
# Compile any strings to regular expressions
((re.compile(pattern) if isinstance(pattern, str) else pattern), view)
for pattern, view in routes
]
async def resolve_database(self, request):
database_route = tilde_decode(request.url_vars["database"])
try:
return self.get_database(route=database_route)
except KeyError:
raise DatabaseNotFound(database_route)
async def resolve_table(self, request):
db = await self.resolve_database(request)
table_name = tilde_decode(request.url_vars["table"])
# Table must exist
is_view = False
table_exists = await db.table_exists(table_name)
if not table_exists:
is_view = await db.view_exists(table_name)
if not (table_exists or is_view):
raise TableNotFound(db.name, table_name)
return ResolvedTable(db, table_name, is_view)
async def resolve_row(self, request):
db, table_name, _ = await self.resolve_table(request)
pk_values = urlsafe_components(request.url_vars["pks"])
sql, params, pks = await row_sql_params_pks(db, table_name, pk_values)
results = await db.execute(sql, params, truncate=True)
row = results.first()
if row is None:
raise RowNotFound(db.name, table_name, pk_values)
return ResolvedRow(db, table_name, sql, params, pks, pk_values, results.first())
def app(self):
"""Returns an ASGI app function that serves the whole of Datasette"""
routes = self._routes()
async def setup_db():
# First time server starts up, calculate table counts for immutable databases
for database in self.databases.values():
if not database.is_mutable:
await database.table_counts(limit=60 * 60 * 1000)
async def custom_csrf_error(scope, send, message_id):
await asgi_send(
send,
content=await self.render_template(
"csrf_error.html",
{"message_id": message_id, "message_name": Errors(message_id).name},
),
status=403,
content_type="text/html; charset=utf-8",
)
asgi = asgi_csrf.asgi_csrf(
DatasetteRouter(self, routes),
signing_secret=self._secret,
cookie_name="ds_csrftoken",
skip_if_scope=lambda scope: any(
pm.hook.skip_csrf(datasette=self, scope=scope)
),
send_csrf_failed=custom_csrf_error,
)
if self.setting("trace_debug"):
asgi = AsgiTracer(asgi)
asgi = AsgiLifespan(asgi)
asgi = AsgiRunOnFirstRequest(asgi, on_startup=[setup_db, self.invoke_startup])
for wrapper in pm.hook.asgi_wrapper(datasette=self):
asgi = wrapper(asgi)
return asgi
class DatasetteRouter:
def __init__(self, datasette, routes):
self.ds = datasette
self.routes = routes or []
async def __call__(self, scope, receive, send):
# Because we care about "foo/bar" v.s. "foo%2Fbar" we decode raw_path ourselves
path = scope["path"]
raw_path = scope.get("raw_path")
if raw_path:
path = raw_path.decode("ascii")
path = path.partition("?")[0]
return await self.route_path(scope, receive, send, path)
async def route_path(self, scope, receive, send, path):
# Strip off base_url if present before routing
base_url = self.ds.setting("base_url")
if base_url != "/" and path.startswith(base_url):
path = "/" + path[len(base_url) :]
scope = dict(scope, route_path=path)
request = Request(scope, receive)
# Populate request_messages if ds_messages cookie is present
try:
request._messages = self.ds.unsign(
request.cookies.get("ds_messages", ""), "messages"
)
except BadSignature:
pass
scope_modifications = {}
# Apply force_https_urls, if set
if (
self.ds.setting("force_https_urls")
and scope["type"] == "http"
and scope.get("scheme") != "https"
):
scope_modifications["scheme"] = "https"
# Handle authentication
default_actor = scope.get("actor") or None
actor = None
results = pm.hook.actor_from_request(datasette=self.ds, request=request)
for result in results:
result = await await_me_maybe(result)
if result and actor is None:
actor = result
# Don't break — we must await all coroutines to avoid
# "coroutine was never awaited" warnings
scope_modifications["actor"] = actor or default_actor
scope = dict(scope, **scope_modifications)
match, view = resolve_routes(self.routes, path)
if match is None:
return await self.handle_404(request, send)
new_scope = dict(scope, url_route={"kwargs": match.groupdict()})
request.scope = new_scope
try:
response = await view(request, send)
if response:
self.ds._write_messages_to_response(request, response)
await response.asgi_send(send)
return
except NotFound as exception:
return await self.handle_404(request, send, exception)
except Forbidden as exception:
# Try the forbidden() plugin hook
for custom_response in pm.hook.forbidden(
datasette=self.ds, request=request, message=exception.args[0]
):
custom_response = await await_me_maybe(custom_response)
assert (
custom_response
), "Default forbidden() hook should have been called"
return await custom_response.asgi_send(send)
except Exception as exception:
return await self.handle_exception(request, send, exception)
async def handle_404(self, request, send, exception=None):
# If path contains % encoding, redirect to tilde encoding
if "%" in request.path:
# Try the same path but with "%" replaced by "~"
# and "~" replaced with "~7E"
# and "." replaced with "~2E"
new_path = (
request.path.replace("~", "~7E").replace("%", "~").replace(".", "~2E")
)
if request.query_string:
new_path += "?{}".format(request.query_string)
await asgi_send_redirect(send, new_path)
return
# If URL has a trailing slash, redirect to URL without it
path = request.scope.get(
"raw_path", request.scope["path"].encode("utf8")
).partition(b"?")[0]
context = {}
if path.endswith(b"/"):
path = path.rstrip(b"/")
if request.scope["query_string"]:
path += b"?" + request.scope["query_string"]
await asgi_send_redirect(send, path.decode("latin1"))
else:
# Is there a pages/* template matching this path?
route_path = request.scope.get("route_path", request.scope["path"])
# Jinja requires template names to use "/" even on Windows
template_name = "pages" + route_path + ".html"
# Build a list of pages/blah/{name}.html matching expressions
environment = self.ds.get_jinja_environment(request)
pattern_templates = [
filepath
for filepath in environment.list_templates()
if "{" in filepath and filepath.startswith("pages/")
]
page_routes = [
(route_pattern_from_filepath(filepath[len("pages/") :]), filepath)
for filepath in pattern_templates
]
try:
template = environment.select_template([template_name])
except TemplateNotFound:
template = None
if template is None:
# Try for a pages/blah/{name}.html template match
for regex, wildcard_template in page_routes:
match = regex.match(route_path)
if match is not None:
context.update(match.groupdict())
template = wildcard_template
break
if template:
headers = {}
status = [200]
def custom_header(name, value):
headers[name] = value
return ""
def custom_status(code):
status[0] = code
return ""
def custom_redirect(location, code=302):
status[0] = code
headers["Location"] = location
return ""
def raise_404(message=""):
raise NotFoundExplicit(message)
context.update(
{
"custom_header": custom_header,
"custom_status": custom_status,
"custom_redirect": custom_redirect,
"raise_404": raise_404,
}
)
try:
body = await self.ds.render_template(
template,
context,
request=request,
view_name="page",
)
except NotFoundExplicit as e:
await self.handle_exception(request, send, e)
return
# Pull content-type out into separate parameter
content_type = "text/html; charset=utf-8"
matches = [k for k in headers if k.lower() == "content-type"]
if matches:
content_type = headers[matches[0]]
await asgi_send(
send,
body,
status=status[0],
headers=headers,
content_type=content_type,
)
else:
await self.handle_exception(request, send, exception or NotFound("404"))
async def handle_exception(self, request, send, exception):
responses = []
for hook in pm.hook.handle_exception(
datasette=self.ds,
request=request,
exception=exception,
):
response = await await_me_maybe(hook)
if response is not None:
responses.append(response)
assert responses, "Default exception handler should have returned something"
# Even if there are multiple responses use just the first one
response = responses[0]
await response.asgi_send(send)
_cleaner_task_str_re = re.compile(r"\S*site-packages/")
def _cleaner_task_str(task):
s = str(task)
# This has something like the following in it:
# running at /Users/simonw/Dropbox/Development/datasette/venv-3.7.5/lib/python3.7/site-packages/uvicorn/main.py:361>
# Clean up everything up to and including site-packages
return _cleaner_task_str_re.sub("", s)
def wrap_view(view_fn_or_class, datasette):
is_function = isinstance(view_fn_or_class, types.FunctionType)
if is_function:
return wrap_view_function(view_fn_or_class, datasette)
else:
if not isinstance(view_fn_or_class, type):
raise ValueError("view_fn_or_class must be a function or a class")
return wrap_view_class(view_fn_or_class, datasette)
def wrap_view_class(view_class, datasette):
async def async_view_for_class(request, send):
instance = view_class()
if inspect.iscoroutinefunction(instance.__call__):
return await async_call_with_supported_arguments(
instance.__call__,
scope=request.scope,
receive=request.receive,
send=send,
request=request,
datasette=datasette,
)
else:
return call_with_supported_arguments(
instance.__call__,
scope=request.scope,
receive=request.receive,
send=send,
request=request,
datasette=datasette,
)
async_view_for_class.view_class = view_class
return async_view_for_class
def wrap_view_function(view_fn, datasette):
@functools.wraps(view_fn)
async def async_view_fn(request, send):
if inspect.iscoroutinefunction(view_fn):
response = await async_call_with_supported_arguments(
view_fn,
scope=request.scope,
receive=request.receive,
send=send,
request=request,
datasette=datasette,
)
else:
response = call_with_supported_arguments(
view_fn,
scope=request.scope,
receive=request.receive,
send=send,
request=request,
datasette=datasette,
)
if response is not None:
return response
return async_view_fn
def permanent_redirect(path, forward_query_string=False, forward_rest=False):
return wrap_view(
lambda request, send: Response.redirect(
path
+ (request.url_vars["rest"] if forward_rest else "")
+ (
("?" + request.query_string)
if forward_query_string and request.query_string
else ""
),
status=301,
),
datasette=None,
)
_curly_re = re.compile(r"({.*?})")
def route_pattern_from_filepath(filepath):
# Drop the ".html" suffix
if filepath.endswith(".html"):
filepath = filepath[: -len(".html")]
re_bits = ["/"]
for bit in _curly_re.split(filepath):
if _curly_re.match(bit):
re_bits.append(f"(?P<{bit[1:-1]}>[^/]*)")
else:
re_bits.append(re.escape(bit))
return re.compile("^" + "".join(re_bits) + "$")
class NotFoundExplicit(NotFound):
pass
class DatasetteClient:
"""Internal HTTP client for making requests to a Datasette instance.
Used for testing and for internal operations that need to make HTTP requests
to the Datasette app without going through an actual HTTP server.
"""
def __init__(self, ds):
self.ds = ds
@property
def app(self):
return self.ds.app()
def actor_cookie(self, actor):
# Utility method, mainly for tests
return self.ds.sign({"a": actor}, "actor")
def _fix(self, path, avoid_path_rewrites=False):
if not isinstance(path, PrefixedUrlString) and not avoid_path_rewrites:
path = self.ds.urls.path(path)
if path.startswith("/"):
path = f"http://localhost{path}"
return path
async def _request(self, method, path, skip_permission_checks=False, **kwargs):
from datasette.permissions import SkipPermissions
with _DatasetteClientContext():
if skip_permission_checks:
with SkipPermissions():
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=self.app),
cookies=kwargs.pop("cookies", None),
) as client:
return await getattr(client, method)(self._fix(path), **kwargs)
else:
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=self.app),
cookies=kwargs.pop("cookies", None),
) as client:
return await getattr(client, method)(self._fix(path), **kwargs)
async def get(self, path, skip_permission_checks=False, **kwargs):
return await self._request(
"get", path, skip_permission_checks=skip_permission_checks, **kwargs
)
async def options(self, path, skip_permission_checks=False, **kwargs):
return await self._request(
"options", path, skip_permission_checks=skip_permission_checks, **kwargs
)
async def head(self, path, skip_permission_checks=False, **kwargs):
return await self._request(
"head", path, skip_permission_checks=skip_permission_checks, **kwargs
)
async def post(self, path, skip_permission_checks=False, **kwargs):
return await self._request(
"post", path, skip_permission_checks=skip_permission_checks, **kwargs
)
async def put(self, path, skip_permission_checks=False, **kwargs):
return await self._request(
"put", path, skip_permission_checks=skip_permission_checks, **kwargs
)
async def patch(self, path, skip_permission_checks=False, **kwargs):
return await self._request(
"patch", path, skip_permission_checks=skip_permission_checks, **kwargs
)
async def delete(self, path, skip_permission_checks=False, **kwargs):
return await self._request(
"delete", path, skip_permission_checks=skip_permission_checks, **kwargs
)
async def request(self, method, path, skip_permission_checks=False, **kwargs):
"""Make an HTTP request with the specified method.
Args:
method: HTTP method (e.g., "GET", "POST", "PUT")
path: The path to request
skip_permission_checks: If True, bypass all permission checks for this request
**kwargs: Additional arguments to pass to httpx
Returns:
httpx.Response: The response from the request
"""
from datasette.permissions import SkipPermissions
avoid_path_rewrites = kwargs.pop("avoid_path_rewrites", None)
with _DatasetteClientContext():
if skip_permission_checks:
with SkipPermissions():
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=self.app),
cookies=kwargs.pop("cookies", None),
) as client:
return await client.request(
method, self._fix(path, avoid_path_rewrites), **kwargs
)
else:
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=self.app),
cookies=kwargs.pop("cookies", None),
) as client:
return await client.request(
method, self._fix(path, avoid_path_rewrites), **kwargs
)
================================================
FILE: datasette/blob_renderer.py
================================================
from datasette import hookimpl
from datasette.utils.asgi import Response, BadRequest
from datasette.utils import to_css_class
import hashlib
_BLOB_COLUMN = "_blob_column"
_BLOB_HASH = "_blob_hash"
async def render_blob(datasette, database, rows, columns, request, table, view_name):
if _BLOB_COLUMN not in request.args:
raise BadRequest(f"?{_BLOB_COLUMN}= is required")
blob_column = request.args[_BLOB_COLUMN]
if blob_column not in columns:
raise BadRequest(f"{blob_column} is not a valid column")
# If ?_blob_hash= provided, use that to select the row - otherwise use first row
blob_hash = None
if _BLOB_HASH in request.args:
blob_hash = request.args[_BLOB_HASH]
for row in rows:
value = row[blob_column]
if hashlib.sha256(value).hexdigest() == blob_hash:
break
else:
# Loop did not break
raise BadRequest(
"Link has expired - the requested binary content has changed or could not be found."
)
else:
row = rows[0]
value = row[blob_column]
filename_bits = []
if table:
filename_bits.append(to_css_class(table))
if "pks" in request.url_vars:
filename_bits.append(request.url_vars["pks"])
filename_bits.append(to_css_class(blob_column))
if blob_hash:
filename_bits.append(blob_hash[:6])
filename = "-".join(filename_bits) + ".blob"
headers = {
"X-Content-Type-Options": "nosniff",
"Content-Disposition": f'attachment; filename="{filename}"',
}
return Response(
body=value or b"",
status=200,
headers=headers,
content_type="application/binary",
)
@hookimpl
def register_output_renderer():
return {
"extension": "blob",
"render": render_blob,
"can_render": lambda: False,
}
================================================
FILE: datasette/cli.py
================================================
import asyncio
import uvicorn
import click
from click import formatting
from click.types import CompositeParamType
from click_default_group import DefaultGroup
import functools
import json
import os
import pathlib
from runpy import run_module
import shutil
from subprocess import call
import sys
import textwrap
import webbrowser
from .app import (
Datasette,
DEFAULT_SETTINGS,
SETTINGS,
SQLITE_LIMIT_ATTACHED,
pm,
)
from .utils import (
LoadExtension,
StartupError,
check_connection,
deep_dict_update,
find_spatialite,
parse_metadata,
ConnectionProblem,
SpatialiteConnectionProblem,
initial_path_for_datasette,
pairs_to_nested_config,
temporary_docker_directory,
value_as_boolean,
SpatialiteNotFound,
StaticMount,
ValueAsBooleanError,
)
from .utils.sqlite import sqlite3
from .utils.testing import TestClient
from .version import __version__
def run_sync(coro_func):
"""Run an async callable to completion on a fresh event loop."""
loop = asyncio.new_event_loop()
try:
asyncio.set_event_loop(loop)
return loop.run_until_complete(coro_func())
finally:
asyncio.set_event_loop(None)
loop.close()
# Use Rich for tracebacks if it is installed
try:
from rich.traceback import install
install(show_locals=True)
except ImportError:
pass
class Setting(CompositeParamType):
name = "setting"
arity = 2
def convert(self, config, param, ctx):
name, value = config
if name in DEFAULT_SETTINGS:
# For backwards compatibility with how this worked prior to
# Datasette 1.0, we turn bare setting names into setting.name
# Type checking for those older settings
default = DEFAULT_SETTINGS[name]
name = "settings.{}".format(name)
if isinstance(default, bool):
try:
return name, "true" if value_as_boolean(value) else "false"
except ValueAsBooleanError:
self.fail(f'"{name}" should be on/off/true/false/1/0', param, ctx)
elif isinstance(default, int):
if not value.isdigit():
self.fail(f'"{name}" should be an integer', param, ctx)
return name, value
elif isinstance(default, str):
return name, value
else:
# Should never happen:
self.fail("Invalid option")
return name, value
def sqlite_extensions(fn):
fn = click.option(
"sqlite_extensions",
"--load-extension",
type=LoadExtension(),
envvar="DATASETTE_LOAD_EXTENSION",
multiple=True,
help="Path to a SQLite extension to load, and optional entrypoint",
)(fn)
# Wrap it in a custom error handler
@functools.wraps(fn)
def wrapped(*args, **kwargs):
try:
return fn(*args, **kwargs)
except AttributeError as e:
if "enable_load_extension" in str(e):
raise click.ClickException(textwrap.dedent("""
Your Python installation does not have the ability to load SQLite extensions.
More information: https://datasette.io/help/extensions
""").strip())
raise
return wrapped
@click.group(cls=DefaultGroup, default="serve", default_if_no_args=True)
@click.version_option(version=__version__)
def cli():
"""
Datasette is an open source multi-tool for exploring and publishing data
\b
About Datasette: https://datasette.io/
Full documentation: https://docs.datasette.io/
"""
@cli.command()
@click.argument("files", type=click.Path(exists=True), nargs=-1)
@click.option("--inspect-file", default="-")
@sqlite_extensions
def inspect(files, inspect_file, sqlite_extensions):
"""
Generate JSON summary of provided database files
This can then be passed to "datasette --inspect-file" to speed up count
operations against immutable database files.
"""
inspect_data = run_sync(lambda: inspect_(files, sqlite_extensions))
if inspect_file == "-":
sys.stdout.write(json.dumps(inspect_data, indent=2))
else:
with open(inspect_file, "w") as fp:
fp.write(json.dumps(inspect_data, indent=2))
async def inspect_(files, sqlite_extensions):
app = Datasette([], immutables=files, sqlite_extensions=sqlite_extensions)
data = {}
for name, database in app.databases.items():
counts = await database.table_counts(limit=3600 * 1000)
data[name] = {
"hash": database.hash,
"size": database.size,
"file": database.path,
"tables": {
table_name: {"count": table_count}
for table_name, table_count in counts.items()
},
}
return data
@cli.group()
def publish():
"""Publish specified SQLite database files to the internet along with a Datasette-powered interface and API"""
pass
# Register publish plugins
pm.hook.publish_subcommand(publish=publish)
@cli.command()
@click.option("--all", help="Include built-in default plugins", is_flag=True)
@click.option(
"--requirements", help="Output requirements.txt of installed plugins", is_flag=True
)
@click.option(
"--plugins-dir",
type=click.Path(exists=True, file_okay=False, dir_okay=True),
help="Path to directory containing custom plugins",
)
def plugins(all, requirements, plugins_dir):
"""List currently installed plugins"""
app = Datasette([], plugins_dir=plugins_dir)
if requirements:
for plugin in app._plugins():
if plugin["version"]:
click.echo("{}=={}".format(plugin["name"], plugin["version"]))
else:
click.echo(json.dumps(app._plugins(all=all), indent=4))
@cli.command()
@click.argument("files", type=click.Path(exists=True), nargs=-1, required=True)
@click.option(
"-t",
"--tag",
help="Name for the resulting Docker container, can optionally use name:tag format",
)
@click.option(
"-m",
"--metadata",
type=click.File(mode="r"),
help="Path to JSON/YAML file containing metadata to publish",
)
@click.option("--extra-options", help="Extra options to pass to datasette serve")
@click.option("--branch", help="Install datasette from a GitHub branch e.g. main")
@click.option(
"--template-dir",
type=click.Path(exists=True, file_okay=False, dir_okay=True),
help="Path to directory containing custom templates",
)
@click.option(
"--plugins-dir",
type=click.Path(exists=True, file_okay=False, dir_okay=True),
help="Path to directory containing custom plugins",
)
@click.option(
"--static",
type=StaticMount(),
help="Serve static files from this directory at /MOUNT/...",
multiple=True,
)
@click.option(
"--install", help="Additional packages (e.g. plugins) to install", multiple=True
)
@click.option("--spatialite", is_flag=True, help="Enable SpatialLite extension")
@click.option("--version-note", help="Additional note to show on /-/versions")
@click.option(
"--secret",
help="Secret used for signing secure values, such as signed cookies",
envvar="DATASETTE_PUBLISH_SECRET",
default=lambda: os.urandom(32).hex(),
)
@click.option(
"-p",
"--port",
default=8001,
type=click.IntRange(1, 65535),
help="Port to run the server on, defaults to 8001",
)
@click.option("--title", help="Title for metadata")
@click.option("--license", help="License label for metadata")
@click.option("--license_url", help="License URL for metadata")
@click.option("--source", help="Source label for metadata")
@click.option("--source_url", help="Source URL for metadata")
@click.option("--about", help="About label for metadata")
@click.option("--about_url", help="About URL for metadata")
def package(
files,
tag,
metadata,
extra_options,
branch,
template_dir,
plugins_dir,
static,
install,
spatialite,
version_note,
secret,
port,
**extra_metadata,
):
"""Package SQLite files into a Datasette Docker container"""
if not shutil.which("docker"):
click.secho(
' The package command requires "docker" to be installed and configured ',
bg="red",
fg="white",
bold=True,
err=True,
)
sys.exit(1)
with temporary_docker_directory(
files,
"datasette",
metadata=metadata,
extra_options=extra_options,
branch=branch,
template_dir=template_dir,
plugins_dir=plugins_dir,
static=static,
install=install,
spatialite=spatialite,
version_note=version_note,
secret=secret,
extra_metadata=extra_metadata,
port=port,
):
args = ["docker", "build"]
if tag:
args.append("-t")
args.append(tag)
args.append(".")
call(args)
@cli.command()
@click.argument("packages", nargs=-1)
@click.option(
"-U", "--upgrade", is_flag=True, help="Upgrade packages to latest version"
)
@click.option(
"-r",
"--requirement",
type=click.Path(exists=True),
help="Install from requirements file",
)
@click.option(
"-e",
"--editable",
help="Install a project in editable mode from this path",
)
def install(packages, upgrade, requirement, editable):
"""Install plugins and packages from PyPI into the same environment as Datasette"""
if not packages and not requirement and not editable:
raise click.UsageError("Please specify at least one package to install")
args = ["pip", "install"]
if upgrade:
args += ["--upgrade"]
if editable:
args += ["--editable", editable]
if requirement:
args += ["-r", requirement]
args += list(packages)
sys.argv = args
run_module("pip", run_name="__main__")
@cli.command()
@click.argument("packages", nargs=-1, required=True)
@click.option("-y", "--yes", is_flag=True, help="Don't ask for confirmation")
def uninstall(packages, yes):
"""Uninstall plugins and Python packages from the Datasette environment"""
sys.argv = ["pip", "uninstall"] + list(packages) + (["-y"] if yes else [])
run_module("pip", run_name="__main__")
@cli.command()
@click.argument("files", type=click.Path(), nargs=-1)
@click.option(
"-i",
"--immutable",
type=click.Path(exists=True),
help="Database files to open in immutable mode",
multiple=True,
)
@click.option(
"-h",
"--host",
default="127.0.0.1",
help=(
"Host for server. Defaults to 127.0.0.1 which means only connections "
"from the local machine will be allowed. Use 0.0.0.0 to listen to "
"all IPs and allow access from other machines."
),
)
@click.option(
"-p",
"--port",
default=8001,
type=click.IntRange(0, 65535),
help="Port for server, defaults to 8001. Use -p 0 to automatically assign an available port.",
)
@click.option(
"--uds",
help="Bind to a Unix domain socket",
)
@click.option(
"--reload",
is_flag=True,
help="Automatically reload if code or metadata change detected - useful for development",
)
@click.option(
"--cors", is_flag=True, help="Enable CORS by serving Access-Control-Allow-Origin: *"
)
@sqlite_extensions
@click.option(
"--inspect-file", help='Path to JSON file created using "datasette inspect"'
)
@click.option(
"-m",
"--metadata",
type=click.File(mode="r"),
help="Path to JSON/YAML file containing license/source metadata",
)
@click.option(
"--template-dir",
type=click.Path(exists=True, file_okay=False, dir_okay=True),
help="Path to directory containing custom templates",
)
@click.option(
"--plugins-dir",
type=click.Path(exists=True, file_okay=False, dir_okay=True),
help="Path to directory containing custom plugins",
)
@click.option(
"--static",
type=StaticMount(),
help="Serve static files from this directory at /MOUNT/...",
multiple=True,
)
@click.option("--memory", is_flag=True, help="Make /_memory database available")
@click.option(
"-c",
"--config",
type=click.File(mode="r"),
help="Path to JSON/YAML Datasette configuration file",
)
@click.option(
"-s",
"--setting",
"settings",
type=Setting(),
help="nested.key, value setting to use in Datasette configuration",
multiple=True,
)
@click.option(
"--secret",
help="Secret used for signing secure values, such as signed cookies",
envvar="DATASETTE_SECRET",
)
@click.option(
"--root",
help="Output URL that sets a cookie authenticating the root user",
is_flag=True,
)
@click.option(
"--default-deny",
help="Deny all permissions by default",
is_flag=True,
)
@click.option(
"--get",
help="Run an HTTP GET request against this path, print results and exit",
)
@click.option(
"--headers",
is_flag=True,
help="Include HTTP headers in --get output",
)
@click.option(
"--token",
help="API token to send with --get requests",
)
@click.option(
"--actor",
help="Actor to use for --get requests (JSON string)",
)
@click.option("--version-note", help="Additional note to show on /-/versions")
@click.option("--help-settings", is_flag=True, help="Show available settings")
@click.option("--pdb", is_flag=True, help="Launch debugger on any errors")
@click.option(
"-o",
"--open",
"open_browser",
is_flag=True,
help="Open Datasette in your web browser",
)
@click.option(
"--create",
is_flag=True,
help="Create database files if they do not exist",
)
@click.option(
"--crossdb",
is_flag=True,
help="Enable cross-database joins using the /_memory database",
)
@click.option(
"--nolock",
is_flag=True,
help="Ignore locking, open locked files in read-only mode",
)
@click.option(
"--ssl-keyfile",
help="SSL key file",
envvar="DATASETTE_SSL_KEYFILE",
)
@click.option(
"--ssl-certfile",
help="SSL certificate file",
envvar="DATASETTE_SSL_CERTFILE",
)
@click.option(
"--internal",
type=click.Path(),
help="Path to a persistent Datasette internal SQLite database",
)
def serve(
files,
immutable,
host,
port,
uds,
reload,
cors,
sqlite_extensions,
inspect_file,
metadata,
template_dir,
plugins_dir,
static,
memory,
config,
settings,
secret,
root,
default_deny,
get,
headers,
token,
actor,
version_note,
help_settings,
pdb,
open_browser,
create,
crossdb,
nolock,
ssl_keyfile,
ssl_certfile,
internal,
return_instance=False,
):
"""Serve up specified SQLite database files with a web UI"""
if help_settings:
formatter = formatting.HelpFormatter()
with formatter.section("Settings"):
formatter.write_dl(
[
(option.name, f"{option.help} (default={option.default})")
for option in SETTINGS
]
)
click.echo(formatter.getvalue())
sys.exit(0)
if reload:
import hupper
reloader = hupper.start_reloader("datasette.cli.cli")
if immutable:
reloader.watch_files(immutable)
if config:
reloader.watch_files([config.name])
if metadata:
reloader.watch_files([metadata.name])
inspect_data = None
if inspect_file:
with open(inspect_file) as fp:
inspect_data = json.load(fp)
metadata_data = None
if metadata:
metadata_data = parse_metadata(metadata.read())
config_data = None
if config:
config_data = parse_metadata(config.read())
config_data = config_data or {}
# Merge in settings from -s/--setting
if settings:
settings_updates = pairs_to_nested_config(settings)
# Merge recursively, to avoid over-writing nested values
# https://github.com/simonw/datasette/issues/2389
deep_dict_update(config_data, settings_updates)
kwargs = dict(
immutables=immutable,
cache_headers=not reload,
cors=cors,
inspect_data=inspect_data,
config=config_data,
metadata=metadata_data,
sqlite_extensions=sqlite_extensions,
template_dir=template_dir,
plugins_dir=plugins_dir,
static_mounts=static,
settings=None, # These are passed in config= now
memory=memory,
secret=secret,
version_note=version_note,
pdb=pdb,
crossdb=crossdb,
nolock=nolock,
internal=internal,
default_deny=default_deny,
)
# Separate directories from files
directories = [f for f in files if os.path.isdir(f)]
file_paths = [f for f in files if not os.path.isdir(f)]
# Handle config_dir - only one directory allowed
if len(directories) > 1:
raise click.ClickException(
"Cannot pass multiple directories. Pass a single directory as config_dir."
)
elif len(directories) == 1:
kwargs["config_dir"] = pathlib.Path(directories[0])
# Verify list of files, create if needed (and --create)
for file in file_paths:
if not pathlib.Path(file).exists():
if create:
sqlite3.connect(file).execute("vacuum")
else:
raise click.ClickException(
"Invalid value for '[FILES]...': Path '{}' does not exist.".format(
file
)
)
# Check for duplicate files by resolving all paths to their absolute forms
# Collect all database files that will be loaded (explicit files + config_dir files)
all_db_files = []
# Add explicit files
for file in file_paths:
all_db_files.append((file, pathlib.Path(file).resolve()))
# Add config_dir databases if config_dir is set
if "config_dir" in kwargs:
config_dir = kwargs["config_dir"]
for ext in ("db", "sqlite", "sqlite3"):
for db_file in config_dir.glob(f"*.{ext}"):
all_db_files.append((str(db_file), db_file.resolve()))
# Check for duplicates
seen = {}
for original_path, resolved_path in all_db_files:
if resolved_path in seen:
raise click.ClickException(
f"Duplicate database file: '{original_path}' and '{seen[resolved_path]}' "
f"both refer to {resolved_path}"
)
seen[resolved_path] = original_path
files = file_paths
try:
ds = Datasette(files, **kwargs)
except SpatialiteNotFound:
raise click.ClickException("Could not find SpatiaLite extension")
except StartupError as e:
raise click.ClickException(e.args[0])
if return_instance:
# Private utility mechanism for writing unit tests
return ds
# Run async soundness checks before startup hooks, since invoke_startup
# now populates internal tables which requires querying each database
run_sync(lambda: check_databases(ds))
# Run the "startup" plugin hooks
try:
run_sync(ds.invoke_startup)
except StartupError as e:
raise click.ClickException(e.args[0])
if headers and not get:
raise click.ClickException("--headers can only be used with --get")
if token and not get:
raise click.ClickException("--token can only be used with --get")
if get:
client = TestClient(ds)
request_headers = {}
if token:
request_headers["Authorization"] = "Bearer {}".format(token)
cookies = {}
if actor:
cookies["ds_actor"] = client.actor_cookie(json.loads(actor))
response = client.get(get, headers=request_headers, cookies=cookies)
if headers:
# Output HTTP status code, headers, two newlines, then the response body
click.echo(f"HTTP/1.1 {response.status}")
for key, value in response.headers.items():
click.echo(f"{key}: {value}")
if response.text:
click.echo()
click.echo(response.text)
else:
click.echo(response.text)
exit_code = 0 if response.status == 200 else 1
sys.exit(exit_code)
return
# Start the server
url = None
if root:
ds.root_enabled = True
url = "http://{}:{}{}?token={}".format(
host, port, ds.urls.path("-/auth-token"), ds._root_token
)
click.echo(url)
if open_browser:
if url is None:
# Figure out most convenient URL - to table, database or homepage
path = run_sync(lambda: initial_path_for_datasette(ds))
url = f"http://{host}:{port}{path}"
webbrowser.open(url)
uvicorn_kwargs = dict(
host=host, port=port, log_level="info", lifespan="on", workers=1
)
if uds:
uvicorn_kwargs["uds"] = uds
if ssl_keyfile:
uvicorn_kwargs["ssl_keyfile"] = ssl_keyfile
if ssl_certfile:
uvicorn_kwargs["ssl_certfile"] = ssl_certfile
uvicorn.run(ds.app(), **uvicorn_kwargs)
@cli.command()
@click.argument("id")
@click.option(
"--secret",
help="Secret used for signing the API tokens",
envvar="DATASETTE_SECRET",
required=True,
)
@click.option(
"-e",
"--expires-after",
help="Token should expire after this many seconds",
type=int,
)
@click.option(
"alls",
"-a",
"--all",
type=str,
metavar="ACTION",
multiple=True,
help="Restrict token to this action",
)
@click.option(
"databases",
"-d",
"--database",
type=(str, str),
metavar="DB ACTION",
multiple=True,
help="Restrict token to this action on this database",
)
@click.option(
"resources",
"-r",
"--resource",
type=(str, str, str),
metavar="DB RESOURCE ACTION",
multiple=True,
help="Restrict token to this action on this database resource (a table, SQL view or named query)",
)
@click.option(
"--debug",
help="Show decoded token",
is_flag=True,
)
@click.option(
"--plugins-dir",
type=click.Path(exists=True, file_okay=False, dir_okay=True),
help="Path to directory containing custom plugins",
)
def create_token(
id, secret, expires_after, alls, databases, resources, debug, plugins_dir
):
"""
Create a signed API token for the specified actor ID
Example:
datasette create-token root --secret mysecret
To allow only "view-database-download" for all databases:
\b
datasette create-token root --secret mysecret \\
--all view-database-download
To allow "create-table" against a specific database:
\b
datasette create-token root --secret mysecret \\
--database mydb create-table
To allow "insert-row" against a specific table:
\b
datasette create-token root --secret myscret \\
--resource mydb mytable insert-row
Restricted actions can be specified multiple times using
multiple --all, --database, and --resource options.
Add --debug to see a decoded version of the token.
"""
ds = Datasette(secret=secret, plugins_dir=plugins_dir)
# Run ds.invoke_startup() in an event loop
try:
run_sync(ds.invoke_startup)
except StartupError as e:
raise click.ClickException(e.args[0])
# Warn about any unknown actions
actions = []
actions.extend(alls)
actions.extend([p[1] for p in databases])
actions.extend([p[2] for p in resources])
for action in actions:
if not ds.actions.get(action):
click.secho(
f" Unknown permission: {action} ",
fg="red",
err=True,
)
from datasette.tokens import TokenRestrictions
restrictions = TokenRestrictions()
for action in alls:
restrictions.allow_all(action)
for database, action in databases:
restrictions.allow_database(database, action)
for database, resource, action in resources:
restrictions.allow_resource(database, resource, action)
token = run_sync(
lambda: ds.create_token(
id,
expires_after=expires_after,
restrictions=restrictions,
handler="signed",
)
)
click.echo(token)
if debug:
encoded = token[len("dstok_") :]
click.echo("\nDecoded:\n")
click.echo(json.dumps(ds.unsign(encoded, namespace="token"), indent=2))
pm.hook.register_commands(cli=cli)
async def check_databases(ds):
# Run check_connection against every connected database
# to confirm they are all usable
for database in list(ds.databases.values()):
try:
await database.execute_fn(check_connection)
except SpatialiteConnectionProblem:
suggestion = ""
try:
find_spatialite()
suggestion = "\n\nTry adding the --load-extension=spatialite option."
except SpatialiteNotFound:
pass
raise click.UsageError(
"It looks like you're trying to load a SpatiaLite"
+ " database without first loading the SpatiaLite module."
+ suggestion
+ "\n\nRead more: https://docs.datasette.io/en/stable/spatialite.html"
)
except ConnectionProblem as e:
raise click.UsageError(
f"Connection to {database.path} failed check: {str(e.args[0])}"
)
# If --crossdb and more than SQLITE_LIMIT_ATTACHED show warning
if (
ds.crossdb
and len([db for db in ds.databases.values() if not db.is_memory])
> SQLITE_LIMIT_ATTACHED
):
msg = (
"Warning: --crossdb only works with the first {} attached databases".format(
SQLITE_LIMIT_ATTACHED
)
)
click.echo(click.style(msg, bold=True, fg="yellow"), err=True)
================================================
FILE: datasette/column_types.py
================================================
from enum import Enum
class SQLiteType(Enum):
TEXT = "TEXT"
INTEGER = "INTEGER"
REAL = "REAL"
BLOB = "BLOB"
NULL = "NULL"
@classmethod
def from_declared_type(cls, declared_type: str | None) -> "SQLiteType | None":
if declared_type is None:
return cls.NULL
normalized = declared_type.strip().upper()
if not normalized:
return cls.NULL
if normalized == cls.NULL.value:
return cls.NULL
if "INT" in normalized:
return cls.INTEGER
if any(token in normalized for token in ("CHAR", "CLOB", "TEXT")):
return cls.TEXT
if "BLOB" in normalized:
return cls.BLOB
if any(
token in normalized
for token in ("REAL", "FLOA", "DOUB") # codespell:ignore doub
):
return cls.REAL
return None
class ColumnType:
"""
Base class for column types.
Subclasses must define ``name`` and ``description`` as class attributes:
- ``name``: Unique identifier string. Lowercase, no spaces.
Examples: "markdown", "file", "email", "url", "point", "image".
- ``description``: Human-readable label for admin UI dropdowns.
Examples: "Markdown text", "File reference", "Email address".
- ``sqlite_types``: Optional tuple of SQLiteType values restricting
which SQLite column types this ColumnType can be assigned to.
Instantiate with an optional ``config`` dict to bind per-column
configuration::
ct = MyColumnType(config={"key": "value"})
ct.config # {"key": "value"}
"""
name: str
description: str
sqlite_types: tuple[SQLiteType, ...] | None = None
def __init__(self, config=None):
self.config = config
async def render_cell(self, value, column, table, database, datasette, request):
"""
Return an HTML string to render this cell value, or None to
fall through to the default render_cell plugin hook chain.
"""
return None
async def validate(self, value, datasette):
"""
Validate a value before it is written. Return None if valid,
or a string error message if invalid.
"""
return None
async def transform_value(self, value, datasette):
"""
Transform a value before it appears in JSON API output.
Return the transformed value. Default: return unchanged.
"""
return value
================================================
FILE: datasette/database.py
================================================
import asyncio
from collections import namedtuple
from pathlib import Path
import janus
import queue
import sqlite_utils
import sys
import threading
import uuid
from .tracer import trace
from .utils import (
detect_fts,
detect_primary_keys,
detect_spatialite,
get_all_foreign_keys,
get_outbound_foreign_keys,
md5_not_usedforsecurity,
sqlite_timelimit,
sqlite3,
table_columns,
table_column_details,
)
from .utils.sqlite import sqlite_version
from .inspect import inspect_hash
connections = threading.local()
AttachedDatabase = namedtuple("AttachedDatabase", ("seq", "name", "file"))
class Database:
# For table counts stop at this many rows:
count_limit = 10000
_thread_local_id_counter = 1
def __init__(
self,
ds,
path=None,
is_mutable=True,
is_memory=False,
memory_name=None,
mode=None,
):
self.name = None
self._thread_local_id = f"x{self._thread_local_id_counter}"
Database._thread_local_id_counter += 1
self.route = None
self.ds = ds
self.path = path
self.is_mutable = is_mutable
self.is_memory = is_memory
self.memory_name = memory_name
if memory_name is not None:
self.is_memory = True
self.cached_hash = None
self.cached_size = None
self._cached_table_counts = None
self._write_thread = None
self._write_queue = None
# These are used when in non-threaded mode:
self._read_connection = None
self._write_connection = None
# This is used to track all file connections so they can be closed
self._all_file_connections = []
self.mode = mode
@property
def cached_table_counts(self):
if self._cached_table_counts is not None:
return self._cached_table_counts
# Maybe use self.ds.inspect_data to populate cached_table_counts
if self.ds.inspect_data and self.ds.inspect_data.get(self.name):
self._cached_table_counts = {
key: value["count"]
for key, value in self.ds.inspect_data[self.name]["tables"].items()
}
return self._cached_table_counts
@property
def color(self):
if self.hash:
return self.hash[:6]
return md5_not_usedforsecurity(self.name)[:6]
def suggest_name(self):
if self.path:
return Path(self.path).stem
elif self.memory_name:
return self.memory_name
else:
return "db"
def connect(self, write=False):
extra_kwargs = {}
if write:
extra_kwargs["isolation_level"] = "IMMEDIATE"
if self.memory_name:
uri = "file:{}?mode=memory&cache=shared".format(self.memory_name)
conn = sqlite3.connect(
uri, uri=True, check_same_thread=False, **extra_kwargs
)
if not write:
conn.execute("PRAGMA query_only=1")
return conn
if self.is_memory:
return sqlite3.connect(":memory:", uri=True)
# mode=ro or immutable=1?
if self.is_mutable:
qs = "?mode=ro"
if self.ds.nolock:
qs += "&nolock=1"
else:
qs = "?immutable=1"
assert not (write and not self.is_mutable)
if write:
qs = ""
if self.mode is not None:
qs = f"?mode={self.mode}"
conn = sqlite3.connect(
f"file:{self.path}{qs}", uri=True, check_same_thread=False, **extra_kwargs
)
self._all_file_connections.append(conn)
return conn
def close(self):
# Close all connections - useful to avoid running out of file handles in tests
for connection in self._all_file_connections:
connection.close()
async def execute_write(self, sql, params=None, block=True, request=None):
def _inner(conn):
return conn.execute(sql, params or [])
with trace("sql", database=self.name, sql=sql.strip(), params=params):
results = await self.execute_write_fn(_inner, block=block, request=request)
return results
async def execute_write_script(self, sql, block=True, request=None):
def _inner(conn):
return conn.executescript(sql)
with trace("sql", database=self.name, sql=sql.strip(), executescript=True):
results = await self.execute_write_fn(
_inner, block=block, transaction=False, request=request
)
return results
async def execute_write_many(self, sql, params_seq, block=True, request=None):
def _inner(conn):
count = 0
def count_params(params):
nonlocal count
for param in params:
count += 1
yield param
return conn.executemany(sql, count_params(params_seq)), count
with trace(
"sql", database=self.name, sql=sql.strip(), executemany=True
) as kwargs:
results, count = await self.execute_write_fn(
_inner, block=block, request=request
)
kwargs["count"] = count
return results
async def execute_isolated_fn(self, fn):
# Open a new connection just for the duration of this function
# blocking the write queue to avoid any writes occurring during it
if self.ds.executor is None:
# non-threaded mode
isolated_connection = self.connect(write=True)
try:
result = fn(isolated_connection)
finally:
isolated_connection.close()
try:
self._all_file_connections.remove(isolated_connection)
except ValueError:
# Was probably a memory connection
pass
return result
else:
# Threaded mode - send to write thread
return await self._send_to_write_thread(fn, isolated_connection=True)
async def execute_write_fn(self, fn, block=True, transaction=True, request=None):
fn = self._wrap_fn_with_hooks(fn, request, transaction)
if self.ds.executor is None:
# non-threaded mode
if self._write_connection is None:
self._write_connection = self.connect(write=True)
self.ds._prepare_connection(self._write_connection, self.name)
if transaction:
with self._write_connection:
return fn(self._write_connection)
else:
return fn(self._write_connection)
else:
return await self._send_to_write_thread(
fn, block=block, transaction=transaction
)
def _wrap_fn_with_hooks(self, fn, request, transaction):
from .plugins import pm
wrappers = pm.hook.write_wrapper(
datasette=self.ds,
database=self.name,
request=request,
transaction=transaction,
)
wrappers = [w for w in wrappers if w is not None]
if not wrappers:
return fn
# Build the wrapped fn by nesting context manager generators.
# The first wrapper returned by pluggy is outermost.
original_fn = fn
for wrapper_factory in reversed(wrappers):
original_fn = _apply_write_wrapper(original_fn, wrapper_factory)
return original_fn
async def _send_to_write_thread(
self, fn, block=True, isolated_connection=False, transaction=True
):
if self._write_queue is None:
self._write_queue = queue.Queue()
if self._write_thread is None:
self._write_thread = threading.Thread(
target=self._execute_writes, daemon=True
)
self._write_thread.name = "_execute_writes for database {}".format(
self.name
)
self._write_thread.start()
task_id = uuid.uuid5(uuid.NAMESPACE_DNS, "datasette.io")
reply_queue = janus.Queue()
self._write_queue.put(
WriteTask(fn, task_id, reply_queue, isolated_connection, transaction)
)
if block:
result = await reply_queue.async_q.get()
if isinstance(result, Exception):
raise result
else:
return result
else:
return task_id
def _execute_writes(self):
# Infinite looping thread that protects the single write connection
# to this database
conn_exception = None
conn = None
try:
conn = self.connect(write=True)
self.ds._prepare_connection(conn, self.name)
except Exception as e:
conn_exception = e
while True:
task = self._write_queue.get()
if conn_exception is not None:
result = conn_exception
else:
if task.isolated_connection:
isolated_connection = self.connect(write=True)
try:
result = task.fn(isolated_connection)
except Exception as e:
sys.stderr.write("{}\n".format(e))
sys.stderr.flush()
result = e
finally:
isolated_connection.close()
try:
self._all_file_connections.remove(isolated_connection)
except ValueError:
# Was probably a memory connection
pass
else:
try:
if task.transaction:
with conn:
result = task.fn(conn)
else:
result = task.fn(conn)
except Exception as e:
sys.stderr.write("{}\n".format(e))
sys.stderr.flush()
result = e
task.reply_queue.sync_q.put(result)
async def execute_fn(self, fn):
if self.ds.executor is None:
# non-threaded mode
if self._read_connection is None:
self._read_connection = self.connect()
self.ds._prepare_connection(self._read_connection, self.name)
return fn(self._read_connection)
# threaded mode
def in_thread():
conn = getattr(connections, self._thread_local_id, None)
if not conn:
conn = self.connect()
self.ds._prepare_connection(conn, self.name)
setattr(connections, self._thread_local_id, conn)
return fn(conn)
return await asyncio.get_event_loop().run_in_executor(
self.ds.executor, in_thread
)
async def execute(
self,
sql,
params=None,
truncate=False,
custom_time_limit=None,
page_size=None,
log_sql_errors=True,
):
"""Executes sql against db_name in a thread"""
page_size = page_size or self.ds.page_size
def sql_operation_in_thread(conn):
time_limit_ms = self.ds.sql_time_limit_ms
if custom_time_limit and custom_time_limit < time_limit_ms:
time_limit_ms = custom_time_limit
with sqlite_timelimit(conn, time_limit_ms):
try:
cursor = conn.cursor()
cursor.execute(sql, params if params is not None else {})
max_returned_rows = self.ds.max_returned_rows
if max_returned_rows == page_size:
max_returned_rows += 1
if max_returned_rows and truncate:
rows = cursor.fetchmany(max_returned_rows + 1)
truncated = len(rows) > max_returned_rows
rows = rows[:max_returned_rows]
else:
rows = cursor.fetchall()
truncated = False
except (sqlite3.OperationalError, sqlite3.DatabaseError) as e:
if e.args == ("interrupted",):
raise QueryInterrupted(e, sql, params)
if log_sql_errors:
sys.stderr.write(
"ERROR: conn={}, sql = {}, params = {}: {}\n".format(
conn, repr(sql), params, e
)
)
sys.stderr.flush()
raise
if truncate:
return Results(rows, truncated, cursor.description)
else:
return Results(rows, False, cursor.description)
with trace("sql", database=self.name, sql=sql.strip(), params=params):
results = await self.execute_fn(sql_operation_in_thread)
return results
@property
def hash(self):
if self.cached_hash is not None:
return self.cached_hash
elif self.is_mutable or self.is_memory:
return None
elif self.ds.inspect_data and self.ds.inspect_data.get(self.name):
self.cached_hash = self.ds.inspect_data[self.name]["hash"]
return self.cached_hash
else:
p = Path(self.path)
self.cached_hash = inspect_hash(p)
return self.cached_hash
@property
def size(self):
if self.cached_size is not None:
return self.cached_size
elif self.is_memory:
return 0
elif self.is_mutable:
return Path(self.path).stat().st_size
elif self.ds.inspect_data and self.ds.inspect_data.get(self.name):
self.cached_size = self.ds.inspect_data[self.name]["size"]
return self.cached_size
else:
self.cached_size = Path(self.path).stat().st_size
return self.cached_size
async def table_counts(self, limit=10):
if not self.is_mutable and self.cached_table_counts is not None:
return self.cached_table_counts
# Try to get counts for each table, $limit timeout for each count
counts = {}
for table in await self.table_names():
try:
table_count = (
await self.execute(
f"select count(*) from (select * from [{table}] limit {self.count_limit + 1})",
custom_time_limit=limit,
)
).rows[0][0]
counts[table] = table_count
# In some cases I saw "SQL Logic Error" here in addition to
# QueryInterrupted - so we catch that too:
except (QueryInterrupted, sqlite3.OperationalError, sqlite3.DatabaseError):
counts[table] = None
if not self.is_mutable:
self._cached_table_counts = counts
return counts
@property
def mtime_ns(self):
if self.is_memory:
retu
gitextract_10flwja5/
├── .coveragerc
├── .dockerignore
├── .git-blame-ignore-revs
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── deploy-branch-preview.yml
│ ├── deploy-latest.yml
│ ├── documentation-links.yml
│ ├── prettier.yml
│ ├── publish.yml
│ ├── push_docker_tag.yml
│ ├── spellcheck.yml
│ ├── stable-docs.yml
│ ├── test-coverage.yml
│ ├── test-pyodide.yml
│ ├── test-sqlite-support.yml
│ ├── test.yml
│ ├── tmate-mac.yml
│ └── tmate.yml
├── .gitignore
├── .isort.cfg
├── .prettierrc
├── .readthedocs.yaml
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── Justfile
├── LICENSE
├── MANIFEST.in
├── README.md
├── codecov.yml
├── datasette/
│ ├── __init__.py
│ ├── __main__.py
│ ├── actor_auth_cookie.py
│ ├── app.py
│ ├── blob_renderer.py
│ ├── cli.py
│ ├── column_types.py
│ ├── database.py
│ ├── default_actions.py
│ ├── default_column_types.py
│ ├── default_magic_parameters.py
│ ├── default_menu_links.py
│ ├── default_permissions/
│ │ ├── __init__.py
│ │ ├── config.py
│ │ ├── defaults.py
│ │ ├── helpers.py
│ │ ├── restrictions.py
│ │ ├── root.py
│ │ └── tokens.py
│ ├── events.py
│ ├── facets.py
│ ├── filters.py
│ ├── forbidden.py
│ ├── handle_exception.py
│ ├── hookspecs.py
│ ├── inspect.py
│ ├── permissions.py
│ ├── plugins.py
│ ├── publish/
│ │ ├── __init__.py
│ │ ├── cloudrun.py
│ │ ├── common.py
│ │ └── heroku.py
│ ├── renderer.py
│ ├── resources.py
│ ├── sql_functions.py
│ ├── static/
│ │ ├── app.css
│ │ ├── cm-editor-6.0.1.bundle.js
│ │ ├── cm-editor-6.0.1.js
│ │ ├── column-chooser.js
│ │ ├── datasette-manager.js
│ │ ├── json-format-highlight-1.0.1.js
│ │ ├── mobile-column-actions.js
│ │ ├── navigation-search.js
│ │ └── table.js
│ ├── templates/
│ │ ├── _action_menu.html
│ │ ├── _close_open_menus.html
│ │ ├── _codemirror.html
│ │ ├── _codemirror_foot.html
│ │ ├── _crumbs.html
│ │ ├── _debug_common_functions.html
│ │ ├── _description_source_license.html
│ │ ├── _facet_results.html
│ │ ├── _footer.html
│ │ ├── _permission_ui_styles.html
│ │ ├── _permissions_debug_tabs.html
│ │ ├── _suggested_facets.html
│ │ ├── _table.html
│ │ ├── allow_debug.html
│ │ ├── api_explorer.html
│ │ ├── base.html
│ │ ├── create_token.html
│ │ ├── csrf_error.html
│ │ ├── database.html
│ │ ├── debug_actions.html
│ │ ├── debug_allowed.html
│ │ ├── debug_check.html
│ │ ├── debug_permissions_playground.html
│ │ ├── debug_rules.html
│ │ ├── error.html
│ │ ├── index.html
│ │ ├── logout.html
│ │ ├── messages_debug.html
│ │ ├── patterns.html
│ │ ├── query.html
│ │ ├── row.html
│ │ ├── schema.html
│ │ ├── show_json.html
│ │ └── table.html
│ ├── tokens.py
│ ├── tracer.py
│ ├── url_builder.py
│ ├── utils/
│ │ ├── __init__.py
│ │ ├── actions_sql.py
│ │ ├── asgi.py
│ │ ├── baseconv.py
│ │ ├── check_callable.py
│ │ ├── internal_db.py
│ │ ├── multipart.py
│ │ ├── permissions.py
│ │ ├── shutil_backport.py
│ │ ├── sqlite.py
│ │ └── testing.py
│ ├── version.py
│ └── views/
│ ├── __init__.py
│ ├── base.py
│ ├── database.py
│ ├── index.py
│ ├── row.py
│ ├── special.py
│ └── table.py
├── demos/
│ ├── apache-proxy/
│ │ ├── 000-default.conf
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── deploy-to-cloud-run.sh
│ │ └── fly.toml
│ └── plugins/
│ ├── example_js_manager_plugins.py
│ └── static/
│ └── table-example-plugins.js
├── docs/
│ ├── .gitignore
│ ├── Makefile
│ ├── _static/
│ │ ├── css/
│ │ │ └── custom.css
│ │ └── js/
│ │ └── custom.js
│ ├── _templates/
│ │ ├── base.html
│ │ └── sidebar/
│ │ ├── brand.html
│ │ └── navigation.html
│ ├── authentication.rst
│ ├── auto-build.sh
│ ├── binary_data.rst
│ ├── changelog.rst
│ ├── cli-reference.rst
│ ├── codespell-ignore-words.txt
│ ├── conf.py
│ ├── configuration.rst
│ ├── contributing.rst
│ ├── csv_export.rst
│ ├── custom_templates.rst
│ ├── deploying.rst
│ ├── ecosystem.rst
│ ├── events.md
│ ├── facets.rst
│ ├── full_text_search.rst
│ ├── getting_started.rst
│ ├── index.rst
│ ├── installation.rst
│ ├── internals.rst
│ ├── introspection.rst
│ ├── javascript_plugins.rst
│ ├── json_api.rst
│ ├── metadata.rst
│ ├── metadata_doc.py
│ ├── pages.rst
│ ├── performance.rst
│ ├── plugin_hooks.rst
│ ├── plugins.rst
│ ├── publish.rst
│ ├── settings.rst
│ ├── spatialite.rst
│ ├── sql_queries.rst
│ ├── testing_plugins.rst
│ ├── upgrade-1.0a20.md
│ ├── upgrade_guide.md
│ └── writing_plugins.rst
├── package.json
├── pyproject.toml
├── pytest.ini
├── ruff.toml
├── setup.cfg
├── test-in-pyodide-with-shot-scraper.sh
└── tests/
├── __init__.py
├── build_small_spatialite_db.py
├── conftest.py
├── ext.c
├── fixtures.py
├── plugins/
│ ├── messages_output_renderer.py
│ ├── my_plugin.py
│ ├── my_plugin_2.py
│ ├── register_output_renderer.py
│ ├── sleep_sql_function.py
│ └── view_name.py
├── test-datasette-load-plugins.sh
├── test_actions_sql.py
├── test_actor_restriction_bug.py
├── test_allowed_resources.py
├── test_api.py
├── test_api_write.py
├── test_auth.py
├── test_base_view.py
├── test_canned_queries.py
├── test_cli.py
├── test_cli_serve_get.py
├── test_cli_serve_server.py
├── test_column_types.py
├── test_config_dir.py
├── test_config_permission_rules.py
├── test_crossdb.py
├── test_csv.py
├── test_custom_pages.py
├── test_datasette_https_server.sh
├── test_default_deny.py
├── test_docs.py
├── test_docs_plugins.py
├── test_facets.py
├── test_filters.py
├── test_html.py
├── test_internal_db.py
├── test_internals_database.py
├── test_internals_datasette.py
├── test_internals_datasette_client.py
├── test_internals_request.py
├── test_internals_response.py
├── test_internals_urls.py
├── test_label_column_for_table.py
├── test_load_extensions.py
├── test_messages.py
├── test_multipart.py
├── test_package.py
├── test_permission_endpoints.py
├── test_permissions.py
├── test_plugins.py
├── test_publish_cloudrun.py
├── test_publish_heroku.py
├── test_restriction_sql.py
├── test_routes.py
├── test_schema_endpoints.py
├── test_search_tables.py
├── test_spatialite.py
├── test_table_api.py
├── test_table_html.py
├── test_templates/
│ ├── _table.html
│ ├── pages/
│ │ ├── 202.html
│ │ ├── about.html
│ │ ├── atom.html
│ │ ├── headers.html
│ │ ├── nested/
│ │ │ └── nest.html
│ │ ├── redirect.html
│ │ ├── redirect2.html
│ │ ├── request.html
│ │ ├── route_{name}.html
│ │ ├── topic_{topic}/
│ │ │ └── {slug}.html
│ │ └── topic_{topic}.html
│ └── show_json.html
├── test_token_handler.py
├── test_tracer.py
├── test_utils.py
├── test_utils_check_callable.py
├── test_utils_permissions.py
├── test_write_wrapper.py
└── utils.py
Showing preview only (336K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (3597 symbols across 128 files)
FILE: datasette/actor_auth_cookie.py
function actor_from_request (line 8) | def actor_from_request(datasette, request):
FILE: datasette/app.py
class _DatasetteClientContext (line 141) | class _DatasetteClientContext:
method __enter__ (line 144) | def __enter__(self):
method __exit__ (line 148) | def __exit__(self, exc_type, exc_val, exc_tb):
class PermissionCheck (line 154) | class PermissionCheck:
function favicon (line 277) | async def favicon(request, send):
function _to_string (line 292) | def _to_string(value):
class Datasette (line 299) | class Datasette:
method __init__ (line 305) | def __init__(
method apply_metadata_json (line 540) | async def apply_metadata_json(self):
method get_jinja_environment (line 574) | def get_jinja_environment(self, request: Request = None) -> Environment:
method get_action (line 583) | def get_action(self, name_or_abbr: str):
method refresh_schemas (line 595) | async def refresh_schemas(self):
method _refresh_schemas (line 605) | async def _refresh_schemas(self):
method urls (line 646) | def urls(self):
method pm (line 650) | def pm(self):
method invoke_startup (line 660) | async def invoke_startup(self):
method sign (line 719) | def sign(self, value, namespace="default"):
method unsign (line 722) | def unsign(self, signed, namespace="default"):
method in_client (line 725) | def in_client(self) -> bool:
method _token_handlers (line 733) | def _token_handlers(self):
method create_token (line 745) | async def create_token(
method verify_token (line 785) | async def verify_token(self, token: str) -> dict | None:
method get_database (line 798) | def get_database(self, name=None, route=None):
method add_database (line 808) | def add_database(self, db, name=None, route=None):
method add_memory_database (line 827) | def add_memory_database(self, memory_name, name=None, route=None):
method remove_database (line 832) | def remove_database(self, name):
method setting (line 838) | def setting(self, key):
method settings_dict (line 841) | def settings_dict(self):
method _metadata_recursive_update (line 845) | def _metadata_recursive_update(self, orig, updated):
method get_instance_metadata (line 856) | async def get_instance_metadata(self):
method get_database_metadata (line 865) | async def get_database_metadata(self, database_name: str):
method get_resource_metadata (line 878) | async def get_resource_metadata(self, database_name: str, resource_nam...
method get_column_metadata (line 892) | async def get_column_metadata(
method set_instance_metadata (line 909) | async def set_instance_metadata(self, key: str, value: str):
method set_database_metadata (line 920) | async def set_database_metadata(self, database_name: str, key: str, va...
method set_resource_metadata (line 931) | async def set_resource_metadata(
method set_column_metadata (line 944) | async def set_column_metadata(
method _get_resource_column_details (line 964) | async def _get_resource_column_details(self, database: str, resource: ...
method _column_type_is_applicable (line 977) | def _column_type_is_applicable(ct_cls, column_detail) -> bool:
method _validate_column_type_assignment (line 986) | async def _validate_column_type_assignment(
method _apply_column_types_config (line 1021) | async def _apply_column_types_config(self):
method get_column_type (line 1049) | async def get_column_type(self, database: str, resource: str, column: ...
method get_column_types (line 1073) | async def get_column_types(self, database: str, resource: str) -> dict:
method set_column_type (line 1094) | async def set_column_type(
method remove_column_type (line 1121) | async def remove_column_type(
method get_internal_database (line 1131) | def get_internal_database(self):
method plugin_config (line 1134) | def plugin_config(self, plugin_name, database=None, table=None, fallba...
method _plugin_config_top (line 1143) | def _plugin_config_top(self, plugin_name):
method _plugin_config_nested (line 1147) | def _plugin_config_nested(self, plugin_name, database, table=None, fal...
method app_css_hash (line 1174) | def app_css_hash(self):
method get_canned_queries (line 1182) | async def get_canned_queries(self, database_name, actor):
method get_canned_query (line 1199) | async def get_canned_query(self, database_name, query_name, actor):
method _prepare_connection (line 1205) | def _prepare_connection(self, conn, database):
method add_message (line 1237) | def add_message(self, request, message, type=INFO):
method _write_messages_to_response (line 1243) | def _write_messages_to_response(self, request, response):
method _show_messages (line 1250) | def _show_messages(self, request):
method _crumb_items (line 1259) | async def _crumb_items(self, request, table=None, database=None):
method actors_from_ids (line 1296) | async def actors_from_ids(
method track_event (line 1306) | async def track_event(self, event: Event):
method resource_for_action (line 1313) | def resource_for_action(self, action: str, parent: str | None, child: ...
method check_visibility (line 1342) | async def check_visibility(
method allowed_resources_sql (line 1373) | async def allowed_resources_sql(
method allowed_resources (line 1413) | async def allowed_resources(
method allowed (line 1562) | async def allowed(
method ensure_permission (line 1629) | async def ensure_permission(
method execute (line 1662) | async def execute(
method expand_foreign_keys (line 1681) | async def expand_foreign_keys(self, actor, database, table, column, va...
method absolute_url (line 1730) | def absolute_url(self, request, path):
method _connected_databases (line 1736) | def _connected_databases(self):
method _versions (line 1750) | def _versions(self):
method _plugins (line 1825) | def _plugins(self, request=None, all=False):
method _threads (line 1846) | def _threads(self):
method _actor (line 1865) | def _actor(self, request):
method _actions (line 1868) | def _actions(self):
method table_config (line 1884) | async def table_config(self, database: str, table: str) -> dict:
method _register_renderers (line 1894) | def _register_renderers(self):
method render_template (line 1915) | async def render_template(
method set_actor_cookie (line 2018) | def set_actor_cookie(
method delete_actor_cookie (line 2027) | def delete_actor_cookie(self, response: Response):
method _asset_urls (line 2030) | async def _asset_urls(self, key, template, context, request, view_name):
method _config (line 2070) | def _config(self):
method _routes (line 2075) | def _routes(self):
method resolve_database (line 2270) | async def resolve_database(self, request):
method resolve_table (line 2277) | async def resolve_table(self, request):
method resolve_row (line 2289) | async def resolve_row(self, request):
method app (line 2299) | def app(self):
class DatasetteRouter (line 2338) | class DatasetteRouter:
method __init__ (line 2339) | def __init__(self, datasette, routes):
method __call__ (line 2343) | async def __call__(self, scope, receive, send):
method route_path (line 2352) | async def route_path(self, scope, receive, send, path):
method handle_404 (line 2416) | async def handle_404(self, request, send, exception=None):
method handle_exception (line 2521) | async def handle_exception(self, request, send, exception):
function _cleaner_task_str (line 2541) | def _cleaner_task_str(task):
function wrap_view (line 2549) | def wrap_view(view_fn_or_class, datasette):
function wrap_view_class (line 2559) | def wrap_view_class(view_class, datasette):
function wrap_view_function (line 2585) | def wrap_view_function(view_fn, datasette):
function permanent_redirect (line 2612) | def permanent_redirect(path, forward_query_string=False, forward_rest=Fa...
function route_pattern_from_filepath (line 2631) | def route_pattern_from_filepath(filepath):
class NotFoundExplicit (line 2644) | class NotFoundExplicit(NotFound):
class DatasetteClient (line 2648) | class DatasetteClient:
method __init__ (line 2655) | def __init__(self, ds):
method app (line 2659) | def app(self):
method actor_cookie (line 2662) | def actor_cookie(self, actor):
method _fix (line 2666) | def _fix(self, path, avoid_path_rewrites=False):
method _request (line 2673) | async def _request(self, method, path, skip_permission_checks=False, *...
method get (line 2691) | async def get(self, path, skip_permission_checks=False, **kwargs):
method options (line 2696) | async def options(self, path, skip_permission_checks=False, **kwargs):
method head (line 2701) | async def head(self, path, skip_permission_checks=False, **kwargs):
method post (line 2706) | async def post(self, path, skip_permission_checks=False, **kwargs):
method put (line 2711) | async def put(self, path, skip_permission_checks=False, **kwargs):
method patch (line 2716) | async def patch(self, path, skip_permission_checks=False, **kwargs):
method delete (line 2721) | async def delete(self, path, skip_permission_checks=False, **kwargs):
method request (line 2726) | async def request(self, method, path, skip_permission_checks=False, **...
FILE: datasette/blob_renderer.py
function render_blob (line 10) | async def render_blob(datasette, database, rows, columns, request, table...
function register_output_renderer (line 56) | def register_output_renderer():
FILE: datasette/cli.py
function run_sync (line 46) | def run_sync(coro_func):
class Setting (line 66) | class Setting(CompositeParamType):
method convert (line 70) | def convert(self, config, param, ctx):
function sqlite_extensions (line 95) | def sqlite_extensions(fn):
function cli (line 124) | def cli():
function inspect (line 138) | def inspect(files, inspect_file, sqlite_extensions):
function inspect_ (line 153) | async def inspect_(files, sqlite_extensions):
function publish (line 171) | def publish():
function plugins (line 190) | def plugins(all, requirements, plugins_dir):
function package (line 257) | def package(
function install (line 323) | def install(packages, upgrade, requirement, editable):
function uninstall (line 342) | def uninstall(packages, yes):
function serve (line 499) | def serve(
function create_token (line 782) | def create_token(
function check_databases (line 864) | async def check_databases(ds):
FILE: datasette/column_types.py
class SQLiteType (line 4) | class SQLiteType(Enum):
method from_declared_type (line 12) | def from_declared_type(cls, declared_type: str | None) -> "SQLiteType ...
class ColumnType (line 37) | class ColumnType:
method __init__ (line 61) | def __init__(self, config=None):
method render_cell (line 64) | async def render_cell(self, value, column, table, database, datasette,...
method validate (line 71) | async def validate(self, value, datasette):
method transform_value (line 78) | async def transform_value(self, value, datasette):
FILE: datasette/database.py
class Database (line 32) | class Database:
method __init__ (line 37) | def __init__(
method cached_table_counts (line 70) | def cached_table_counts(self):
method color (line 82) | def color(self):
method suggest_name (line 87) | def suggest_name(self):
method connect (line 95) | def connect(self, write=False):
method close (line 128) | def close(self):
method execute_write (line 133) | async def execute_write(self, sql, params=None, block=True, request=No...
method execute_write_script (line 141) | async def execute_write_script(self, sql, block=True, request=None):
method execute_write_many (line 151) | async def execute_write_many(self, sql, params_seq, block=True, reques...
method execute_isolated_fn (line 172) | async def execute_isolated_fn(self, fn):
method execute_write_fn (line 192) | async def execute_write_fn(self, fn, block=True, transaction=True, req...
method _wrap_fn_with_hooks (line 209) | def _wrap_fn_with_hooks(self, fn, request, transaction):
method _send_to_write_thread (line 228) | async def _send_to_write_thread(
method _execute_writes (line 255) | def _execute_writes(self):
method execute_fn (line 298) | async def execute_fn(self, fn):
method execute (line 319) | async def execute(
method hash (line 373) | def hash(self):
method size (line 387) | def size(self):
method table_counts (line 401) | async def table_counts(self, limit=10):
method mtime_ns (line 424) | def mtime_ns(self):
method attached_databases (line 429) | async def attached_databases(self):
method table_exists (line 442) | async def table_exists(self, table):
method view_exists (line 448) | async def view_exists(self, table):
method table_names (line 454) | async def table_names(self):
method table_columns (line 460) | async def table_columns(self, table):
method table_column_details (line 463) | async def table_column_details(self, table):
method primary_keys (line 466) | async def primary_keys(self, table):
method fts_table (line 469) | async def fts_table(self, table):
method label_column_for_table (line 472) | async def label_column_for_table(self, table):
method foreign_keys_for_table (line 520) | async def foreign_keys_for_table(self, table):
method hidden_table_names (line 525) | async def hidden_table_names(self):
method view_names (line 638) | async def view_names(self):
method get_all_foreign_keys (line 642) | async def get_all_foreign_keys(self):
method get_table_definition (line 645) | async def get_table_definition(self, table, type_="table"):
method get_view_definition (line 666) | async def get_view_definition(self, view):
method __repr__ (line 669) | def __repr__(self):
function _apply_write_wrapper (line 685) | def _apply_write_wrapper(fn, wrapper_factory):
class WriteTask (line 726) | class WriteTask:
method __init__ (line 729) | def __init__(self, fn, task_id, reply_queue, isolated_connection, tran...
class QueryInterrupted (line 737) | class QueryInterrupted(Exception):
method __init__ (line 738) | def __init__(self, e, sql, params):
method __str__ (line 743) | def __str__(self):
class MultipleValues (line 747) | class MultipleValues(Exception):
class Results (line 751) | class Results:
method __init__ (line 752) | def __init__(self, rows, truncated, description):
method columns (line 758) | def columns(self):
method first (line 761) | def first(self):
method single_value (line 767) | def single_value(self):
method dicts (line 773) | def dicts(self):
method __iter__ (line 776) | def __iter__(self):
method __len__ (line 779) | def __len__(self):
FILE: datasette/default_actions.py
function register_actions (line 11) | def register_actions():
FILE: datasette/default_column_types.py
class UrlColumnType (line 10) | class UrlColumnType(ColumnType):
method render_cell (line 15) | async def render_cell(self, value, column, table, database, datasette,...
method validate (line 21) | async def validate(self, value, datasette):
class EmailColumnType (line 31) | class EmailColumnType(ColumnType):
method render_cell (line 36) | async def render_cell(self, value, column, table, database, datasette,...
method validate (line 42) | async def validate(self, value, datasette):
class JsonColumnType (line 52) | class JsonColumnType(ColumnType):
method render_cell (line 57) | async def render_cell(self, value, column, table, database, datasette,...
method validate (line 68) | async def validate(self, value, datasette):
function register_column_types (line 80) | def register_column_types(datasette):
FILE: datasette/default_magic_parameters.py
function header (line 7) | def header(key, request):
function actor (line 13) | def actor(key, request):
function cookie (line 19) | def cookie(key, request):
function now (line 23) | def now(key, request):
function random (line 37) | def random(key, request):
function register_magic_parameters (line 50) | def register_magic_parameters():
FILE: datasette/default_menu_links.py
function menu_links (line 5) | def menu_links(datasette, actor):
FILE: datasette/default_permissions/__init__.py
function skip_csrf (line 43) | def skip_csrf(scope) -> Optional[bool]:
function canned_queries (line 53) | def canned_queries(datasette: "Datasette", database: str, actor) -> dict:
FILE: datasette/default_permissions/config.py
class ConfigPermissionProcessor (line 21) | class ConfigPermissionProcessor:
method __init__ (line 56) | def __init__(
method evaluate_allow_block (line 95) | def evaluate_allow_block(self, allow_block: Any) -> Optional[bool]:
method is_in_restriction_allowlist (line 101) | def is_in_restriction_allowlist(
method add_permissions_rule (line 144) | def add_permissions_rule(
method add_allow_block_rule (line 166) | def add_allow_block_rule(
method _add_restriction_gate_denies (line 199) | def _add_restriction_gate_denies(
method process (line 234) | def process(self) -> Optional[PermissionSQL]:
method _process_root_permissions (line 242) | def _process_root_permissions(self) -> None:
method _process_databases (line 252) | def _process_databases(self) -> None:
method _process_database (line 259) | def _process_database(self, db_name: str, db_config: dict) -> None:
method _process_table (line 281) | def _process_table(
method _process_query (line 306) | def _process_query(
method _process_database_allow_blocks (line 335) | def _process_database_allow_blocks(
method _process_root_allow_blocks (line 377) | def _process_root_allow_blocks(self) -> None:
function config_permissions_sql (line 423) | async def config_permissions_sql(
FILE: datasette/default_permissions/defaults.py
function default_allow_sql_check (line 31) | async def default_allow_sql_check(
function default_action_permissions_sql (line 50) | async def default_action_permissions_sql(
FILE: datasette/default_permissions/helpers.py
function get_action_name_variants (line 16) | def get_action_name_variants(datasette: "Datasette", action: str) -> Set...
function action_in_list (line 30) | def action_in_list(datasette: "Datasette", action: str, action_list: lis...
class PermissionRow (line 36) | class PermissionRow:
class PermissionRowCollector (line 45) | class PermissionRowCollector:
method __init__ (line 48) | def __init__(self, prefix: str = "row"):
method add (line 52) | def add(
method to_permission_sql (line 65) | def to_permission_sql(self) -> Optional[PermissionSQL]:
FILE: datasette/default_permissions/restrictions.py
class ActorRestrictions (line 23) | class ActorRestrictions:
method from_actor (line 31) | def from_actor(cls, actor: Optional[dict]) -> Optional["ActorRestricti...
method is_action_globally_allowed (line 47) | def is_action_globally_allowed(self, datasette: "Datasette", action: s...
method get_allowed_databases (line 51) | def get_allowed_databases(self, datasette: "Datasette", action: str) -...
method get_allowed_tables (line 59) | def get_allowed_tables(
function actor_restrictions_sql (line 72) | async def actor_restrictions_sql(
function restrictions_allow_action (line 142) | def restrictions_allow_action(
FILE: datasette/default_permissions/root.py
function root_user_permissions_sql (line 19) | async def root_user_permissions_sql(
FILE: datasette/default_permissions/tokens.py
function register_token_handler (line 20) | def register_token_handler(datasette: "Datasette"):
function actor_from_signed_api_token (line 26) | async def actor_from_signed_api_token(
FILE: datasette/events.py
class Event (line 8) | class Event(ABC):
method name (line 10) | def name(self):
method properties (line 18) | def properties(self):
class LoginEvent (line 26) | class LoginEvent(Event):
class LogoutEvent (line 37) | class LogoutEvent(Event):
class CreateTokenEvent (line 48) | class CreateTokenEvent(Event):
class CreateTableEvent (line 72) | class CreateTableEvent(Event):
class DropTableEvent (line 93) | class DropTableEvent(Event):
class AlterTableEvent (line 111) | class AlterTableEvent(Event):
class InsertRowsEvent (line 135) | class InsertRowsEvent(Event):
class UpsertRowsEvent (line 162) | class UpsertRowsEvent(Event):
class UpdateRowEvent (line 183) | class UpdateRowEvent(Event):
class DeleteRowEvent (line 203) | class DeleteRowEvent(Event):
function register_events (line 223) | def register_events():
FILE: datasette/facets.py
function load_facet_configs (line 14) | def load_facet_configs(request, table_config):
function register_facet_classes (line 59) | def register_facet_classes():
class Facet (line 66) | class Facet:
method __init__ (line 71) | def __init__(
method get_configs (line 94) | def get_configs(self):
method get_querystring_pairs (line 98) | def get_querystring_pairs(self):
method get_facet_size (line 103) | def get_facet_size(self):
method suggest (line 133) | async def suggest(self):
method facet_results (line 136) | async def facet_results(self):
method get_columns (line 142) | async def get_columns(self, sql, params=None):
class ColumnFacet (line 151) | class ColumnFacet(Facet):
method suggest (line 154) | async def suggest(self):
method get_row_count (line 208) | async def get_row_count(self):
method facet_results (line 219) | async def facet_results(self):
class ArrayFacet (line 297) | class ArrayFacet(Facet):
method _is_json_array_of_strings (line 300) | def _is_json_array_of_strings(self, json_string):
method suggest (line 310) | async def suggest(self):
method facet_results (line 380) | async def facet_results(self):
class DateFacet (line 468) | class DateFacet(Facet):
method suggest (line 471) | async def suggest(self):
method facet_results (line 513) | async def facet_results(self):
FILE: datasette/filters.py
function where_filters (line 10) | def where_filters(request, database, datasette):
function search_filters (line 43) | def search_filters(request, database, table, datasette):
function through_filters (line 120) | def through_filters(request, database, table, datasette):
class FilterArguments (line 163) | class FilterArguments:
method __init__ (line 164) | def __init__(
class Filter (line 173) | class Filter:
method where_clause (line 178) | def where_clause(self, table, column, value, param_counter):
method human_clause (line 181) | def human_clause(self, column, value):
class TemplatedFilter (line 185) | class TemplatedFilter(Filter):
method __init__ (line 186) | def __init__(
method where_clause (line 204) | def where_clause(self, table, column, value, param_counter):
method human_clause (line 215) | def human_clause(self, column, value):
class InFilter (line 226) | class InFilter(Filter):
method split_value (line 230) | def split_value(self, value):
method where_clause (line 236) | def where_clause(self, table, column, value, param_counter):
method human_clause (line 242) | def human_clause(self, column, value):
class NotInFilter (line 246) | class NotInFilter(InFilter):
method where_clause (line 250) | def where_clause(self, table, column, value, param_counter):
method human_clause (line 256) | def human_clause(self, column, value):
class Filters (line 260) | class Filters:
method __init__ (line 370) | def __init__(self, pairs):
method lookups (line 373) | def lookups(self):
method human_description_en (line 378) | def human_description_en(self, extra=None):
method selections (line 398) | def selections(self):
method has_selections (line 408) | def has_selections(self):
method build_where_clauses (line 411) | def build_where_clauses(self, table):
FILE: datasette/forbidden.py
function forbidden (line 5) | def forbidden(datasette, request, message):
FILE: datasette/handle_exception.py
function handle_exception (line 22) | def handle_exception(datasette, request, exception):
FILE: datasette/hookspecs.py
function startup (line 9) | def startup(datasette):
function asgi_wrapper (line 14) | def asgi_wrapper(datasette):
function prepare_connection (line 19) | def prepare_connection(conn, database, datasette):
function prepare_jinja2_environment (line 24) | def prepare_jinja2_environment(env, datasette):
function extra_css_urls (line 29) | def extra_css_urls(template, database, table, columns, view_name, reques...
function extra_js_urls (line 34) | def extra_js_urls(template, database, table, columns, view_name, request...
function extra_body_script (line 39) | def extra_body_script(
function extra_template_vars (line 46) | def extra_template_vars(
function publish_subcommand (line 53) | def publish_subcommand(publish):
function render_cell (line 58) | def render_cell(
function register_output_renderer (line 73) | def register_output_renderer(datasette):
function register_facet_classes (line 78) | def register_facet_classes():
function register_actions (line 83) | def register_actions(datasette):
function register_column_types (line 88) | def register_column_types(datasette):
function register_routes (line 93) | def register_routes(datasette):
function register_commands (line 98) | def register_commands(cli):
function actor_from_request (line 103) | def actor_from_request(datasette, request):
function actors_from_ids (line 108) | def actors_from_ids(datasette, actor_ids):
function jinja2_environment_from_request (line 113) | def jinja2_environment_from_request(datasette, request, env):
function filters_from_request (line 118) | def filters_from_request(request, database, table, datasette):
function permission_resources_sql (line 129) | def permission_resources_sql(datasette, actor, action):
function canned_queries (line 141) | def canned_queries(datasette, database, actor):
function register_magic_parameters (line 146) | def register_magic_parameters(datasette):
function forbidden (line 151) | def forbidden(datasette, request, message):
function menu_links (line 156) | def menu_links(datasette, actor, request):
function row_actions (line 161) | def row_actions(datasette, actor, request, database, table, row):
function table_actions (line 166) | def table_actions(datasette, actor, database, table, request):
function view_actions (line 171) | def view_actions(datasette, actor, database, view, request):
function query_actions (line 176) | def query_actions(datasette, actor, database, query_name, request, sql, ...
function database_actions (line 181) | def database_actions(datasette, actor, database, request):
function homepage_actions (line 186) | def homepage_actions(datasette, actor, request):
function skip_csrf (line 191) | def skip_csrf(datasette, scope):
function handle_exception (line 196) | def handle_exception(datasette, request, exception):
function track_event (line 201) | def track_event(datasette, event):
function register_events (line 206) | def register_events(datasette):
function top_homepage (line 211) | def top_homepage(datasette, request):
function top_database (line 216) | def top_database(datasette, request, database):
function top_table (line 221) | def top_table(datasette, request, database, table):
function top_row (line 226) | def top_row(datasette, request, database, table, row):
function top_query (line 231) | def top_query(datasette, request, database, sql):
function top_canned_query (line 236) | def top_canned_query(datasette, request, database, query_name):
function register_token_handler (line 241) | def register_token_handler(datasette):
function write_wrapper (line 246) | def write_wrapper(datasette, database, request, transaction):
FILE: datasette/inspect.py
function inspect_hash (line 16) | def inspect_hash(path):
function inspect_views (line 29) | def inspect_views(conn):
function inspect_tables (line 36) | def inspect_tables(conn, database_metadata):
FILE: datasette/permissions.py
class SkipPermissions (line 12) | class SkipPermissions:
method __enter__ (line 23) | def __enter__(self):
method __exit__ (line 27) | def __exit__(self, exc_type, exc_val, exc_tb):
class Resource (line 32) | class Resource(ABC):
method __init__ (line 49) | def __init__(self, parent: str | None = None, child: str | None = None):
method private (line 62) | def private(self) -> bool:
method private (line 80) | def private(self, value: bool):
method __init_subclass__ (line 84) | def __init_subclass__(cls):
method resources_sql (line 108) | async def resources_sql(cls, datasette, actor=None) -> str:
class AllowedResource (line 117) | class AllowedResource(NamedTuple):
class Action (line 125) | class Action:
method takes_parent (line 133) | def takes_parent(self) -> bool:
method takes_child (line 143) | def takes_child(self) -> bool:
class PermissionSQL (line 160) | class PermissionSQL:
method allow (line 183) | def allow(cls, reason: str, _allow: bool = True) -> "PermissionSQL":
method deny (line 193) | def deny(cls, reason: str) -> "PermissionSQL":
class Permission (line 199) | class Permission:
FILE: datasette/plugins.py
function before (line 43) | def before(hook_name, hook_impls, kwargs):
function after (line 51) | def after(outcome, hook_name, hook_impls, kwargs):
function get_plugins (line 93) | def get_plugins():
FILE: datasette/publish/cloudrun.py
function publish_subcommand (line 16) | def publish_subcommand(publish):
function _ensure_artifact_registry (line 245) | def _ensure_artifact_registry(artifact_project, artifact_region, artifac...
function get_existing_services (line 291) | def get_existing_services():
function _validate_memory (line 310) | def _validate_memory(ctx, param, value):
FILE: datasette/publish/common.py
function add_common_publish_arguments_and_options (line 8) | def add_common_publish_arguments_and_options(subcommand):
function fail_if_publish_binary_not_installed (line 75) | def fail_if_publish_binary_not_installed(binary, publish_target, install...
function validate_plugin_secret (line 94) | def validate_plugin_secret(ctx, param, value):
FILE: datasette/publish/heroku.py
function publish_subcommand (line 20) | def publish_subcommand(publish):
function temporary_heroku_directory (line 160) | def temporary_heroku_directory(
FILE: datasette/renderer.py
function convert_specific_columns_to_json (line 12) | def convert_specific_columns_to_json(rows, columns, json_cols):
function json_renderer (line 30) | def json_renderer(request, args, data, error, truncated=None):
FILE: datasette/resources.py
class DatabaseResource (line 6) | class DatabaseResource(Resource):
method __init__ (line 12) | def __init__(self, database: str):
method resources_sql (line 16) | async def resources_sql(cls, datasette, actor=None) -> str:
class TableResource (line 23) | class TableResource(Resource):
method __init__ (line 29) | def __init__(self, database: str, table: str):
method resources_sql (line 33) | async def resources_sql(cls, datasette, actor=None) -> str:
class QueryResource (line 43) | class QueryResource(Resource):
method __init__ (line 49) | def __init__(self, database: str, query: str):
method resources_sql (line 53) | async def resources_sql(cls, datasette, actor=None) -> str:
FILE: datasette/sql_functions.py
function prepare_connection (line 6) | def prepare_connection(conn):
FILE: datasette/static/cm-editor-6.0.1.bundle.js
class e (line 1) | class e{constructor(){}lineAt(t){if(t<0||t>this.length)throw new RangeEr...
method constructor (line 1) | constructor(){}
method lineAt (line 1) | lineAt(t){if(t<0||t>this.length)throw new RangeError(`Invalid position...
method line (line 1) | line(t){if(t<1||t>this.lines)throw new RangeError(`Invalid line number...
method replace (line 1) | replace(t,e,i){let s=[];return this.decompose(0,t,s,2),i.length&&i.dec...
method append (line 1) | append(t){return this.replace(this.length,this.length,t)}
method slice (line 1) | slice(t,e=this.length){let i=[];return this.decompose(t,e,i,0),n.from(...
method eq (line 1) | eq(t){if(t==this)return!0;if(t.length!=this.length||t.lines!=this.line...
method iter (line 1) | iter(t=1){return new o(this,t)}
method iterRange (line 1) | iterRange(t,e=this.length){return new l(this,t,e)}
method iterLines (line 1) | iterLines(t,e){let i;if(null==t)i=this.iter();else{null==e&&(e=this.li...
method toString (line 1) | toString(){return this.sliceString(0)}
method toJSON (line 1) | toJSON(){let t=[];return this.flatten(t),t}
method of (line 1) | static of(t){if(0==t.length)throw new RangeError("A document must have...
class i (line 1) | class i extends e{constructor(t,e=function(t){let e=-1;for(let i of t)e+...
method constructor (line 1) | constructor(t,e=function(t){let e=-1;for(let i of t)e+=i.length+1;retu...
method lines (line 1) | get lines(){return this.text.length}
method children (line 1) | get children(){return null}
method lineInner (line 1) | lineInner(t,e,i,n){for(let s=0;;s++){let r=this.text[s],o=n+r.length;i...
method decompose (line 1) | decompose(t,e,n,o){let l=t<=0&&e>=this.length?this:new i(r(this.text,t...
method replace (line 1) | replace(t,e,o){if(!(o instanceof i))return super.replace(t,e,o);let l=...
method sliceString (line 1) | sliceString(t,e=this.length,i="\n"){let n="";for(let s=0,r=0;s<=e&&r<t...
method flatten (line 1) | flatten(t){for(let e of this.text)t.push(e)}
method scanIdentical (line 1) | scanIdentical(){return 0}
method split (line 1) | static split(t,e){let n=[],s=-1;for(let r of t)n.push(r),s+=r.length+1...
class n (line 1) | class n extends e{constructor(t,e){super(),this.children=t,this.length=e...
method constructor (line 1) | constructor(t,e){super(),this.children=t,this.length=e,this.lines=0;fo...
method lineInner (line 1) | lineInner(t,e,i,n){for(let s=0;;s++){let r=this.children[s],o=n+r.leng...
method decompose (line 1) | decompose(t,e,i,n){for(let s=0,r=0;r<=e&&s<this.children.length;s++){l...
method replace (line 1) | replace(t,e,i){if(i.lines<this.lines)for(let s=0,r=0;s<this.children.l...
method sliceString (line 1) | sliceString(t,e=this.length,i="\n"){let n="";for(let s=0,r=0;s<this.ch...
method flatten (line 1) | flatten(t){for(let e of this.children)e.flatten(t)}
method scanIdentical (line 1) | scanIdentical(t,e){if(!(t instanceof n))return 0;let i=0,[s,r,o,l]=e>0...
method from (line 1) | static from(t,e=t.reduce(((t,e)=>t+e.length+1),-1)){let s=0;for(let e ...
function s (line 1) | function s(t,e,i=0,n=1e9){for(let s=0,r=0,o=!0;r<t.length&&s<=n;r++){let...
function r (line 1) | function r(t,e,i){return s(t,[""],e,i)}
class o (line 1) | class o{constructor(t,e=1){this.dir=e,this.done=!1,this.lineBreak=!1,thi...
method constructor (line 1) | constructor(t,e=1){this.dir=e,this.done=!1,this.lineBreak=!1,this.valu...
method nextInner (line 1) | nextInner(t,e){for(this.done=this.lineBreak=!1;;){let n=this.nodes.len...
method next (line 1) | next(t=0){return t<0&&(this.nextInner(-t,-this.dir),t=this.value.lengt...
class l (line 1) | class l{constructor(t,e,i){this.value="",this.done=!1,this.cursor=new o(...
method constructor (line 1) | constructor(t,e,i){this.value="",this.done=!1,this.cursor=new o(t,e>i?...
method nextInner (line 1) | nextInner(t,e){if(e<0?this.pos<=this.from:this.pos>=this.to)return thi...
method next (line 1) | next(t=0){return t<0?t=Math.max(t,this.from-this.pos):t>0&&(t=Math.min...
method lineBreak (line 1) | get lineBreak(){return this.cursor.lineBreak&&""!=this.value}
class a (line 1) | class a{constructor(t){this.inner=t,this.afterBreak=!0,this.value="",thi...
method constructor (line 1) | constructor(t){this.inner=t,this.afterBreak=!0,this.value="",this.done...
method next (line 1) | next(t=0){let{done:e,lineBreak:i,value:n}=this.inner.next(t);return e?...
method lineBreak (line 1) | get lineBreak(){return!1}
class h (line 1) | class h{constructor(t,e,i,n){this.from=t,this.to=e,this.number=i,this.te...
method constructor (line 1) | constructor(t,e,i,n){this.from=t,this.to=e,this.number=i,this.text=n}
method length (line 1) | get length(){return this.to-this.from}
function u (line 1) | function u(t){for(let e=1;e<c.length;e+=2)if(c[e]>t)return c[e-1]<=t;ret...
function f (line 1) | function f(t){return t>=127462&&t<=127487}
function d (line 1) | function d(t,e,i=!0,n=!0){return(i?p:m)(t,e,n)}
function p (line 1) | function p(t,e,i){if(e==t.length)return e;e&&g(t.charCodeAt(e))&&v(t.cha...
function m (line 1) | function m(t,e,i){for(;e>0;){let n=p(t,e-2,i);if(n<e)return n;e--}return 0}
function g (line 1) | function g(t){return t>=56320&&t<57344}
function v (line 1) | function v(t){return t>=55296&&t<56320}
function w (line 1) | function w(t,e){let i=t.charCodeAt(e);if(!v(i)||e+1==t.length)return i;l...
function y (line 1) | function y(t){return t<=65535?String.fromCharCode(t):(t-=65536,String.fr...
function b (line 1) | function b(t){return t<65536?1:2}
class S (line 1) | class S{constructor(t){this.sections=t}get length(){let t=0;for(let e=0;...
method constructor (line 1) | constructor(t){this.sections=t}
method length (line 1) | get length(){let t=0;for(let e=0;e<this.sections.length;e+=2)t+=this.s...
method newLength (line 1) | get newLength(){let t=0;for(let e=0;e<this.sections.length;e+=2){let i...
method empty (line 1) | get empty(){return 0==this.sections.length||2==this.sections.length&&t...
method iterGaps (line 1) | iterGaps(t){for(let e=0,i=0,n=0;e<this.sections.length;){let s=this.se...
method iterChangedRanges (line 1) | iterChangedRanges(t,e=!1){M(this,t,e)}
method invertedDesc (line 1) | get invertedDesc(){let t=[];for(let e=0;e<this.sections.length;){let i...
method composeDesc (line 1) | composeDesc(t){return this.empty?t:t.empty?this:T(this,t)}
method mapDesc (line 1) | mapDesc(t,e=!1){return t.empty?this:D(this,t,e)}
method mapPos (line 1) | mapPos(t,e=-1,i=k.Simple){let n=0,s=0;for(let r=0;r<this.sections.leng...
method touchesRange (line 1) | touchesRange(t,e=t){for(let i=0,n=0;i<this.sections.length&&n<=e;){let...
method toString (line 1) | toString(){let t="";for(let e=0;e<this.sections.length;){let i=this.se...
method toJSON (line 1) | toJSON(){return this.sections}
method fromJSON (line 1) | static fromJSON(t){if(!Array.isArray(t)||t.length%2||t.some((t=>"numbe...
method create (line 1) | static create(t){return new S(t)}
class C (line 1) | class C extends S{constructor(t,e){super(t),this.inserted=e}apply(t){if(...
method constructor (line 1) | constructor(t,e){super(t),this.inserted=e}
method apply (line 1) | apply(t){if(this.length!=t.length)throw new RangeError("Applying chang...
method mapDesc (line 1) | mapDesc(t,e=!1){return D(this,t,e,!0)}
method invert (line 1) | invert(t){let i=this.sections.slice(),n=[];for(let s=0,r=0;s<i.length;...
method compose (line 1) | compose(t){return this.empty?t:t.empty?this:T(this,t,!0)}
method map (line 1) | map(t,e=!1){return t.empty?this:D(this,t,e,!0)}
method iterChanges (line 1) | iterChanges(t,e=!1){M(this,t,e)}
method desc (line 1) | get desc(){return S.create(this.sections)}
method filter (line 1) | filter(t){let e=[],i=[],n=[],s=new P(this);t:for(let r=0,o=0;;){let l=...
method toJSON (line 1) | toJSON(){let t=[];for(let e=0;e<this.sections.length;e+=2){let i=this....
method of (line 1) | static of(t,i,n){let s=[],r=[],o=0,l=null;function a(t=!1){if(!t&&!s.l...
method empty (line 1) | static empty(t){return new C(t?[t,-1]:[],[])}
method fromJSON (line 1) | static fromJSON(t){if(!Array.isArray(t))throw new RangeError("Invalid ...
method createSet (line 1) | static createSet(t,e){return new C(t,e)}
function A (line 1) | function A(t,e,i,n=!1){if(0==e&&i<=0)return;let s=t.length-2;s>=0&&i<=0&...
function O (line 1) | function O(t,i,n){if(0==n.length)return;let s=i.length-2>>1;if(s<t.lengt...
function M (line 1) | function M(t,i,n){let s=t.inserted;for(let r=0,o=0,l=0;l<t.sections.leng...
function D (line 1) | function D(t,e,i,n=!1){let s=[],r=n?[]:null,o=new P(t),l=new P(e);for(le...
function T (line 1) | function T(t,e,i=!1){let n=[],s=i?[]:null,r=new P(t),o=new P(e);for(let ...
class P (line 1) | class P{constructor(t){this.set=t,this.i=0,this.next()}next(){let{sectio...
method constructor (line 1) | constructor(t){this.set=t,this.i=0,this.next()}
method next (line 1) | next(){let{sections:t}=this.set;this.i<t.length?(this.len=t[this.i++],...
method done (line 1) | get done(){return-2==this.ins}
method len2 (line 1) | get len2(){return this.ins<0?this.len:this.ins}
method text (line 1) | get text(){let{inserted:t}=this.set,i=this.i-2>>1;return i>=t.length?e...
method textBit (line 1) | textBit(t){let{inserted:i}=this.set,n=this.i-2>>1;return n>=i.length&&...
method forward (line 1) | forward(t){t==this.len?this.next():(this.len-=t,this.off+=t)}
method forward2 (line 1) | forward2(t){-1==this.ins?this.forward(t):t==this.ins?this.next():(this...
class R (line 1) | class R{constructor(t,e,i){this.from=t,this.to=e,this.flags=i}get anchor...
method constructor (line 1) | constructor(t,e,i){this.from=t,this.to=e,this.flags=i}
method anchor (line 1) | get anchor(){return 16&this.flags?this.to:this.from}
method head (line 1) | get head(){return 16&this.flags?this.from:this.to}
method empty (line 1) | get empty(){return this.from==this.to}
method assoc (line 1) | get assoc(){return 4&this.flags?-1:8&this.flags?1:0}
method bidiLevel (line 1) | get bidiLevel(){let t=3&this.flags;return 3==t?null:t}
method goalColumn (line 1) | get goalColumn(){let t=this.flags>>5;return 33554431==t?void 0:t}
method map (line 1) | map(t,e=-1){let i,n;return this.empty?i=n=t.mapPos(this.from,e):(i=t.m...
method extend (line 1) | extend(t,e=t){if(t<=this.anchor&&e>=this.anchor)return E.range(t,e);le...
method eq (line 1) | eq(t){return this.anchor==t.anchor&&this.head==t.head}
method toJSON (line 1) | toJSON(){return{anchor:this.anchor,head:this.head}}
method fromJSON (line 1) | static fromJSON(t){if(!t||"number"!=typeof t.anchor||"number"!=typeof ...
method create (line 1) | static create(t,e,i){return new R(t,e,i)}
class E (line 1) | class E{constructor(t,e){this.ranges=t,this.mainIndex=e}map(t,e=-1){retu...
method constructor (line 1) | constructor(t,e){this.ranges=t,this.mainIndex=e}
method map (line 1) | map(t,e=-1){return t.empty?this:E.create(this.ranges.map((i=>i.map(t,e...
method eq (line 1) | eq(t){if(this.ranges.length!=t.ranges.length||this.mainIndex!=t.mainIn...
method main (line 1) | get main(){return this.ranges[this.mainIndex]}
method asSingle (line 1) | asSingle(){return 1==this.ranges.length?this:new E([this.main],0)}
method addRange (line 1) | addRange(t,e=!0){return E.create([t].concat(this.ranges),e?0:this.main...
method replaceRange (line 1) | replaceRange(t,e=this.mainIndex){let i=this.ranges.slice();return i[e]...
method toJSON (line 1) | toJSON(){return{ranges:this.ranges.map((t=>t.toJSON())),main:this.main...
method fromJSON (line 1) | static fromJSON(t){if(!t||!Array.isArray(t.ranges)||"number"!=typeof t...
method single (line 1) | static single(t,e=t){return new E([E.range(t,e)],0)}
method create (line 1) | static create(t,e=0){if(0==t.length)throw new RangeError("A selection ...
method cursor (line 1) | static cursor(t,e=0,i,n){return R.create(t,t,(0==e?0:e<0?4:8)|(null==i...
method range (line 1) | static range(t,e,i){let n=(null!=i?i:33554431)<<5;return e<t?R.create(...
method normalized (line 1) | static normalized(t,e=0){let i=t[e];t.sort(((t,e)=>t.from-e.from)),e=t...
function B (line 1) | function B(t,e){for(let i of t.ranges)if(i.to>e)throw new RangeError("Se...
class N (line 1) | class N{constructor(t,e,i,n,s){this.combine=t,this.compareInput=e,this.c...
method constructor (line 1) | constructor(t,e,i,n,s){this.combine=t,this.compareInput=e,this.compare...
method define (line 1) | static define(t={}){return new N(t.combine||(t=>t),t.compareInput||((t...
method of (line 1) | of(t){return new V([],this,0,t)}
method compute (line 1) | compute(t,e){if(this.isStatic)throw new Error("Can't compute a static ...
method computeN (line 1) | computeN(t,e){if(this.isStatic)throw new Error("Can't compute a static...
method from (line 1) | from(t,e){return e||(e=t=>t),this.compute([t],(i=>e(i.field(t))))}
function I (line 1) | function I(t,e){return t==e||t.length==e.length&&t.every(((t,i)=>t===e[i...
class V (line 1) | class V{constructor(t,e,i,n){this.dependencies=t,this.facet=e,this.type=...
method constructor (line 1) | constructor(t,e,i,n){this.dependencies=t,this.facet=e,this.type=i,this...
method dynamicSlot (line 1) | dynamicSlot(t){var e;let i=this.value,n=this.facet.compareInput,s=this...
function W (line 1) | function W(t,e,i){if(t.length!=e.length)return!1;for(let n=0;n<t.length;...
function z (line 1) | function z(t,e){let i=!1;for(let n of e)1&Y(t,n)&&(i=!0);return i}
function H (line 1) | function H(t,e,i){let n=i.map((e=>t[e.id])),s=i.map((t=>t.type)),r=n.fil...
class q (line 1) | class q{constructor(t,e,i,n,s){this.id=t,this.createF=e,this.updateF=i,t...
method constructor (line 1) | constructor(t,e,i,n,s){this.id=t,this.createF=e,this.updateF=i,this.co...
method define (line 1) | static define(t){let e=new q(L++,t.create,t.update,t.compare||((t,e)=>...
method create (line 1) | create(t){let e=t.facet(F).find((t=>t.field==this));return((null==e?vo...
method slot (line 1) | slot(t){let e=t[this.id]>>1;return{create:t=>(t.values[e]=this.create(...
method init (line 1) | init(t){return[this,F.of({field:this,create:t})]}
method extension (line 1) | get extension(){return this}
function Q (line 1) | function Q(t){return e=>new G(e,t)}
class G (line 1) | class G{constructor(t,e){this.inner=t,this.prec=e}}
method constructor (line 1) | constructor(t,e){this.inner=t,this.prec=e}
class J (line 1) | class J{of(t){return new X(this,t)}reconfigure(t){return J.reconfigure.o...
method of (line 1) | of(t){return new X(this,t)}
method reconfigure (line 1) | reconfigure(t){return J.reconfigure.of({compartment:this,extension:t})}
method get (line 1) | get(t){return t.config.compartments.get(this)}
class X (line 1) | class X{constructor(t,e){this.compartment=t,this.inner=e}}
method constructor (line 1) | constructor(t,e){this.compartment=t,this.inner=e}
class Z (line 1) | class Z{constructor(t,e,i,n,s,r){for(this.base=t,this.compartments=e,thi...
method constructor (line 1) | constructor(t,e,i,n,s,r){for(this.base=t,this.compartments=e,this.dyna...
method staticFacet (line 1) | staticFacet(t){let e=this.address[t.id];return null==e?t.default:this....
method resolve (line 1) | static resolve(t,e,i){let n=[],s=Object.create(null),r=new Map;for(let...
function Y (line 1) | function Y(t,e){if(1&e)return 2;let i=e>>1,n=t.status[i];if(4==n)throw n...
function tt (line 1) | function tt(t,e){return 1&e?t.config.staticValues[e>>1]:t.values[e>>1]}
class at (line 1) | class at{constructor(t,e){this.type=t,this.value=e}static define(){retur...
method constructor (line 1) | constructor(t,e){this.type=t,this.value=e}
method define (line 1) | static define(){return new ht}
class ht (line 1) | class ht{of(t){return new at(this,t)}}
method of (line 1) | of(t){return new at(this,t)}
class ct (line 1) | class ct{constructor(t){this.map=t}of(t){return new ut(this,t)}}
method constructor (line 1) | constructor(t){this.map=t}
method of (line 1) | of(t){return new ut(this,t)}
class ut (line 1) | class ut{constructor(t,e){this.type=t,this.value=e}map(t){let e=this.typ...
method constructor (line 1) | constructor(t,e){this.type=t,this.value=e}
method map (line 1) | map(t){let e=this.type.map(this.value,t);return void 0===e?void 0:e==t...
method is (line 1) | is(t){return this.type==t}
method define (line 1) | static define(t={}){return new ct(t.map||(t=>t))}
method mapEffects (line 1) | static mapEffects(t,e){if(!t.length)return t;let i=[];for(let n of t){...
class ft (line 1) | class ft{constructor(t,e,i,n,s,r){this.startState=t,this.changes=e,this....
method constructor (line 1) | constructor(t,e,i,n,s,r){this.startState=t,this.changes=e,this.selecti...
method create (line 1) | static create(t,e,i,n,s,r){return new ft(t,e,i,n,s,r)}
method newDoc (line 1) | get newDoc(){return this._doc||(this._doc=this.changes.apply(this.star...
method newSelection (line 1) | get newSelection(){return this.selection||this.startState.selection.ma...
method state (line 1) | get state(){return this._state||this.startState.applyTransaction(this)...
method annotation (line 1) | annotation(t){for(let e of this.annotations)if(e.type==t)return e.value}
method docChanged (line 1) | get docChanged(){return!this.changes.empty}
method reconfigured (line 1) | get reconfigured(){return this.startState.config!=this.state.config}
method isUserEvent (line 1) | isUserEvent(t){let e=this.annotation(ft.userEvent);return!(!e||!(e==t|...
function dt (line 1) | function dt(t,e){let i=[];for(let n=0,s=0;;){let r,o;if(n<t.length&&(s==...
function pt (line 1) | function pt(t,e,i){var n;let s,r,o;return i?(s=e.changes,r=C.empty(e.cha...
function mt (line 1) | function mt(t,e,i){let n=e.selection,s=wt(e.annotations);return e.userEv...
function gt (line 1) | function gt(t,e,i){let n=mt(t,e.length?e[0]:{},t.doc.length);e.length&&!...
function wt (line 1) | function wt(t){return null==t?vt:Array.isArray(t)?t:[t]}
function kt (line 1) | function kt(t){return e=>{if(!/\S/.test(e))return yt.Space;if(function(t...
class St (line 1) | class St{constructor(t,e,i,n,s,r){this.config=t,this.doc=e,this.selectio...
method constructor (line 1) | constructor(t,e,i,n,s,r){this.config=t,this.doc=e,this.selection=i,thi...
method field (line 1) | field(t,e=!0){let i=this.config.address[t.id];if(null!=i)return Y(this...
method update (line 1) | update(...t){return gt(this,t,!0)}
method applyTransaction (line 1) | applyTransaction(t){let e,i=this.config,{base:n,compartments:s}=i;for(...
method replaceSelection (line 1) | replaceSelection(t){return"string"==typeof t&&(t=this.toText(t)),this....
method changeByRange (line 1) | changeByRange(t){let e=this.selection,i=t(e.ranges[0]),n=this.changes(...
method changes (line 1) | changes(t=[]){return t instanceof C?t:C.of(t,this.doc.length,this.face...
method toText (line 1) | toText(t){return e.of(t.split(this.facet(St.lineSeparator)||x))}
method sliceDoc (line 1) | sliceDoc(t=0,e=this.doc.length){return this.doc.sliceString(t,e,this.l...
method facet (line 1) | facet(t){let e=this.config.address[t.id];return null==e?t.default:(Y(t...
method toJSON (line 1) | toJSON(t){let e={doc:this.sliceDoc(),selection:this.selection.toJSON()...
method fromJSON (line 1) | static fromJSON(t,e={},i){if(!t||"string"!=typeof t.doc)throw new Rang...
method create (line 1) | static create(t={}){let i=Z.resolve(t.extensions||[],new Map),n=t.doc ...
method tabSize (line 1) | get tabSize(){return this.facet(St.tabSize)}
method lineBreak (line 1) | get lineBreak(){return this.facet(St.lineSeparator)||"\n"}
method readOnly (line 1) | get readOnly(){return this.facet(lt)}
method phrase (line 1) | phrase(t,...e){for(let e of this.facet(St.phrases))if(Object.prototype...
method languageDataAt (line 1) | languageDataAt(t,e,i=-1){let n=[];for(let s of this.facet(et))for(let ...
method charCategorizer (line 1) | charCategorizer(t){return kt(this.languageDataAt("wordChars",t).join("...
method wordAt (line 1) | wordAt(t){let{text:e,from:i,length:n}=this.doc.lineAt(t),s=this.charCa...
function Ct (line 1) | function Ct(t,e,i={}){let n={};for(let e of t)for(let t of Object.keys(e...
method compare (line 1) | compare(t,e){let i=Object.keys(t),n=Object.keys(e);return i.length==n.le...
class At (line 1) | class At{eq(t){return this==t}range(t,e=t){return Ot.create(t,e,this)}}
method eq (line 1) | eq(t){return this==t}
method range (line 1) | range(t,e=t){return Ot.create(t,e,this)}
method constructor (line 1) | constructor(t,e,i){this.from=t,this.to=e,this.value=i}
method create (line 1) | static create(t,e,i){return new Ot(t,e,i)}
function Mt (line 1) | function Mt(t,e){return t.from-e.from||t.value.startSide-e.value.startSide}
class Dt (line 1) | class Dt{constructor(t,e,i,n){this.from=t,this.to=e,this.value=i,this.ma...
method constructor (line 1) | constructor(t,e,i,n){this.from=t,this.to=e,this.value=i,this.maxPoint=n}
method length (line 1) | get length(){return this.to[this.to.length-1]}
method findIndex (line 1) | findIndex(t,e,i,n=0){let s=i?this.to:this.from;for(let r=n,o=s.length;...
method between (line 1) | between(t,e,i,n){for(let s=this.findIndex(e,-1e9,!0),r=this.findIndex(...
method map (line 1) | map(t,e){let i=[],n=[],s=[],r=-1,o=-1;for(let l=0;l<this.value.length;...
class Tt (line 1) | class Tt{constructor(t,e,i,n){this.chunkPos=t,this.chunk=e,this.nextLaye...
method constructor (line 1) | constructor(t,e,i,n){this.chunkPos=t,this.chunk=e,this.nextLayer=i,thi...
method create (line 1) | static create(t,e,i,n){return new Tt(t,e,i,n)}
method length (line 1) | get length(){let t=this.chunk.length-1;return t<0?0:Math.max(this.chun...
method size (line 1) | get size(){if(this.isEmpty)return 0;let t=this.nextLayer.size;for(let ...
method chunkEnd (line 1) | chunkEnd(t){return this.chunkPos[t]+this.chunk[t].length}
method update (line 1) | update(t){let{add:e=[],sort:i=!1,filterFrom:n=0,filterTo:s=this.length...
method map (line 1) | map(t){if(t.empty||this.isEmpty)return this;let e=[],i=[],n=-1;for(let...
method between (line 1) | between(t,e,i){if(!this.isEmpty){for(let n=0;n<this.chunk.length;n++){...
method iter (line 1) | iter(t=0){return Bt.from([this]).goto(t)}
method isEmpty (line 1) | get isEmpty(){return this.nextLayer==this}
method iter (line 1) | static iter(t,e=0){return Bt.from(t).goto(e)}
method compare (line 1) | static compare(t,e,i,n,s=-1){let r=t.filter((t=>t.maxPoint>0||!t.isEmp...
method eq (line 1) | static eq(t,e,i=0,n){null==n&&(n=999999999);let s=t.filter((t=>!t.isEm...
method spans (line 1) | static spans(t,e,i,n,s=-1){let r=new Nt(t,null,s).goto(e),o=e,l=r.open...
method of (line 1) | static of(t,e=!1){let i=new Pt;for(let n of t instanceof Ot?[t]:e?func...
class Pt (line 1) | class Pt{constructor(){this.chunks=[],this.chunkPos=[],this.chunkStart=-...
method constructor (line 1) | constructor(){this.chunks=[],this.chunkPos=[],this.chunkStart=-1,this....
method finishChunk (line 1) | finishChunk(t){this.chunks.push(new Dt(this.from,this.to,this.value,th...
method add (line 1) | add(t,e,i){this.addInner(t,e,i)||(this.nextLayer||(this.nextLayer=new ...
method addInner (line 1) | addInner(t,e,i){let n=t-this.lastTo||i.startSide-this.last.endSide;if(...
method addChunk (line 1) | addChunk(t,e){if((t-this.lastTo||e.value[0].startSide-this.last.endSid...
method finish (line 1) | finish(){return this.finishInner(Tt.empty)}
method finishInner (line 1) | finishInner(t){if(this.from.length&&this.finishChunk(!1),0==this.chunk...
function Rt (line 1) | function Rt(t,e,i){let n=new Map;for(let e of t)for(let t=0;t<e.chunk.le...
class Et (line 1) | class Et{constructor(t,e,i,n=0){this.layer=t,this.skip=e,this.minPoint=i...
method constructor (line 1) | constructor(t,e,i,n=0){this.layer=t,this.skip=e,this.minPoint=i,this.r...
method startSide (line 1) | get startSide(){return this.value?this.value.startSide:0}
method endSide (line 1) | get endSide(){return this.value?this.value.endSide:0}
method goto (line 1) | goto(t,e=-1e9){return this.chunkIndex=this.rangeIndex=0,this.gotoInner...
method gotoInner (line 1) | gotoInner(t,e,i){for(;this.chunkIndex<this.layer.chunk.length;){let e=...
method forward (line 1) | forward(t,e){(this.to-t||this.endSide-e)<0&&this.gotoInner(t,e,!0)}
method next (line 1) | next(){for(;;){if(this.chunkIndex==this.layer.chunk.length){this.from=...
method setRangeIndex (line 1) | setRangeIndex(t){if(t==this.layer.chunk[this.chunkIndex].value.length)...
method nextChunk (line 1) | nextChunk(){this.chunkIndex++,this.rangeIndex=0,this.next()}
method compare (line 1) | compare(t){return this.from-t.from||this.startSide-t.startSide||this.r...
class Bt (line 1) | class Bt{constructor(t){this.heap=t}static from(t,e=null,i=-1){let n=[];...
method constructor (line 1) | constructor(t){this.heap=t}
method from (line 1) | static from(t,e=null,i=-1){let n=[];for(let s=0;s<t.length;s++)for(let...
method startSide (line 1) | get startSide(){return this.value?this.value.startSide:0}
method goto (line 1) | goto(t,e=-1e9){for(let i of this.heap)i.goto(t,e);for(let t=this.heap....
method forward (line 1) | forward(t,e){for(let i of this.heap)i.forward(t,e);for(let t=this.heap...
method next (line 1) | next(){if(0==this.heap.length)this.from=this.to=1e9,this.value=null,th...
function Lt (line 1) | function Lt(t,e){for(let i=t[e];;){let n=1+(e<<1);if(n>=t.length)break;l...
class Nt (line 1) | class Nt{constructor(t,e,i){this.minPoint=i,this.active=[],this.activeTo...
method constructor (line 1) | constructor(t,e,i){this.minPoint=i,this.active=[],this.activeTo=[],thi...
method goto (line 1) | goto(t,e=-1e9){return this.cursor.goto(t,e),this.active.length=this.ac...
method forward (line 1) | forward(t,e){for(;this.minActive>-1&&(this.activeTo[this.minActive]-t|...
method removeActive (line 1) | removeActive(t){Wt(this.active,t),Wt(this.activeTo,t),Wt(this.activeRa...
method addActive (line 1) | addActive(t){let e=0,{value:i,to:n,rank:s}=this.cursor;for(;e<this.act...
method next (line 1) | next(){let t=this.to,e=this.point;this.point=null;let i=this.openStart...
method activeForPoint (line 1) | activeForPoint(t){if(!this.active.length)return this.active;let e=[];f...
method openEnd (line 1) | openEnd(t){let e=0;for(let i=this.activeTo.length-1;i>=0&&this.activeT...
function It (line 1) | function It(t,e,i,n,s,r){t.goto(e),i.goto(n);let o=n+s,l=n,a=n-e;for(;;)...
function Vt (line 1) | function Vt(t,e){if(t.length!=e.length)return!1;for(let i=0;i<t.length;i...
function Wt (line 1) | function Wt(t,e){for(let i=e,n=t.length-1;i<n;i++)t[i]=t[i+1];t.pop()}
function zt (line 1) | function zt(t,e,i){for(let i=t.length-1;i>=e;i--)t[i+1]=t[i];t[e]=i}
function Ht (line 1) | function Ht(t,e){let i=-1,n=1e9;for(let s=0;s<e.length;s++)(e[s]-n||t[s]...
function Ft (line 1) | function Ft(t,e,i=t.length){let n=0;for(let s=0;s<i;)9==t.charCodeAt(s)?...
function qt (line 1) | function qt(t,e,i,n){for(let n=0,s=0;;){if(s>=e)return n;if(n==t.length)...
class $t (line 1) | class $t{constructor(t,e){this.rules=[];let{finish:i}=e||{};function n(t...
method constructor (line 1) | constructor(t,e){this.rules=[];let{finish:i}=e||{};function n(t){retur...
method getRules (line 1) | getRules(){return this.rules.join("\n")}
method newName (line 1) | static newName(){let t=Ut[_t]||1;return Ut[_t]=t+1,"ͼ"+t.toString(36)}
method mount (line 1) | static mount(t,e){(t[jt]||new Kt(t)).mount(Array.isArray(e)?e:[e])}
class Kt (line 1) | class Kt{constructor(t){if(!t.head&&t.adoptedStyleSheets&&"undefined"!=t...
method constructor (line 1) | constructor(t){if(!t.head&&t.adoptedStyleSheets&&"undefined"!=typeof C...
method mount (line 1) | mount(t){let e=this.sheet,i=0,n=0;for(let s=0;s<t.length;s++){let r=t[...
function ne (line 1) | function ne(t){let e;return e=11==t.nodeType?t.getSelection?t:t.ownerDoc...
function se (line 1) | function se(t,e){return!!e&&(t==e||t.contains(1!=e.nodeType?e.parentNode...
function re (line 1) | function re(t,e){if(!e.anchorNode)return!1;try{return se(t,e.anchorNode)...
function oe (line 1) | function oe(t){return 3==t.nodeType?we(t,0,t.nodeValue.length).getClient...
function le (line 1) | function le(t,e,i,n){return!!i&&(he(t,e,i,n,-1)||he(t,e,i,n,1))}
function ae (line 1) | function ae(t){for(var e=0;;e++)if(!(t=t.previousSibling))return e}
function he (line 1) | function he(t,e,i,n,s){for(;;){if(t==i&&e==n)return!0;if(e==(s<0?0:ce(t)...
function ce (line 1) | function ce(t){return 3==t.nodeType?t.nodeValue.length:t.childNodes.length}
function fe (line 1) | function fe(t,e){let i=e?t.left:t.right;return{left:i,right:i,top:t.top,...
function de (line 1) | function de(t){return{left:0,right:t.innerWidth,top:0,bottom:t.innerHeig...
class pe (line 1) | class pe{constructor(){this.anchorNode=null,this.anchorOffset=0,this.foc...
method constructor (line 1) | constructor(){this.anchorNode=null,this.anchorOffset=0,this.focusNode=...
method eq (line 1) | eq(t){return this.anchorNode==t.anchorNode&&this.anchorOffset==t.ancho...
method setRange (line 1) | setRange(t){this.set(t.anchorNode,t.anchorOffset,t.focusNode,t.focusOf...
method set (line 1) | set(t,e,i,n){this.anchorNode=t,this.anchorOffset=e,this.focusNode=i,th...
function ve (line 1) | function ve(t){if(t.setActive)return t.setActive();if(ge)return t.focus(...
function we (line 1) | function we(t,e,i=e){let n=me||(me=document.createRange());return n.setE...
function ye (line 1) | function ye(t,e,i){let n={key:e,code:e,keyCode:i,which:i,cancelable:!0},...
function be (line 1) | function be(t){for(;t.attributes.length;)t.removeAttributeNode(t.attribu...
class xe (line 1) | class xe{constructor(t,e,i=!0){this.node=t,this.offset=e,this.precise=i}...
method constructor (line 1) | constructor(t,e,i=!0){this.node=t,this.offset=e,this.precise=i}
method before (line 1) | static before(t,e){return new xe(t.parentNode,ae(t),e)}
method after (line 1) | static after(t,e){return new xe(t.parentNode,ae(t)+1,e)}
class Se (line 1) | class Se{constructor(){this.parent=null,this.dom=null,this.dirty=2}get e...
method constructor (line 1) | constructor(){this.parent=null,this.dom=null,this.dirty=2}
method editorView (line 1) | get editorView(){if(!this.parent)throw new Error("Accessing view in or...
method overrideDOMText (line 1) | get overrideDOMText(){return null}
method posAtStart (line 1) | get posAtStart(){return this.parent?this.parent.posBefore(this):0}
method posAtEnd (line 1) | get posAtEnd(){return this.posAtStart+this.length}
method posBefore (line 1) | posBefore(t){let e=this.posAtStart;for(let i of this.children){if(i==t...
method posAfter (line 1) | posAfter(t){return this.posBefore(t)+t.length}
method coordsAt (line 1) | coordsAt(t,e){return null}
method sync (line 1) | sync(t){if(2&this.dirty){let e,i=this.dom,n=null;for(let s of this.chi...
method reuseDOM (line 1) | reuseDOM(t){}
method localPosFromDOM (line 1) | localPosFromDOM(t,e){let i;if(t==this.dom)i=this.dom.childNodes[e];els...
method domBoundsAround (line 1) | domBoundsAround(t,e,i=0){let n=-1,s=-1,r=-1,o=-1;for(let l=0,a=i,h=i;l...
method markDirty (line 1) | markDirty(t=!1){this.dirty|=2,this.markParentsDirty(t)}
method markParentsDirty (line 1) | markParentsDirty(t){for(let e=this.parent;e;e=e.parent){if(t&&(e.dirty...
method setParent (line 1) | setParent(t){this.parent!=t&&(this.parent=t,this.dirty&&this.markParen...
method setDOM (line 1) | setDOM(t){this.dom&&(this.dom.cmView=null),this.dom=t,t.cmView=this}
method rootView (line 1) | get rootView(){for(let t=this;;){let e=t.parent;if(!e)return t;t=e}}
method replaceChildren (line 1) | replaceChildren(t,e,i=ke){this.markDirty();for(let i=t;i<e;i++){let t=...
method ignoreMutation (line 1) | ignoreMutation(t){return!1}
method ignoreEvent (line 1) | ignoreEvent(t){return!1}
method childCursor (line 1) | childCursor(t=this.length){return new Ae(this.children,t,this.children...
method childPos (line 1) | childPos(t,e=1){return this.childCursor().findPos(t,e)}
method toString (line 1) | toString(){let t=this.constructor.name.replace("View","");return t+(th...
method get (line 1) | static get(t){return t.cmView}
method isEditable (line 1) | get isEditable(){return!0}
method merge (line 1) | merge(t,e,i,n,s,r){return!1}
method become (line 1) | become(t){return!1}
method canReuseDOM (line 1) | canReuseDOM(t){return t.constructor==this.constructor}
method getSide (line 1) | getSide(){return 0}
method destroy (line 1) | destroy(){this.parent=null}
function Ce (line 1) | function Ce(t){let e=t.nextSibling;return t.parentNode.removeChild(t),e}
class Ae (line 1) | class Ae{constructor(t,e,i){this.children=t,this.pos=e,this.i=i,this.off...
method constructor (line 1) | constructor(t,e,i){this.children=t,this.pos=e,this.i=i,this.off=0}
method findPos (line 1) | findPos(t,e=1){for(;;){if(t>this.pos||t==this.pos&&(e>0||0==this.i||th...
function Oe (line 1) | function Oe(t,e,i,n,s,r,o,l,a){let{children:h}=t,c=h.length?h[e]:null,u=...
function Me (line 1) | function Me(t,e,i,n,s,r){let o=t.childCursor(),{i:l,off:a}=o.findPos(i,1...
class He (line 1) | class He extends Se{constructor(t){super(),this.text=t}get length(){retu...
method constructor (line 1) | constructor(t){super(),this.text=t}
method length (line 1) | get length(){return this.text.length}
method createDOM (line 1) | createDOM(t){this.setDOM(t||document.createTextNode(this.text))}
method sync (line 1) | sync(t){this.dom||this.createDOM(),this.dom.nodeValue!=this.text&&(t&&...
method reuseDOM (line 1) | reuseDOM(t){3==t.nodeType&&this.createDOM(t)}
method merge (line 1) | merge(t,e,i){return(!i||i instanceof He&&!(this.length-(e-t)+i.length>...
method split (line 1) | split(t){let e=new He(this.text.slice(t));return this.text=this.text.s...
method localPosFromDOM (line 1) | localPosFromDOM(t,e){return t==this.dom?e:e?this.text.length:0}
method domAtPos (line 1) | domAtPos(t){return new xe(this.dom,t)}
method domBoundsAround (line 1) | domBoundsAround(t,e,i){return{from:i,to:i+this.length,startDOM:this.do...
method coordsAt (line 1) | coordsAt(t,e){return qe(this.dom,t,e)}
class Fe (line 1) | class Fe extends Se{constructor(t,e=[],i=0){super(),this.mark=t,this.chi...
method constructor (line 1) | constructor(t,e=[],i=0){super(),this.mark=t,this.children=e,this.lengt...
method setAttrs (line 1) | setAttrs(t){if(be(t),this.mark.class&&(t.className=this.mark.class),th...
method reuseDOM (line 1) | reuseDOM(t){t.nodeName==this.mark.tagName.toUpperCase()&&(this.setDOM(...
method sync (line 1) | sync(t){this.dom?4&this.dirty&&this.setAttrs(this.dom):this.setDOM(thi...
method merge (line 1) | merge(t,e,i,n,s,r){return(!i||!(!(i instanceof Fe&&i.mark.eq(this.mark...
method split (line 1) | split(t){let e=[],i=0,n=-1,s=0;for(let r of this.children){let o=i+r.l...
method domAtPos (line 1) | domAtPos(t){return Ke(this,t)}
method coordsAt (line 1) | coordsAt(t,e){return Je(this,t,e)}
function qe (line 1) | function qe(t,e,i){let n=t.nodeValue.length;e>n&&(e=n);let s=e,r=e,o=0;0...
class _e (line 1) | class _e extends Se{constructor(t,e,i){super(),this.widget=t,this.length...
method constructor (line 1) | constructor(t,e,i){super(),this.widget=t,this.length=e,this.side=i,thi...
method create (line 1) | static create(t,e,i){return new(t.customView||_e)(t,e,i)}
method split (line 1) | split(t){let e=_e.create(this.widget,this.length-t,this.side);return t...
method sync (line 1) | sync(){this.dom&&this.widget.updateDOM(this.dom)||(this.dom&&this.prev...
method getSide (line 1) | getSide(){return this.side}
method merge (line 1) | merge(t,e,i,n,s,r){return!(i&&(!(i instanceof _e&&this.widget.compare(...
method become (line 1) | become(t){return t.length==this.length&&t instanceof _e&&t.side==this....
method ignoreMutation (line 1) | ignoreMutation(){return!0}
method ignoreEvent (line 1) | ignoreEvent(t){return this.widget.ignoreEvent(t)}
method overrideDOMText (line 1) | get overrideDOMText(){if(0==this.length)return e.empty;let t=this;for(...
method domAtPos (line 1) | domAtPos(t){return 0==t?xe.before(this.dom):xe.after(this.dom,t==this....
method domBoundsAround (line 1) | domBoundsAround(){return null}
method coordsAt (line 1) | coordsAt(t,e){let i=this.dom.getClientRects(),n=null;if(!i.length)retu...
method isEditable (line 1) | get isEditable(){return!1}
method destroy (line 1) | destroy(){super.destroy(),this.dom&&this.widget.destroy(this.dom)}
class je (line 1) | class je extends _e{domAtPos(t){let{topView:e,text:i}=this.widget;return...
method domAtPos (line 1) | domAtPos(t){let{topView:e,text:i}=this.widget;return e?Ue(t,0,e,i,((t,...
method sync (line 1) | sync(){this.setDOM(this.widget.toDOM())}
method localPosFromDOM (line 1) | localPosFromDOM(t,e){let{topView:i,text:n}=this.widget;return i?$e(t,e...
method ignoreMutation (line 1) | ignoreMutation(){return!1}
method overrideDOMText (line 1) | get overrideDOMText(){return null}
method coordsAt (line 1) | coordsAt(t,e){let{topView:i,text:n}=this.widget;return i?Ue(t,e,i,n,((...
method destroy (line 1) | destroy(){var t;super.destroy(),null===(t=this.widget.topView)||void 0...
method isEditable (line 1) | get isEditable(){return!0}
method canReuseDOM (line 1) | canReuseDOM(){return!0}
function Ue (line 1) | function Ue(t,e,i,n,s,r){if(i instanceof Fe){for(let o=i.dom.firstChild;...
function $e (line 1) | function $e(t,e,i,n){if(i instanceof Fe)for(let s of i.children){let i=0...
class Qe (line 1) | class Qe extends Se{constructor(t){super(),this.side=t}get length(){retu...
method constructor (line 1) | constructor(t){super(),this.side=t}
method length (line 1) | get length(){return 0}
method merge (line 1) | merge(){return!1}
method become (line 1) | become(t){return t instanceof Qe&&t.side==this.side}
method split (line 1) | split(){return new Qe(this.side)}
method sync (line 1) | sync(){if(!this.dom){let t=document.createElement("img");t.className="...
method getSide (line 1) | getSide(){return this.side}
method domAtPos (line 1) | domAtPos(t){return xe.before(this.dom)}
method localPosFromDOM (line 1) | localPosFromDOM(){return 0}
method domBoundsAround (line 1) | domBoundsAround(){return null}
method coordsAt (line 1) | coordsAt(t){let e=this.dom.getBoundingClientRect(),i=function(t,e){let...
method overrideDOMText (line 1) | get overrideDOMText(){return e.empty}
function Ke (line 1) | function Ke(t,e){let i=t.dom,{children:n}=t,s=0;for(let t=0;s<n.length;s...
function Ge (line 1) | function Ge(t,e,i){let n,{children:s}=t;i>0&&e instanceof Fe&&s.length&&...
function Je (line 1) | function Je(t,e,i){let n=null,s=-1,r=null,o=-1;!function t(e,i){for(let ...
function Xe (line 1) | function Xe(t,e){for(let i in t)"class"==i&&e.class?e.class+=" "+t.class...
function Ze (line 1) | function Ze(t,e){if(t==e)return!0;if(!t||!e)return!1;let i=Object.keys(t...
function Ye (line 1) | function Ye(t,e,i){let n=null;if(e)for(let s in e)i&&s in i||t.removeAtt...
class ti (line 1) | class ti{eq(t){return!1}updateDOM(t){return!1}compare(t){return this==t|...
method eq (line 1) | eq(t){return!1}
method updateDOM (line 1) | updateDOM(t){return!1}
method compare (line 1) | compare(t){return this==t||this.constructor==t.constructor&&this.eq(t)}
method estimatedHeight (line 1) | get estimatedHeight(){return-1}
method ignoreEvent (line 1) | ignoreEvent(t){return!0}
method customView (line 1) | get customView(){return null}
method destroy (line 1) | destroy(t){}
class ii (line 1) | class ii extends At{constructor(t,e,i,n){super(),this.startSide=t,this.e...
method constructor (line 1) | constructor(t,e,i,n){super(),this.startSide=t,this.endSide=e,this.widg...
method heightRelevant (line 1) | get heightRelevant(){return!1}
method mark (line 1) | static mark(t){return new ni(t)}
method widget (line 1) | static widget(t){let e=t.side||0,i=!!t.block;return e+=i?e>0?3e8:-4e8:...
method replace (line 1) | static replace(t){let e,i,n=!!t.block;if(t.isBlockGap)e=-5e8,i=4e8;els...
method line (line 1) | static line(t){return new si(t)}
method set (line 1) | static set(t,e=!1){return Tt.of(t,e)}
method hasHeight (line 1) | hasHeight(){return!!this.widget&&this.widget.estimatedHeight>-1}
class ni (line 1) | class ni extends ii{constructor(t){let{start:e,end:i}=oi(t);super(e?-1:5...
method constructor (line 1) | constructor(t){let{start:e,end:i}=oi(t);super(e?-1:5e8,i?1:-6e8,null,t...
method eq (line 1) | eq(t){return this==t||t instanceof ni&&this.tagName==t.tagName&&this.c...
method range (line 1) | range(t,e=t){if(t>=e)throw new RangeError("Mark decorations may not be...
class si (line 1) | class si extends ii{constructor(t){super(-2e8,-2e8,null,t)}eq(t){return ...
method constructor (line 1) | constructor(t){super(-2e8,-2e8,null,t)}
method eq (line 1) | eq(t){return t instanceof si&&Ze(this.spec.attributes,t.spec.attributes)}
method range (line 1) | range(t,e=t){if(e!=t)throw new RangeError("Line decoration ranges must...
class ri (line 1) | class ri extends ii{constructor(t,e,i,n,s,r){super(e,i,s,t),this.block=n...
method constructor (line 1) | constructor(t,e,i,n,s,r){super(e,i,s,t),this.block=n,this.isReplace=r,...
method type (line 1) | get type(){return this.startSide<this.endSide?ei.WidgetRange:this.star...
method heightRelevant (line 1) | get heightRelevant(){return this.block||!!this.widget&&this.widget.est...
method eq (line 1) | eq(t){return t instanceof ri&&(e=this.widget,i=t.widget,e==i||!!(e&&i&...
method range (line 1) | range(t,e=t){if(this.isReplace&&(t>e||t==e&&this.startSide>0&&this.end...
function oi (line 1) | function oi(t,e=!1){let{inclusiveStart:i,inclusiveEnd:n}=t;return null==...
function li (line 1) | function li(t,e,i,n=0){let s=i.length-1;s>=0&&i[s]+n>=t?i[s]=Math.max(i[...
class ai (line 1) | class ai extends Se{constructor(){super(...arguments),this.children=[],t...
method constructor (line 1) | constructor(){super(...arguments),this.children=[],this.length=0,this....
method merge (line 1) | merge(t,e,i,n,s,r){if(i){if(!(i instanceof ai))return!1;this.dom||i.tr...
method split (line 1) | split(t){let e=new ai;if(e.breakAfter=this.breakAfter,0==this.length)r...
method transferDOM (line 1) | transferDOM(t){this.dom&&(this.markDirty(),t.setDOM(this.dom),t.prevAt...
method setDeco (line 1) | setDeco(t){Ze(this.attrs,t)||(this.dom&&(this.prevAttrs=this.attrs,thi...
method append (line 1) | append(t,e){Ge(this,t,e)}
method addLineDeco (line 1) | addLineDeco(t){let e=t.spec.attributes,i=t.spec.class;e&&(this.attrs=X...
method domAtPos (line 1) | domAtPos(t){return Ke(this,t)}
method reuseDOM (line 1) | reuseDOM(t){"DIV"==t.nodeName&&(this.setDOM(t),this.dirty|=6)}
method sync (line 1) | sync(t){var e;this.dom?4&this.dirty&&(be(this.dom),this.dom.className=...
method measureTextSize (line 1) | measureTextSize(){if(0==this.children.length||this.length>20)return nu...
method coordsAt (line 1) | coordsAt(t,e){return Je(this,t,e)}
method become (line 1) | become(t){return!1}
method type (line 1) | get type(){return ei.Text}
method find (line 1) | static find(t,e){for(let i=0,n=0;i<t.children.length;i++){let s=t.chil...
class hi (line 1) | class hi extends Se{constructor(t,e,i){super(),this.widget=t,this.length...
method constructor (line 1) | constructor(t,e,i){super(),this.widget=t,this.length=e,this.type=i,thi...
method merge (line 1) | merge(t,e,i,n,s,r){return!(i&&(!(i instanceof hi&&this.widget.compare(...
method domAtPos (line 1) | domAtPos(t){return 0==t?xe.before(this.dom):xe.after(this.dom,t==this....
method split (line 1) | split(t){let e=this.length-t;this.length=t;let i=new hi(this.widget,e,...
method children (line 1) | get children(){return ke}
method sync (line 1) | sync(){this.dom&&this.widget.updateDOM(this.dom)||(this.dom&&this.prev...
method overrideDOMText (line 1) | get overrideDOMText(){return this.parent?this.parent.view.state.doc.sl...
method domBoundsAround (line 1) | domBoundsAround(){return null}
method become (line 1) | become(t){return t instanceof hi&&t.type==this.type&&t.widget.construc...
method ignoreMutation (line 1) | ignoreMutation(){return!0}
method ignoreEvent (line 1) | ignoreEvent(t){return this.widget.ignoreEvent(t)}
method destroy (line 1) | destroy(){super.destroy(),this.dom&&this.widget.destroy(this.dom)}
class ci (line 1) | class ci{constructor(t,e,i,n){this.doc=t,this.pos=e,this.end=i,this.disa...
method constructor (line 1) | constructor(t,e,i,n){this.doc=t,this.pos=e,this.end=i,this.disallowBlo...
method posCovered (line 1) | posCovered(){if(0==this.content.length)return!this.breakAtStart&&this....
method getLine (line 1) | getLine(){return this.curLine||(this.content.push(this.curLine=new ai)...
method flushBuffer (line 1) | flushBuffer(t){this.pendingBuffer&&(this.curLine.append(ui(new Qe(-1),...
method addBlockWidget (line 1) | addBlockWidget(t){this.flushBuffer([]),this.curLine=null,this.content....
method finish (line 1) | finish(t){t?this.pendingBuffer=0:this.flushBuffer([]),this.posCovered(...
method buildText (line 1) | buildText(t,e,i){for(;t>0;){if(this.textOff==this.text.length){let{val...
method span (line 1) | span(t,e,i,n){this.buildText(e-t,i,n),this.pos=e,this.openStart<0&&(th...
method point (line 1) | point(t,e,i,n,s,r){if(this.disallowBlockEffectsFor[r]&&i instanceof ri...
method build (line 1) | static build(t,e,i,n,s){let r=new ci(t,e,i,s);return r.openEnd=Tt.span...
function ui (line 1) | function ui(t,e){for(let i of e)t=new Fe(i,[t],t.length);return t}
class fi (line 1) | class fi extends ti{constructor(t){super(),this.tag=t}eq(t){return t.tag...
method constructor (line 1) | constructor(t){super(),this.tag=t}
method eq (line 1) | eq(t){return t.tag==this.tag}
method toDOM (line 1) | toDOM(){return document.createElement(this.tag)}
method updateDOM (line 1) | updateDOM(t){return t.nodeName.toLowerCase()==this.tag}
class xi (line 1) | class xi{constructor(t,e="nearest",i="nearest",n=5,s=5){this.range=t,thi...
method constructor (line 1) | constructor(t,e="nearest",i="nearest",n=5,s=5){this.range=t,this.y=e,t...
method map (line 1) | map(t){return t.empty?this:new xi(this.range.map(t),this.y,this.x,this...
function Si (line 1) | function Si(t,e,i){let n=t.facet(gi);n.length?n[0](e):window.onerror?win...
class Mi (line 1) | class Mi{constructor(t,e,i,n){this.id=t,this.create=e,this.domEventHandl...
method constructor (line 1) | constructor(t,e,i,n){this.id=t,this.create=e,this.domEventHandlers=i,t...
method define (line 1) | static define(t,e){const{eventHandlers:i,provide:n,decorations:s}=e||{...
method fromClass (line 1) | static fromClass(t,e){return Mi.define((e=>new t(e)),e)}
class Di (line 1) | class Di{constructor(t){this.spec=t,this.mustUpdate=null,this.value=null...
method constructor (line 1) | constructor(t){this.spec=t,this.mustUpdate=null,this.value=null}
method update (line 1) | update(t){if(this.value){if(this.mustUpdate){let t=this.mustUpdate;if(...
method destroy (line 1) | destroy(t){var e;if(null===(e=this.value)||void 0===e?void 0:e.destroy...
method deactivate (line 1) | deactivate(){this.spec=this.value=null}
class Ni (line 1) | class Ni{constructor(t,e,i,n){this.fromA=t,this.toA=e,this.fromB=i,this....
method constructor (line 1) | constructor(t,e,i,n){this.fromA=t,this.toA=e,this.fromB=i,this.toB=n}
method join (line 1) | join(t){return new Ni(Math.min(this.fromA,t.fromA),Math.max(this.toA,t...
method addToSet (line 1) | addToSet(t){let e=t.length,i=this;for(;e>0;e--){let n=t[e-1];if(!(n.fr...
method extendWithRanges (line 1) | static extendWithRanges(t,e){if(0==e.length)return t;let i=[];for(let ...
class Ii (line 1) | class Ii{constructor(t,e,i){this.view=t,this.state=e,this.transactions=i...
method constructor (line 1) | constructor(t,e,i){this.view=t,this.state=e,this.transactions=i,this.f...
method create (line 1) | static create(t,e,i){return new Ii(t,e,i)}
method viewportChanged (line 1) | get viewportChanged(){return(4&this.flags)>0}
method heightChanged (line 1) | get heightChanged(){return(2&this.flags)>0}
method geometryChanged (line 1) | get geometryChanged(){return this.docChanged||(10&this.flags)>0}
method focusChanged (line 1) | get focusChanged(){return(1&this.flags)>0}
method docChanged (line 1) | get docChanged(){return!this.changes.empty}
method selectionSet (line 1) | get selectionSet(){return this.transactions.some((t=>t.selection))}
method empty (line 1) | get empty(){return 0==this.flags&&0==this.transactions.length}
function Hi (line 1) | function Hi(t){let e=[];for(let i=0;i<t.length;i++)e.push(1<<+t[i]);retu...
class $i (line 1) | class $i{constructor(t,e,i){this.from=t,this.to=e,this.level=i}get dir()...
method constructor (line 1) | constructor(t,e,i){this.from=t,this.to=e,this.level=i}
method dir (line 1) | get dir(){return this.level%2?zi:Wi}
method side (line 1) | side(t,e){return this.dir==e==t?this.to:this.from}
method find (line 1) | static find(t,e,i,n){let s=-1;for(let r=0;r<t.length;r++){let o=t[r];i...
function Ki (line 1) | function Ki(t){return[new $i(0,t,0)]}
function Ji (line 1) | function Ji(t,e,i,n,s){var r;let o=n.head-t.from,l=-1;if(0==o){if(!s||!t...
class Zi (line 1) | class Zi{constructor(t,e){this.points=t,this.text="",this.lineSeparator=...
method constructor (line 1) | constructor(t,e){this.points=t,this.text="",this.lineSeparator=e.facet...
method append (line 1) | append(t){this.text+=t}
method lineBreak (line 1) | lineBreak(){this.text+=Xi}
method readRange (line 1) | readRange(t,e){if(!t)return this;let i=t.parentNode;for(let n=t;;){thi...
method readTextNode (line 1) | readTextNode(t){let e=t.nodeValue;for(let i of this.points)i.node==t&&...
method readNode (line 1) | readNode(t){if(t.cmIgnore)return;let e=Se.get(t),i=e&&e.overrideDOMTex...
method findPointBefore (line 1) | findPointBefore(t,e){for(let i of this.points)i.node==t&&t.childNodes[...
method findPointInside (line 1) | findPointInside(t,e){for(let i of this.points)(3==t.nodeType?i.node==t...
function Yi (line 1) | function Yi(t){return 1==t.nodeType&&/^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|...
class tn (line 1) | class tn{constructor(t,e){this.node=t,this.offset=e,this.pos=-1}}
method constructor (line 1) | constructor(t,e){this.node=t,this.offset=e,this.pos=-1}
class en (line 1) | class en extends Se{constructor(t){super(),this.view=t,this.compositionD...
method constructor (line 1) | constructor(t){super(),this.view=t,this.compositionDeco=ii.none,this.d...
method editorView (line 1) | get editorView(){return this.view}
method length (line 1) | get length(){return this.view.state.doc.length}
method update (line 1) | update(t){let e=t.changedRanges;this.minWidth>0&&e.length&&(e.every(((...
method updateInner (line 1) | updateInner(t,e){this.view.viewState.mustMeasureContent=!0,this.update...
method updateChildren (line 1) | updateChildren(t,e){let i=this.childCursor(e);for(let e=t.length-1;;e-...
method updateSelection (line 1) | updateSelection(t=!1,e=!1){if(!t&&this.view.observer.selectionRange.fo...
method enforceCursorAssoc (line 1) | enforceCursorAssoc(){if(this.compositionDeco.size)return;let{view:t}=t...
method mayControlSelection (line 1) | mayControlSelection(){let t=this.view.root.activeElement;return t==thi...
method nearest (line 1) | nearest(t){for(let e=t;e;){let t=Se.get(e);if(t&&t.rootView==this)retu...
method posFromDOM (line 1) | posFromDOM(t,e){let i=this.nearest(t);if(!i)throw new RangeError("Tryi...
method domAtPos (line 1) | domAtPos(t){let{i:e,off:i}=this.childCursor().findPos(t,-1);for(;e<thi...
method coordsAt (line 1) | coordsAt(t,e){for(let i=this.length,n=this.children.length-1;;n--){let...
method measureVisibleLineHeights (line 1) | measureVisibleLineHeights(t){let e=[],{from:i,to:n}=t,s=this.view.cont...
method textDirectionAt (line 1) | textDirectionAt(t){let{i:e}=this.childPos(t,1);return"rtl"==getCompute...
method measureTextSize (line 1) | measureTextSize(){for(let t of this.children)if(t instanceof ai){let e...
method childCursor (line 1) | childCursor(t=this.length){let e=this.children.length;return e&&(t-=th...
method computeBlockGapDeco (line 1) | computeBlockGapDeco(){let t=[],e=this.view.viewState;for(let i=0,n=0;;...
method updateDeco (line 1) | updateDeco(){let t=this.view.state.facet(Ri).map(((t,e)=>(this.dynamic...
method scrollIntoView (line 1) | scrollIntoView(t){let e,{range:i}=t,n=this.coordsAt(i.head,i.empty?i.a...
class nn (line 1) | class nn extends ti{constructor(t){super(),this.height=t}toDOM(){let t=d...
method constructor (line 1) | constructor(t){super(),this.height=t}
method toDOM (line 1) | toDOM(){let t=document.createElement("div");return this.updateDOM(t),t}
method eq (line 1) | eq(t){return t.height==this.height}
method updateDOM (line 1) | updateDOM(t){return t.style.height=this.height+"px",!0}
method estimatedHeight (line 1) | get estimatedHeight(){return this.height}
function sn (line 1) | function sn(t){let e=t.observer.selectionRange,i=e.focusNode&&on(e.focus...
class rn (line 1) | class rn extends ti{constructor(t,e,i){super(),this.top=t,this.text=e,th...
method constructor (line 1) | constructor(t,e,i){super(),this.top=t,this.text=e,this.topView=i}
method eq (line 1) | eq(t){return this.top==t.top&&this.text==t.text}
method toDOM (line 1) | toDOM(){return this.top}
method ignoreEvent (line 1) | ignoreEvent(){return!1}
method customView (line 1) | get customView(){return je}
function on (line 1) | function on(t,e,i){for(;;){if(3==t.nodeType)return t;if(1==t.nodeType&&e...
class ln (line 1) | class ln{constructor(){this.changes=[]}compareRange(t,e){li(t,e,this.cha...
method constructor (line 1) | constructor(){this.changes=[]}
method compareRange (line 1) | compareRange(t,e){li(t,e,this.changes)}
method comparePoint (line 1) | comparePoint(t,e){li(t,e,this.changes)}
function an (line 1) | function an(t,e){return e.left>t?e.left-t:Math.max(0,t-e.right)}
function hn (line 1) | function hn(t,e){return e.top>t?e.top-t:Math.max(0,t-e.bottom)}
function cn (line 1) | function cn(t,e){return t.top<e.bottom-1&&t.bottom>e.top+1}
function un (line 1) | function un(t,e){return e<t.top?{top:e,left:t.left,right:t.right,bottom:...
function fn (line 1) | function fn(t,e){return e>t.bottom?{top:t.top,left:t.left,right:t.right,...
function dn (line 1) | function dn(t,e,i){let n,s,r,o,l,a,h,c,u=!1;for(let f=t.firstChild;f;f=f...
function pn (line 1) | function pn(t,e,i){let n=t.nodeValue.length,s=-1,r=1e9,o=0;for(let l=0;l...
function mn (line 1) | function mn(t,{x:e,y:i},n,s=-1){var r;let o,l=t.contentDOM.getBoundingCl...
function gn (line 1) | function gn(t,e,i,n,s){let r=Math.round((n-e.left)*t.defaultCharacterWid...
function vn (line 1) | function vn(t,e,i,n){let s=t.state.doc.lineAt(e.head),r=t.bidiSpans(s),o...
function wn (line 1) | function wn(t,e,i){let n=t.state.facet(Ei).map((e=>e(t)));for(;;){let t=...
class yn (line 1) | class yn{constructor(t){this.lastKeyCode=0,this.lastKeyTime=0,this.lastT...
method constructor (line 1) | constructor(t){this.lastKeyCode=0,this.lastKeyTime=0,this.lastTouchTim...
method setSelectionOrigin (line 1) | setSelectionOrigin(t){this.lastSelectionOrigin=t,this.lastSelectionTim...
method ensureHandlers (line 1) | ensureHandlers(t,e){var i;let n;this.customHandlers=[];for(let s of e)...
method runCustomHandlers (line 1) | runCustomHandlers(t,e,i){for(let n of this.customHandlers){let s=n.han...
method runScrollHandlers (line 1) | runScrollHandlers(t,e){this.lastScrollTop=t.scrollDOM.scrollTop,this.l...
method keydown (line 1) | keydown(t,e){if(this.lastKeyCode=e.keyCode,this.lastKeyTime=Date.now()...
method flushIOSKey (line 1) | flushIOSKey(t){let e=this.pendingIOSKey;return!!e&&(this.pendingIOSKey...
method ignoreDuringComposition (line 1) | ignoreDuringComposition(t){return!!/^key/.test(t.type)&&(this.composin...
method mustFlushObserver (line 1) | mustFlushObserver(t){return"keydown"==t.type&&229!=t.keyCode}
method startMouseSelection (line 1) | startMouseSelection(t){this.mouseSelection&&this.mouseSelection.destro...
method update (line 1) | update(t){this.mouseSelection&&this.mouseSelection.update(t),t.transac...
method destroy (line 1) | destroy(){this.mouseSelection&&this.mouseSelection.destroy()}
class Sn (line 1) | class Sn{constructor(t,e,i,n){this.view=t,this.style=i,this.mustSelect=n...
method constructor (line 1) | constructor(t,e,i,n){this.view=t,this.style=i,this.mustSelect=n,this.l...
method move (line 1) | move(t){if(0==t.buttons)return this.destroy();!1===this.dragging&&this...
method up (line 1) | up(t){null==this.dragging&&this.select(this.lastEvent),this.dragging||...
method destroy (line 1) | destroy(){let t=this.view.contentDOM.ownerDocument;t.removeEventListen...
method select (line 1) | select(t){let e=this.style.get(t,this.extend,this.multiple);!this.must...
method update (line 1) | update(t){t.docChanged&&this.dragging&&(this.dragging=this.dragging.ma...
function Cn (line 1) | function Cn(t,e){if(!e.bubbles)return!0;if(e.defaultPrevented)return!1;f...
function Dn (line 1) | function Dn(t,e){let i,{state:n}=t,s=1,r=n.toText(e),o=r.lines==n.select...
function Tn (line 1) | function Tn(t,e,i,n){if(1==n)return E.cursor(e,i);if(2==n)return functio...
method update (line 1) | update(t){t.docChanged&&(i.pos=t.changes.mapPos(i.pos),s=s.map(t.changes...
method get (line 1) | get(e,l,a){let h;o&&e.clientX==o.clientX&&e.clientY==o.clientY?h=r:(h=r=...
function En (line 1) | function En(t,e,i,n){let s=ai.find(t.docView,e);if(!s)return 1;let r=e-s...
function Bn (line 1) | function Bn(t,e){let i=t.posAtCoords({x:e.clientX,y:e.clientY},!1);retur...
function Wn (line 1) | function Wn(t){if(!Ln)return t.detail;let e=Nn,i=Vn;return Nn=t,Vn=Date....
function zn (line 1) | function zn(t,e,i,n){if(!i)return;let s=t.posAtCoords({x:e.clientX,y:e.c...
function Fn (line 1) | function Fn(t){setTimeout((()=>{t.hasFocus!=t.inputState.notifiedFocused...
class _n (line 1) | class _n{constructor(t){this.lineWrapping=t,this.doc=e.empty,this.height...
method constructor (line 1) | constructor(t){this.lineWrapping=t,this.doc=e.empty,this.heightSamples...
method heightForGap (line 1) | heightForGap(t,e){let i=this.doc.lineAt(e).number-this.doc.lineAt(t).n...
method heightForLine (line 1) | heightForLine(t){if(!this.lineWrapping)return this.lineHeight;return(1...
method setDoc (line 1) | setDoc(t){return this.doc=t,this}
method mustRefreshForWrapping (line 1) | mustRefreshForWrapping(t){return qn.indexOf(t)>-1!=this.lineWrapping}
method mustRefreshForHeights (line 1) | mustRefreshForHeights(t){let e=!1;for(let i=0;i<t.length;i++){let n=t[...
method refresh (line 1) | refresh(t,e,i,n,s){let r=qn.indexOf(t)>-1,o=Math.round(e)!=Math.round(...
class jn (line 1) | class jn{constructor(t,e){this.from=t,this.heights=e,this.index=0}get mo...
method constructor (line 1) | constructor(t,e){this.from=t,this.heights=e,this.index=0}
method more (line 1) | get more(){return this.index<this.heights.length}
class Un (line 1) | class Un{constructor(t,e,i,n,s){this.from=t,this.length=e,this.top=i,thi...
method constructor (line 1) | constructor(t,e,i,n,s){this.from=t,this.length=e,this.top=i,this.heigh...
method to (line 1) | get to(){return this.from+this.length}
method bottom (line 1) | get bottom(){return this.top+this.height}
method join (line 1) | join(t){let e=(Array.isArray(this.type)?this.type:[this]).concat(Array...
class Kn (line 1) | class Kn{constructor(t,e,i=2){this.length=t,this.height=e,this.flags=i}g...
method constructor (line 1) | constructor(t,e,i=2){this.length=t,this.height=e,this.flags=i}
method outdated (line 1) | get outdated(){return(2&this.flags)>0}
method outdated (line 1) | set outdated(t){this.flags=(t?2:0)|-3&this.flags}
method setHeight (line 1) | setHeight(t,e){this.height!=e&&(Math.abs(this.height-e)>Qn&&(t.heightC...
method replace (line 1) | replace(t,e,i){return Kn.of(i)}
method decomposeLeft (line 1) | decomposeLeft(t,e){e.push(this)}
method decomposeRight (line 1) | decomposeRight(t,e){e.push(this)}
method applyChanges (line 1) | applyChanges(t,e,i,n){let s=this;for(let r=n.length-1;r>=0;r--){let{fr...
method empty (line 1) | static empty(){return new Jn(0,0)}
method of (line 1) | static of(t){if(1==t.length)return t[0];let e=0,i=t.length,n=0,s=0;for...
class Gn (line 1) | class Gn extends Kn{constructor(t,e,i){super(t,e),this.type=i}blockAt(t,...
method constructor (line 1) | constructor(t,e,i){super(t,e),this.type=i}
method blockAt (line 1) | blockAt(t,e,i,n){return new Un(n,this.length,i,this.height,this.type)}
method lineAt (line 1) | lineAt(t,e,i,n,s){return this.blockAt(0,i,n,s)}
method forEachLine (line 1) | forEachLine(t,e,i,n,s,r){t<=s+this.length&&e>=s&&r(this.blockAt(0,i,n,...
method updateHeight (line 1) | updateHeight(t,e=0,i=!1,n){return n&&n.from<=e&&n.more&&this.setHeight...
method toString (line 1) | toString(){return`block(${this.length})`}
class Jn (line 1) | class Jn extends Gn{constructor(t,e){super(t,e,ei.Text),this.collapsed=0...
method constructor (line 1) | constructor(t,e){super(t,e,ei.Text),this.collapsed=0,this.widgetHeight=0}
method replace (line 1) | replace(t,e,i){let n=i[0];return 1==i.length&&(n instanceof Jn||n inst...
method updateHeight (line 1) | updateHeight(t,e=0,i=!1,n){return n&&n.from<=e&&n.more?this.setHeight(...
method toString (line 1) | toString(){return`line(${this.length}${this.collapsed?-this.collapsed:...
class Xn (line 1) | class Xn extends Kn{constructor(t){super(t,0)}lines(t,e){let i=t.lineAt(...
method constructor (line 1) | constructor(t){super(t,0)}
method lines (line 1) | lines(t,e){let i=t.lineAt(e).number,n=t.lineAt(e+this.length).number;r...
method blockAt (line 1) | blockAt(t,e,i,n){let{firstLine:s,lastLine:r,lineHeight:o}=this.lines(e...
method lineAt (line 1) | lineAt(t,e,i,n,s){if(e==$n.ByHeight)return this.blockAt(t,i,n,s);if(e=...
method forEachLine (line 1) | forEachLine(t,e,i,n,s,r){let{firstLine:o,lineHeight:l}=this.lines(i,s)...
method replace (line 1) | replace(t,e,i){let n=this.length-e;if(n>0){let t=i[i.length-1];t insta...
method decomposeLeft (line 1) | decomposeLeft(t,e){e.push(new Xn(t-1),null)}
method decomposeRight (line 1) | decomposeRight(t,e){e.push(null,new Xn(this.length-t-1))}
method updateHeight (line 1) | updateHeight(t,e=0,i=!1,n){let s=e+this.length;if(n&&n.from<=e+this.le...
method toString (line 1) | toString(){return`gap(${this.length})`}
class Zn (line 1) | class Zn extends Kn{constructor(t,e,i){super(t.length+e+i.length,t.heigh...
method constructor (line 1) | constructor(t,e,i){super(t.length+e+i.length,t.height+i.height,e|(t.ou...
method break (line 1) | get break(){return 1&this.flags}
method blockAt (line 1) | blockAt(t,e,i,n){let s=i+this.left.height;return t<s?this.left.blockAt...
method lineAt (line 1) | lineAt(t,e,i,n,s){let r=n+this.left.height,o=s+this.left.length+this.b...
method forEachLine (line 1) | forEachLine(t,e,i,n,s,r){let o=n+this.left.height,l=s+this.left.length...
method replace (line 1) | replace(t,e,i){let n=this.left.length+this.break;if(e<n)return this.ba...
method decomposeLeft (line 1) | decomposeLeft(t,e){let i=this.left.length;if(t<=i)return this.left.dec...
method decomposeRight (line 1) | decomposeRight(t,e){let i=this.left.length,n=i+this.break;if(t>=n)retu...
method balanced (line 1) | balanced(t,e){return t.size>2*e.size||e.size>2*t.size?Kn.of(this.break...
method updateHeight (line 1) | updateHeight(t,e=0,i=!1,n){let{left:s,right:r}=this,o=e+s.length+this....
method toString (line 1) | toString(){return this.left+(this.break?" ":"-")+this.right}
function Yn (line 1) | function Yn(t,e){let i,n;null==t[e]&&(i=t[e-1])instanceof Xn&&(n=t[e+1])...
class ts (line 1) | class ts{constructor(t,e){this.pos=t,this.oracle=e,this.nodes=[],this.li...
method constructor (line 1) | constructor(t,e){this.pos=t,this.oracle=e,this.nodes=[],this.lineStart...
method isCovered (line 1) | get isCovered(){return this.covering&&this.nodes[this.nodes.length-1]=...
method span (line 1) | span(t,e){if(this.lineStart>-1){let t=Math.min(e,this.lineEnd),i=this....
method point (line 1) | point(t,e,i){if(t<e||i.heightRelevant){let n=i.widget?i.widget.estimat...
method enterLine (line 1) | enterLine(){if(this.lineStart>-1)return;let{from:t,to:e}=this.oracle.d...
method blankContent (line 1) | blankContent(t,e){let i=new Xn(e-t);return this.oracle.doc.lineAt(t).t...
method ensureLine (line 1) | ensureLine(){this.enterLine();let t=this.nodes.length?this.nodes[this....
method addBlock (line 1) | addBlock(t){this.enterLine(),t.type!=ei.WidgetAfter||this.isCovered||t...
method addLineDeco (line 1) | addLineDeco(t,e){let i=this.ensureLine();i.length+=e,i.collapsed+=e,i....
method finish (line 1) | finish(t){let e=0==this.nodes.length?null:this.nodes[this.nodes.length...
method build (line 1) | static build(t,e,i,n){let s=new ts(i,t);return Tt.spans(e,i,n,s,0),s.f...
class es (line 1) | class es{constructor(){this.changes=[]}compareRange(){}comparePoint(t,e,...
method constructor (line 1) | constructor(){this.changes=[]}
method compareRange (line 1) | compareRange(){}
method comparePoint (line 1) | comparePoint(t,e,i,n){(t<e||i&&i.heightRelevant||n&&n.heightRelevant)&...
function is (line 1) | function is(t,e){let i=t.getBoundingClientRect(),n=t.ownerDocument,s=n.d...
function ns (line 1) | function ns(t,e){let i=t.getBoundingClientRect();return{left:0,right:i.r...
class ss (line 1) | class ss{constructor(t,e,i){this.from=t,this.to=e,this.size=i}static sam...
method constructor (line 1) | constructor(t,e,i){this.from=t,this.to=e,this.size=i}
method same (line 1) | static same(t,e){if(t.length!=e.length)return!1;for(let i=0;i<t.length...
method draw (line 1) | draw(t){return ii.replace({widget:new rs(this.size,t)}).range(this.fro...
class rs (line 1) | class rs extends ti{constructor(t,e){super(),this.size=t,this.vertical=e...
method constructor (line 1) | constructor(t,e){super(),this.size=t,this.vertical=e}
method eq (line 1) | eq(t){return t.size==this.size&&t.vertical==this.vertical}
method toDOM (line 1) | toDOM(){let t=document.createElement("div");return this.vertical?t.sty...
method estimatedHeight (line 1) | get estimatedHeight(){return this.vertical?this.size:-1}
class os (line 1) | class os{constructor(t){this.state=t,this.pixelViewport={left:0,right:wi...
method constructor (line 1) | constructor(t){this.state=t,this.pixelViewport={left:0,right:window.in...
method updateForViewport (line 1) | updateForViewport(){let t=[this.viewport],{main:e}=this.state.selectio...
method updateViewportLines (line 1) | updateViewportLines(){this.viewportLines=[],this.heightMap.forEachLine...
method update (line 1) | update(t,e=null){this.state=t.state;let i=this.stateDeco;this.stateDec...
method measure (line 1) | measure(t){let i=t.contentDOM,n=window.getComputedStyle(i),s=this.heig...
method visibleTop (line 1) | get visibleTop(){return this.scaler.fromDOM(this.pixelViewport.top)}
method visibleBottom (line 1) | get visibleBottom(){return this.scaler.fromDOM(this.pixelViewport.bott...
method getViewport (line 1) | getViewport(t,e){let i=.5-Math.max(-.5,Math.min(.5,t/1e3/2)),n=this.he...
method mapViewport (line 1) | mapViewport(t,e){let i=e.mapPos(t.from,-1),n=e.mapPos(t.to,1);return n...
method viewportIsAppropriate (line 1) | viewportIsAppropriate({from:t,to:e},i=0){if(!this.inView)return!0;let{...
method mapLineGaps (line 1) | mapLineGaps(t,e){if(!t.length||e.empty)return t;let i=[];for(let n of ...
method ensureLineGaps (line 1) | ensureLineGaps(t,e){let i=this.heightOracle.lineWrapping,n=i?1e4:2e3,s...
method gapSize (line 1) | gapSize(t,e,i,n){let s=cs(n,i)-cs(n,e);return this.heightOracle.lineWr...
method updateLineGaps (line 1) | updateLineGaps(t){ss.same(t,this.lineGaps)||(this.lineGaps=t,this.line...
method computeVisibleRanges (line 1) | computeVisibleRanges(){let t=this.stateDeco;this.lineGaps.length&&(t=t...
method lineBlockAt (line 1) | lineBlockAt(t){return t>=this.viewport.from&&t<=this.viewport.to&&this...
method lineBlockAtHeight (line 1) | lineBlockAtHeight(t){return ds(this.heightMap.lineAt(this.scaler.fromD...
method elementAtHeight (line 1) | elementAtHeight(t){return ds(this.heightMap.blockAt(this.scaler.fromDO...
method docHeight (line 1) | get docHeight(){return this.scaler.toDOM(this.heightMap.height)}
method contentHeight (line 1) | get contentHeight(){return this.docHeight+this.paddingTop+this.padding...
class ls (line 1) | class ls{constructor(t,e){this.from=t,this.to=e}}
method constructor (line 1) | constructor(t,e){this.from=t,this.to=e}
function as (line 1) | function as(t,e,i){let n=[],s=t,r=0;return Tt.spans(i,t,e,{span(){},poin...
function hs (line 1) | function hs({total:t,ranges:e},i){if(i<=0)return e[0].from;if(i>=1)retur...
function cs (line 1) | function cs(t,e){let i=0;for(let{from:n,to:s}of t.ranges){if(e<=s){i+=e-...
class fs (line 1) | class fs{constructor(t,e,i){let n=0,s=0,r=0;this.viewports=i.map((({from...
method constructor (line 1) | constructor(t,e,i){let n=0,s=0,r=0;this.viewports=i.map((({from:i,to:s...
method toDOM (line 1) | toDOM(t){for(let e=0,i=0,n=0;;e++){let s=e<this.viewports.length?this....
method fromDOM (line 1) | fromDOM(t){for(let e=0,i=0,n=0;;e++){let s=e<this.viewports.length?thi...
function ds (line 1) | function ds(t,e){if(1==e.scale)return t;let i=e.toDOM(t.top),n=e.toDOM(t...
function bs (line 1) | function bs(t,e,i){return new $t(e,{finish:e=>/&/.test(e)?e.replace(/&\w...
class ks (line 1) | class ks{constructor(t,e,i,n){this.typeOver=n,this.bounds=null,this.text...
method constructor (line 1) | constructor(t,e,i,n){this.typeOver=n,this.bounds=null,this.text="";let...
function Ss (line 1) | function Ss(t,i){let n,{newSel:s}=i,r=t.state.selection.main;if(i.bounds...
class Os (line 1) | class Os{constructor(t){this.view=t,this.active=!1,this.selectionRange=n...
method constructor (line 1) | constructor(t){this.view=t,this.active=!1,this.selectionRange=new pe,t...
method onScrollChanged (line 1) | onScrollChanged(t){this.view.inputState.runScrollHandlers(this.view,t)...
method onScroll (line 1) | onScroll(t){this.intersecting&&this.flush(!1),this.onScrollChanged(t)}
method onResize (line 1) | onResize(){this.resizeTimeout<0&&(this.resizeTimeout=setTimeout((()=>{...
method onPrint (line 1) | onPrint(){this.view.viewState.printing=!0,this.view.measure(),setTimeo...
method updateGaps (line 1) | updateGaps(t){if(this.gapIntersection&&(t.length!=this.gaps.length||th...
method onSelectionChange (line 1) | onSelectionChange(t){let e=this.selectionChanged;if(!this.readSelectio...
method readSelectionRange (line 1) | readSelectionRange(){let{view:t}=this,e=ze.safari&&11==t.root.nodeType...
method setSelectionRange (line 1) | setSelectionRange(t,e){this.selectionRange.set(t.node,t.offset,e.node,...
method clearSelectionRange (line 1) | clearSelectionRange(){this.selectionRange.set(null,0,null,0)}
method listenForScroll (line 1) | listenForScroll(){this.parentCheck=-1;let t=0,e=null;for(let i=this.do...
method ignore (line 1) | ignore(t){if(!this.active)return t();try{return this.stop(),t()}finall...
method start (line 1) | start(){this.active||(this.observer.observe(this.dom,Cs),As&&this.dom....
method stop (line 1) | stop(){this.active&&(this.active=!1,this.observer.disconnect(),As&&thi...
method clear (line 1) | clear(){this.processRecords(),this.queue.length=0,this.selectionChange...
method delayAndroidKey (line 1) | delayAndroidKey(t,e){var i;if(!this.delayedAndroidKey){let t=()=>{let ...
method clearDelayedAndroidKey (line 1) | clearDelayedAndroidKey(){this.win.cancelAnimationFrame(this.flushingAn...
method flushSoon (line 1) | flushSoon(){this.delayedFlush<0&&(this.delayedFlush=this.view.win.requ...
method forceFlush (line 1) | forceFlush(){this.delayedFlush>=0&&(this.view.win.cancelAnimationFrame...
method processRecords (line 1) | processRecords(){let t=this.queue;for(let e of this.observer.takeRecor...
method readChange (line 1) | readChange(){let{from:t,to:e,typeOver:i}=this.processRecords(),n=this....
method flush (line 1) | flush(t=!0){if(this.delayedFlush>=0||this.delayedAndroidKey)return!1;t...
method readMutation (line 1) | readMutation(t){let e=this.view.docView.nearest(t.target);if(!e||e.ign...
method setWindow (line 1) | setWindow(t){t!=this.win&&(this.removeWindowListeners(this.win),this.w...
method addWindowListeners (line 1) | addWindowListeners(t){t.addEventListener("resize",this.onResize),t.add...
method removeWindowListeners (line 1) | removeWindowListeners(t){t.removeEventListener("scroll",this.onScroll)...
method destroy (line 1) | destroy(){var t,e,i;this.stop(),null===(t=this.intersection)||void 0==...
function Ms (line 1) | function Ms(t,e,i){for(;e;){let n=Se.get(e);if(n&&n.parent==t)return n;l...
class Ds (line 1) | class Ds{constructor(t={}){this.plugins=[],this.pluginMap=new Map,this.e...
method constructor (line 1) | constructor(t={}){this.plugins=[],this.pluginMap=new Map,this.editorAt...
method state (line 1) | get state(){return this.viewState.state}
method viewport (line 1) | get viewport(){return this.viewState.viewport}
method visibleRanges (line 1) | get visibleRanges(){return this.viewState.visibleRanges}
method inView (line 1) | get inView(){return this.viewState.inView}
method composing (line 1) | get composing(){return this.inputState.composing>0}
method compositionStarted (line 1) | get compositionStarted(){return this.inputState.composing>=0}
method root (line 1) | get root(){return this._root}
method win (line 1) | get win(){return this.dom.ownerDocument.defaultView||window}
method dispatch (line 1) | dispatch(...t){this._dispatch(1==t.length&&t[0]instanceof ft?t[0]:this...
method update (line 1) | update(t){if(0!=this.updateState)throw new Error("Calls to EditorView....
method setState (line 1) | setState(t){if(0!=this.updateState)throw new Error("Calls to EditorVie...
method updatePlugins (line 1) | updatePlugins(t){let e=t.startState.facet(Oi),i=t.state.facet(Oi);if(e...
method measure (line 1) | measure(t=!0){if(this.destroyed)return;this.measureScheduled>-1&&cance...
method themeClasses (line 1) | get themeClasses(){return gs+" "+(this.state.facet(ms)?ws:vs)+" "+this...
method updateAttrs (line 1) | updateAttrs(){let t=Es(this,Ti,{class:"cm-editor"+(this.hasFocus?" cm-...
method showAnnouncements (line 1) | showAnnouncements(t){let e=!0;for(let i of t)for(let t of i.effects)if...
method mountStyles (line 1) | mountStyles(){this.styleModules=this.state.facet(Li),$t.mount(this.roo...
method readMeasured (line 1) | readMeasured(){if(2==this.updateState)throw new Error("Reading the edi...
method requestMeasure (line 1) | requestMeasure(t){if(this.measureScheduled<0&&(this.measureScheduled=t...
method plugin (line 1) | plugin(t){let e=this.pluginMap.get(t);return(void 0===e||e&&e.spec!=t)...
method documentTop (line 1) | get documentTop(){return this.contentDOM.getBoundingClientRect().top+t...
method documentPadding (line 1) | get documentPadding(){return{top:this.viewState.paddingTop,bottom:this...
method elementAtHeight (line 1) | elementAtHeight(t){return this.readMeasured(),this.viewState.elementAt...
method lineBlockAtHeight (line 1) | lineBlockAtHeight(t){return this.readMeasured(),this.viewState.lineBlo...
method viewportLineBlocks (line 1) | get viewportLineBlocks(){return this.viewState.viewportLines}
method lineBlockAt (line 1) | lineBlockAt(t){return this.viewState.lineBlockAt(t)}
method contentHeight (line 1) | get contentHeight(){return this.viewState.contentHeight}
method moveByChar (line 1) | moveByChar(t,e,i){return wn(this,t,vn(this,t,e,i))}
method moveByGroup (line 1) | moveByGroup(t,e){return wn(this,t,vn(this,t,e,(e=>function(t,e,i){let ...
method moveToLineBoundary (line 1) | moveToLineBoundary(t,e,i=!0){return function(t,e,i,n){let s=t.state.do...
method moveVertically (line 1) | moveVertically(t,e,i){return wn(this,t,function(t,e,i,n){let s=e.head,...
method domAtPos (line 1) | domAtPos(t){return this.docView.domAtPos(t)}
method posAtDOM (line 1) | posAtDOM(t,e=0){return this.docView.posFromDOM(t,e)}
method posAtCoords (line 1) | posAtCoords(t,e=!0){return this.readMeasured(),mn(this,t,e)}
method coordsAtPos (line 1) | coordsAtPos(t,e=1){this.readMeasured();let i=this.docView.coordsAt(t,e...
method defaultCharacterWidth (line 1) | get defaultCharacterWidth(){return this.viewState.heightOracle.charWidth}
method defaultLineHeight (line 1) | get defaultLineHeight(){return this.viewState.heightOracle.lineHeight}
method textDirection (line 1) | get textDirection(){return this.viewState.defaultTextDirection}
method textDirectionAt (line 1) | textDirectionAt(t){return!this.state.facet(yi)||t<this.viewport.from||...
method lineWrapping (line 1) | get lineWrapping(){return this.viewState.heightOracle.lineWrapping}
method bidiSpans (line 1) | bidiSpans(t){if(t.length>Ts)return Ki(t.length);let e=this.textDirecti...
method hasFocus (line 1) | get hasFocus(){var t;return(this.dom.ownerDocument.hasFocus()||ze.safa...
method focus (line 1) | focus(){this.observer.ignore((()=>{ve(this.contentDOM),this.docView.up...
method setRoot (line 1) | setRoot(t){this._root!=t&&(this._root=t,this.observer.setWindow((9==t....
method destroy (line 1) | destroy(){for(let t of this.plugins)t.destroy(this);this.plugins=[],th...
method scrollIntoView (line 1) | static scrollIntoView(t,e={}){return ki.of(new xi("number"==typeof t?E...
method domEventHandlers (line 1) | static domEventHandlers(t){return Mi.define((()=>({})),{eventHandlers:...
method theme (line 1) | static theme(t,e){let i=$t.newName(),n=[ps.of(i),Li.of(bs(`.${i}`,t))]...
method baseTheme (line 1) | static baseTheme(t){return K.lowest(Li.of(bs("."+gs,t,ys)))}
method findFromDOM (line 1) | static findFromDOM(t){var e;let i=t.querySelector(".cm-content"),n=i&&...
class Rs (line 1) | class Rs{constructor(t,e,i,n){this.from=t,this.to=e,this.dir=i,this.orde...
method constructor (line 1) | constructor(t,e,i,n){this.from=t,this.to=e,this.dir=i,this.order=n}
method update (line 1) | static update(t,e){if(e.empty)return t;let i=[],n=t.length?t[t.length-...
function Es (line 1) | function Es(t,e,i){for(let n=t.state.facet(e),s=n.length-1;s>=0;s--){let...
function Ls (line 1) | function Ls(t,e,i){return e.altKey&&(t="Alt-"+t),e.ctrlKey&&(t="Ctrl-"+t...
function Ws (line 1) | function Ws(t){let e=t.facet(Is),i=Vs.get(e);return i||Vs.set(e,i=functi...
function Hs (line 1) | function Hs(t,e,i,n){let s=function(t){var e=!(te&&(t.ctrlKey||t.altKey|...
function _s (line 1) | function _s(t={}){return[qs.of(t),Us,Qs,bi.of(!0)]}
class js (line 1) | class js{constructor(t,e,i,n,s){this.left=t,this.top=e,this.width=i,this...
method constructor (line 1) | constructor(t,e,i,n,s){this.left=t,this.top=e,this.width=i,this.height...
method draw (line 1) | draw(){let t=document.createElement("div");return t.className=this.cla...
method adjust (line 1) | adjust(t){t.style.left=this.left+"px",t.style.top=this.top+"px",this.w...
method eq (line 1) | eq(t){return this.left==t.left&&this.top==t.top&&this.width==t.width&&...
method constructor (line 1) | constructor(t){this.view=t,this.rangePieces=[],this.cursors=[],this.meas...
method setBlinkRate (line 1) | setBlinkRate(){this.cursorLayer.style.animationDuration=this.view.state....
method update (line 1) | update(t){let e=t.startState.facet(qs)!=t.state.facet(qs);(e||t.selectio...
method readPos (line 1) | readPos(){let{state:t}=this.view,e=t.facet(qs),i=t.selection.ranges.map(...
method drawSel (line 1) | drawSel({rangePieces:t,cursors:e}){if(t.length!=this.rangePieces.length|...
method destroy (line 1) | destroy(){this.selectionLayer.remove(),this.cursorLayer.remove()}
function Ks (line 1) | function Ks(t){let e=t.scrollDOM.getBoundingClientRect();return{left:(t....
function Gs (line 1) | function Gs(t,e,i){let n=E.cursor(e);return{from:Math.max(i.from,t.moveT...
function Js (line 1) | function Js(t,e){let i=t.lineBlockAt(e);if(Array.isArray(i.type))for(let...
function Xs (line 1) | function Xs(t,e,i){let n=t.coordsAtPos(e.head,e.assoc||1);if(!n)return n...
method constructor (line 1) | constructor(t){this.view=t,this.cursor=null,this.measureReq={read:this.r...
method update (line 1) | update(t){var e;let i=t.state.field(Ys);null==i?null!=this.cursor&&(null...
method readPos (line 1) | readPos(){let t=this.view.state.field(Ys),e=null!=t&&this.view.coordsAtP...
method drawCursor (line 1) | drawCursor(t){this.cursor&&(t?(this.cursor.style.left=t.left+"px",this.c...
method destroy (line 1) | destroy(){this.cursor&&this.cursor.remove()}
method setDropPos (line 1) | setDropPos(t){this.view.state.field(Ys)!=t&&this.view.dispatch({effects:...
method dragover (line 1) | dragover(t){this.setDropPos(this.view.posAtCoords({x:t.clientX,y:t.clien...
method dragleave (line 1) | dragleave(t){t.target!=this.view.contentDOM&&this.view.contentDOM.contai...
method dragend (line 1) | dragend(){this.setDropPos(null)}
method drop (line 1) | drop(){this.setDropPos(null)}
function er (line 1) | function er(t,e,i,n,s){e.lastIndex=0;for(let r,o=t.iterRange(i,n),l=i;!o...
class ir (line 1) | class ir{constructor(t){const{regexp:e,decoration:i,decorate:n,boundary:...
method constructor (line 1) | constructor(t){const{regexp:e,decoration:i,decorate:n,boundary:s,maxLe...
method createDeco (line 1) | createDeco(t){let e=new Pt,i=e.add.bind(e);for(let{from:e,to:n}of func...
method updateDeco (line 1) | updateDeco(t,e){let i=1e9,n=-1;return t.docChanged&&t.changes.iterChan...
method updateRange (line 1) | updateRange(t,e,i,n){for(let s of t.visibleRanges){let r=Math.max(s.fr...
method combine (line 1) | combine(t){let e=Ct(t,{render:null,specialChars:sr,addSpecialChars:null}...
function ar (line 1) | function ar(t={}){return[lr.of(t),hr||(hr=Mi.fromClass(class{constructor...
class cr (line 1) | class cr extends ti{constructor(t,e){super(),this.options=t,this.code=e}...
method constructor (line 1) | constructor(t,e){super(),this.options=t,this.code=e}
method eq (line 1) | eq(t){return t.code==this.code}
method toDOM (line 1) | toDOM(t){let e=function(t){return t>=32?"•":10==t?"":String.fromCharC...
method ignoreEvent (line 1) | ignoreEvent(){return!1}
class ur (line 1) | class ur extends ti{constructor(t){super(),this.width=t}eq(t){return t.w...
method constructor (line 1) | constructor(t){super(),this.width=t}
method eq (line 1) | eq(t){return t.width==this.width}
method toDOM (line 1) | toDOM(){let t=document.createElement("span");return t.textContent="\t"...
method ignoreEvent (line 1) | ignoreEvent(){return!1}
method constructor (line 1) | constructor(t){this.decorations=this.getDeco(t)}
method update (line 1) | update(t){(t.docChanged||t.selectionSet)&&(this.decorations=this.getDeco...
method getDeco (line 1) | getDeco(t){let e=-1,i=[];for(let n of t.state.selection.ranges){let s=t....
function mr (line 1) | function mr(t,e){let i=t.posAtCoords({x:e.clientX,y:e.clientY},!1),n=t.s...
function gr (line 1) | function gr(t,e){let i=mr(t,e),n=t.state.selection;return i?{update(t){i...
function vr (line 1) | function vr(t){let e=(null==t?void 0:t.eventFilter)||(t=>t.altKey&&0==t....
function br (line 1) | function br(t={}){let[e,i]=wr[t.key||"Alt"],n=Mi.fromClass(class{constru...
class kr (line 1) | class kr{constructor(t,e,i){this.facet=e,this.createTooltipView=i,this.i...
method constructor (line 1) | constructor(t,e,i){this.facet=e,this.createTooltipView=i,this.input=t....
method update (line 1) | update(t){var e;let i=t.state.facet(this.facet),n=i.filter((t=>t));if(...
function Sr (line 1) | function Sr(t){let{win:e}=t;return{top:0,left:0,bottom:e.innerHeight,rig...
method constructor (line 1) | constructor(t){this.view=t,this.inView=!0,this.lastTransaction=0,this.me...
method createContainer (line 1) | createContainer(){this.parent?(this.container=document.createElement("di...
method observeIntersection (line 1) | observeIntersection(){if(this.intersectionObserver){this.intersectionObs...
method measureSoon (line 1) | measureSoon(){this.measureTimeout<0&&(this.measureTimeout=setTimeout((()...
method update (line 1) | update(t){t.transactions.length&&(this.lastTransaction=Date.now());let e...
method createTooltip (line 1) | createTooltip(t){let e=t.create(this.view);if(e.dom.classList.add("cm-to...
method destroy (line 1) | destroy(){var t,e;this.view.win.removeEventListener("resize",this.measur...
method readMeasure (line 1) | readMeasure(){let t=this.view.dom.getBoundingClientRect();return{editor:...
method writeMeasure (line 1) | writeMeasure(t){let{editor:e,space:i}=t,n=[];for(let s=0;s<this.manager....
method maybeMeasure (line 1) | maybeMeasure(){if(this.manager.tooltips.length&&(this.view.inView&&this....
method scroll (line 1) | scroll(){this.maybeMeasure()}
class Pr (line 1) | class Pr{constructor(t){this.view=t,this.mounted=!1,this.dom=document.cr...
method constructor (line 1) | constructor(t){this.view=t,this.mounted=!1,this.dom=document.createEle...
method create (line 1) | static create(t){return new Pr(t)}
method createHostedView (line 1) | createHostedView(t){let e=t.create(this.view);return e.dom.classList.a...
method mount (line 1) | mount(t){for(let e of this.manager.tooltipViews)e.mount&&e.mount(t);th...
method positioned (line 1) | positioned(t){for(let e of this.manager.tooltipViews)e.positioned&&e.p...
method update (line 1) | update(t){this.manager.update(t)}
class Er (line 1) | class Er{constructor(t,e,i,n,s){this.view=t,this.source=e,this.field=i,t...
method constructor (line 1) | constructor(t,e,i,n,s){this.view=t,this.source=e,this.field=i,this.set...
method update (line 1) | update(){this.pending&&(this.pending=null,clearTimeout(this.restartTim...
method active (line 1) | get active(){return this.view.state.field(this.field)}
method checkHover (line 1) | checkHover(){if(this.hoverTimeout=-1,this.active)return;let t=Date.now...
method startHover (line 1) | startHover(){clearTimeout(this.restartTimeout);let{lastMove:t}=this,e=...
method mousemove (line 1) | mousemove(t){var e;this.lastMove={x:t.clientX,y:t.clientY,target:t.tar...
method mouseleave (line 1) | mouseleave(t){clearTimeout(this.hoverTimeout),this.hoverTimeout=-1,thi...
method destroy (line 1) | destroy(){clearTimeout(this.hoverTimeout),this.view.dom.removeEventLis...
function Br (line 1) | function Br(t){for(let e=t;e;e=e.parentNode)if(1==e.nodeType&&e.classLis...
function Lr (line 1) | function Lr(t,e={}){let i=ut.define(),n=q.define({create:()=>null,update...
method combine (line 1) | combine(t){let e,i;for(let n of t)e=e||n.topContainer,i=i||n.bottomConta...
function Vr (line 1) | function Vr(t,e){let i=t.plugin(Wr),n=i?i.specs.indexOf(e):-1;return n>-...
method constructor (line 1) | constructor(t){this.input=t.state.facet(Fr),this.specs=this.input.filter...
method update (line 1) | update(t){let e=t.state.facet(Ir);this.top.container!=e.topContainer&&(t...
method destroy (line 1) | destroy(){this.top.sync([]),this.bottom.sync([])}
class zr (line 1) | class zr{constructor(t,e,i){this.view=t,this.top=e,this.container=i,this...
method constructor (line 1) | constructor(t,e,i){this.view=t,this.top=e,this.container=i,this.dom=vo...
method sync (line 1) | sync(t){for(let e of this.panels)e.destroy&&t.indexOf(e)<0&&e.destroy(...
method syncDOM (line 1) | syncDOM(){if(0==this.panels.length)return void(this.dom&&(this.dom.rem...
method scrollMargin (line 1) | scrollMargin(){return!this.dom||this.container?0:Math.max(0,this.top?t...
method syncClasses (line 1) | syncClasses(){if(this.container&&this.classes!=this.view.themeClasses)...
function Hr (line 1) | function Hr(t){let e=t.nextSibling;return t.remove(),e}
class qr (line 1) | class qr extends At{compare(t){return this==t||this.constructor==t.const...
method compare (line 1) | compare(t){return this==t||this.constructor==t.constructor&&this.eq(t)}
method eq (line 1) | eq(t){return!1}
method destroy (line 1) | destroy(t){}
function $r (line 1) | function $r(t){return[Kr(),Ur.of(Object.assign(Object.assign({},jr),t))]}
function Kr (line 1) | function Kr(t){let e=[Gr];return t&&!1===t.fixed&&e.push(Qr.of(!0)),e}
method constructor (line 1) | constructor(t){this.view=t,this.prevViewport=t.viewport,this.dom=documen...
method update (line 1) | update(t){if(this.updateGutters(t)){let e=this.prevViewport,i=t.view.vie...
method syncGutters (line 1) | syncGutters(t){let e=this.dom.nextSibling;t&&this.dom.remove();let i=Tt....
method updateGutters (line 1) | updateGutters(t){let e=t.startState.facet(Ur),i=t.state.facet(Ur),n=t.do...
method destroy (line 1) | destroy(){for(let t of this.gutters)t.destroy();this.dom.remove()}
function Jr (line 1) | function Jr(t){return Array.isArray(t)?t:[t]}
function Xr (line 1) | function Xr(t,e,i){for(;t.value&&t.from<=i;)t.from==i&&e.push(t.value),t...
class Zr (line 1) | class Zr{constructor(t,e,i){this.gutter=t,this.height=i,this.localMarker...
method constructor (line 1) | constructor(t,e,i){this.gutter=t,this.height=i,this.localMarkers=[],th...
method line (line 1) | line(t,e,i){this.localMarkers.length&&(this.localMarkers=[]),Xr(this.c...
method finish (line 1) | finish(){let t=this.gutter;for(;t.elements.length>this.i;){let e=t.ele...
class Yr (line 1) | class Yr{constructor(t,e){this.view=t,this.config=e,this.elements=[],thi...
method constructor (line 1) | constructor(t,e){this.view=t,this.config=e,this.elements=[],this.space...
method update (line 1) | update(t){let e=this.markers;if(this.markers=Jr(this.config.markers(t....
method destroy (line 1) | destroy(){for(let t of this.elements)t.destroy()}
class to (line 1) | class to{constructor(t,e,i,n){this.height=-1,this.above=0,this.markers=[...
method constructor (line 1) | constructor(t,e,i,n){this.height=-1,this.above=0,this.markers=[],this....
method update (line 1) | update(t,e,i,n){this.height!=e&&(this.dom.style.height=(this.height=e)...
method setMarkers (line 1) | setMarkers(t,e){let i="cm-gutterElement",n=this.dom.firstChild;for(let...
method destroy (line 1) | destroy(){this.setMarkers(null,[])}
method domEventHandlers (line 1) | domEventHandlers(t,e){let i=Object.assign({},t);for(let t in e){let n=i[...
class no (line 1) | class no extends qr{constructor(t){super(),this.number=t}eq(t){return th...
method constructor (line 1) | constructor(t){super(),this.number=t}
method eq (line 1) | eq(t){return this.number==t.number}
method toDOM (line 1) | toDOM(){return document.createTextNode(this.number)}
function so (line 1) | function so(t,e){return t.state.facet(io).formatNumber(e,t.state)}
method updateSpacer (line 1) | updateSpacer(t,e){let i=so(e.view,lo(e.view.state.doc.lines));return i==...
function oo (line 1) | function oo(t={}){return[io.of(t),Kr(),ro]}
function lo (line 1) | function lo(t){let e=9;for(;e<t;)e=10*e+9;return e}
method constructor (line 1) | constructor(){super(...arguments),this.elementClass="cm-activeLineGutter"}
class fo (line 1) | class fo{constructor(t,e){this.from=t,this.to=e}}
method constructor (line 1) | constructor(t,e){this.from=t,this.to=e}
class po (line 1) | class po{constructor(t={}){this.id=uo++,this.perNode=!!t.perNode,this.de...
method constructor (line 1) | constructor(t={}){this.id=uo++,this.perNode=!!t.perNode,this.deseriali...
method add (line 1) | add(t){if(this.perNode)throw new RangeError("Can't add per-node props ...
class go (line 1) | class go{constructor(t,e,i,n=0){this.name=t,this.props=e,this.id=i,this....
method constructor (line 1) | constructor(t,e,i,n=0){this.name=t,this.props=e,this.id=i,this.flags=n}
method define (line 1) | static define(t){let e=t.props&&t.props.length?Object.create(null):mo,...
method prop (line 1) | prop(t){return this.props[t.id]}
method isTop (line 1) | get isTop(){return(1&this.flags)>0}
method isSkipped (line 1) | get isSkipped(){return(2&this.flags)>0}
method isError (line 1) | get isError(){return(4&this.flags)>0}
method isAnonymous (line 1) | get isAnonymous(){return(8&this.flags)>0}
method is (line 1) | is(t){if("string"==typeof t){if(this.name==t)return!0;let e=this.prop(...
method match (line 1) | static match(t){let e=Object.create(null);for(let i in t)for(let n of ...
class vo (line 1) | class vo{constructor(t){this.types=t;for(let e=0;e<t.length;e++)if(t[e]....
method constructor (line 1) | constructor(t){this.types=t;for(let e=0;e<t.length;e++)if(t[e].id!=e)t...
method extend (line 1) | extend(...t){let e=[];for(let i of this.types){let n=null;for(let e of...
class xo (line 1) | class xo{constructor(t,e,i,n,s){if(this.type=t,this.children=e,this.posi...
method constructor (line 1) | constructor(t,e,i,n,s){if(this.type=t,this.children=e,this.positions=i...
method toString (line 1) | toString(){let t=this.prop(po.mounted);if(t&&!t.overlay)return t.tree....
method cursor (line 1) | cursor(t=0){return new Eo(this.topNode,t)}
method cursorAt (line 1) | cursorAt(t,e=0,i=0){let n=wo.get(this)||this.topNode,s=new Eo(n);retur...
method topNode (line 1) | get topNode(){return new Mo(this,0,0,null)}
method resolve (line 1) | resolve(t,e=0){let i=Oo(wo.get(this)||this.topNode,t,e,!1);return wo.s...
method resolveInner (line 1) | resolveInner(t,e=0){let i=Oo(yo.get(this)||this.topNode,t,e,!0);return...
method iterate (line 1) | iterate(t){let{enter:e,leave:i,from:n=0,to:s=this.length}=t;for(let r=...
method prop (line 1) | prop(t){return t.perNode?this.props?this.props[t.id]:void 0:this.type....
method propValues (line 1) | get propValues(){let t=[];if(this.props)for(let e in this.props)t.push...
method balance (line 1) | balance(t={}){return this.children.length<=8?this:Io(go.none,this.chil...
method build (line 1) | static build(t){return function(t){var e;let{buffer:i,nodeSet:n,maxBuf...
class ko (line 1) | class ko{constructor(t,e){this.buffer=t,this.index=e}get id(){return thi...
method constructor (line 1) | constructor(t,e){this.buffer=t,this.index=e}
method id (line 1) | get id(){return this.buffer[this.index-4]}
method start (line 1) | get start(){return this.buffer[this.index-3]}
method end (line 1) | get end(){return this.buffer[this.index-2]}
method size (line 1) | get size(){return this.buffer[this.index-1]}
method pos (line 1) | get pos(){return this.index}
method next (line 1) | next(){this.index-=4}
method fork (line 1) | fork(){return new ko(this.buffer,this.index)}
class So (line 1) | class So{constructor(t,e,i){this.buffer=t,this.length=e,this.set=i}get t...
method constructor (line 1) | constructor(t,e,i){this.buffer=t,this.length=e,this.set=i}
method type (line 1) | get type(){return go.none}
method toString (line 1) | toString(){let t=[];for(let e=0;e<this.buffer.length;)t.push(this.chil...
method childString (line 1) | childString(t){let e=this.buffer[t],i=this.buffer[t+3],n=this.set.type...
method findChild (line 1) | findChild(t,e,i,n,s){let{buffer:r}=this,o=-1;for(let l=t;l!=e&&!(Co(s,...
method slice (line 1) | slice(t,e,i,n){let s=this.buffer,r=new Uint16Array(e-t);for(let n=t,o=...
function Co (line 1) | function Co(t,e,i,n){switch(t){case-2:return i<e;case-1:return n>=e&&i<e...
function Ao (line 1) | function Ao(t,e){let i=t.childBefore(e);for(;i;){let e=i.lastChild;if(!e...
function Oo (line 1) | function Oo(t,e,i,n){for(var s;t.from==t.to||(i<1?t.from>=e:t.from>e)||(...
class Mo (line 1) | class Mo{constructor(t,e,i,n){this._tree=t,this.from=e,this.index=i,this...
method constructor (line 1) | constructor(t,e,i,n){this._tree=t,this.from=e,this.index=i,this._paren...
method type (line 1) | get type(){return this._tree.type}
method name (line 1) | get name(){return this._tree.type.name}
method to (line 1) | get to(){return this.from+this._tree.length}
method nextChild (line 1) | nextChild(t,e,i,n,s=0){for(let r=this;;){for(let{children:o,positions:...
method firstChild (line 1) | get firstChild(){return this.nextChild(0,1,0,4)}
method lastChild (line 1) | get lastChild(){return this.nextChild(this._tree.children.length-1,-1,...
method childAfter (line 1) | childAfter(t){return this.nextChild(0,1,t,2)}
method childBefore (line 1) | childBefore(t){return this.nextChild(this._tree.children.length-1,-1,t...
method enter (line 1) | enter(t,e,i=0){let n;if(!(i&bo.IgnoreOverlays)&&(n=this._tree.prop(po....
method nextSignificantParent (line 1) | nextSignificantParent(){let t=this;for(;t.type.isAnonymous&&t._parent;...
method parent (line 1) | get parent(){return this._parent?this._parent.nextSignificantParent():...
method nextSibling (line 1) | get nextSibling(){return this._parent&&this.index>=0?this._parent.next...
method prevSibling (line 1) | get prevSibling(){return this._parent&&this.index>=0?this._parent.next...
method cursor (line 1) | cursor(t=0){return new Eo(this,t)}
method tree (line 1) | get tree(){return this._tree}
method toTree (line 1) | toTree(){return this._tree}
method resolve (line 1) | resolve(t,e=0){return Oo(this,t,e,!1)}
method resolveInner (line 1) | resolveInner(t,e=0){return Oo(this,t,e,!0)}
method enterUnfinishedNodesBefore (line 1) | enterUnfinishedNodesBefore(t){return Ao(this,t)}
method getChild (line 1) | getChild(t,e=null,i=null){let n=Do(this,t,e,i);return n.length?n[0]:null}
method getChildren (line 1) | getChildren(t,e=null,i=null){return Do(this,t,e,i)}
method toString (line 1) | toString(){return this._tree.toString()}
method node (line 1) | get node(){return this}
method matchContext (line 1) | matchContext(t){return To(this,t)}
function Do (line 1) | function Do(t,e,i,n){let s=t.cursor(),r=[];if(!s.firstChild())return r;i...
function To (line 1) | function To(t,e,i=e.length-1){for(let n=t.parent;i>=0;n=n.parent){if(!n)...
class Po (line 1) | class Po{constructor(t,e,i,n){this.parent=t,this.buffer=e,this.index=i,t...
method constructor (line 1) | constructor(t,e,i,n){this.parent=t,this.buffer=e,this.index=i,this.sta...
class Ro (line 1) | class Ro{constructor(t,e,i){this.context=t,this._parent=e,this.index=i,t...
method constructor (line 1) | constructor(t,e,i){this.context=t,this._parent=e,this.index=i,this.typ...
method name (line 1) | get name(){return this.type.name}
method from (line 1) | get from(){return this.context.start+this.context.buffer.buffer[this.i...
method to (line 1) | get to(){return this.context.start+this.context.buffer.buffer[this.ind...
method child (line 1) | child(t,e,i){let{buffer:n}=this.context,s=n.findChild(this.index+4,n.b...
method firstChild (line 1) | get firstChild(){return this.child(1,0,4)}
method lastChild (line 1) | get lastChild(){return this.child(-1,0,4)}
method childAfter (line 1) | childAfter(t){return this.child(1,t,2)}
method childBefore (line 1) | childBefore(t){return this.child(-1,t,-2)}
method enter (line 1) | enter(t,e,i=0){if(i&bo.ExcludeBuffers)return null;let{buffer:n}=this.c...
method parent (line 1) | get parent(){return this._parent||this.context.parent.nextSignificantP...
method externalSibling (line 1) | externalSibling(t){return this._parent?null:this.context.parent.nextCh...
method nextSibling (line 1) | get nextSibling(){let{buffer:t}=this.context,e=t.buffer[this.index+3];...
method prevSibling (line 1) | get prevSibling(){let{buffer:t}=this.context,e=this._parent?this._pare...
method cursor (line 1) | cursor(t=0){return new Eo(this,t)}
method tree (line 1) | get tree(){return null}
method toTree (line 1) | toTree(){let t=[],e=[],{buffer:i}=this.context,n=this.index+4,s=i.buff...
method resolve (line 1) | resolve(t,e=0){return Oo(this,t,e,!1)}
method resolveInner (line 1) | resolveInner(t,e=0){return Oo(this,t,e,!0)}
method enterUnfinishedNodesBefore (line 1) | enterUnfinishedNodesBefore(t){return Ao(this,t)}
method toString (line 1) | toString(){return this.context.buffer.childString(this.index)}
method getChild (line 1) | getChild(t,e=null,i=null){let n=Do(this,t,e,i);return n.length?n[0]:null}
method getChildren (line 1) | getChildren(t,e=null,i=null){return Do(this,t,e,i)}
method node (line 1) | get node(){return this}
method matchContext (line 1) | matchContext(t){return To(this,t)}
class Eo (line 1) | class Eo{constructor(t,e=0){if(this.mode=e,this.buffer=null,this.stack=[...
method constructor (line 1) | constructor(t,e=0){if(this.mode=e,this.buffer=null,this.stack=[],this....
method name (line 1) | get name(){return this.type.name}
method yieldNode (line 1) | yieldNode(t){return!!t&&(this._tree=t,this.type=t.type,this.from=t.fro...
method yieldBuf (line 1) | yieldBuf(t,e){this.index=t;let{start:i,buffer:n}=this.buffer;return th...
method yield (line 1) | yield(t){return!!t&&(t instanceof Mo?(this.buffer=null,this.yieldNode(...
method toString (line 1) | toString(){return this.buffer?this.buffer.buffer.childString(this.inde...
method enterChild (line 1) | enterChild(t,e,i){if(!this.buffer)return this.yield(this._tree.nextChi...
method firstChild (line 1) | firstChild(){return this.enterChild(1,0,4)}
method lastChild (line 1) | lastChild(){return this.enterChild(-1,0,4)}
method childAfter (line 1) | childAfter(t){return this.enterChild(1,t,2)}
method childBefore (line 1) | childBefore(t){return this.enterChild(-1,t,-2)}
method enter (line 1) | enter(t,e,i=this.mode){return this.buffer?!(i&bo.ExcludeBuffers)&&this...
method parent (line 1) | parent(){if(!this.buffer)return this.yieldNode(this.mode&bo.IncludeAno...
method sibling (line 1) | sibling(t){if(!this.buffer)return!!this._tree._parent&&this.yield(this...
method nextSibling (line 1) | nextSibling(){return this.sibling(1)}
method prevSibling (line 1) | prevSibling(){return this.sibling(-1)}
method atLastNode (line 1) | atLastNode(t){let e,i,{buffer:n}=this;if(n){if(t>0){if(this.index<n.bu...
method move (line 1) | move(t,e){if(e&&this.enterChild(t,0,4))return!0;for(;;){if(this.siblin...
method next (line 1) | next(t=!0){return this.move(1,t)}
method prev (line 1) | prev(t=!0){return this.move(-1,t)}
method moveTo (line 1) | moveTo(t,e=0){for(;(this.from==this.to||(e<1?this.from>=t:this.from>t)...
method node (line 1) | get node(){if(!this.buffer)return this._tree;let t=this.bufferNode,e=n...
method tree (line 1) | get tree(){return this.buffer?null:this._tree._tree}
method iterate (line 1) | iterate(t,e){for(let i=0;;){let n=!1;if(this.type.isAnonymous||!1!==t(...
method matchContext (line 1) | matchContext(t){if(!this.buffer)return To(this.node,t);let{buffer:e}=t...
function Bo (line 1) | function Bo(t){return t.children.some((t=>t instanceof So||!t.type.isAno...
function No (line 1) | function No(t,e){if(!t.isAnonymous||e instanceof So||e.type!=t)return 1;...
function Io (line 1) | function Io(t,e,i,n,s,r,o,l,a){let h=0;for(let i=n;i<s;i++)h+=No(t,e[i])...
class Vo (line 1) | class Vo{constructor(t,e,i,n,s=!1,r=!1){this.from=t,this.to=e,this.tree=...
method constructor (line 1) | constructor(t,e,i,n,s=!1,r=!1){this.from=t,this.to=e,this.tree=i,this....
method openStart (line 1) | get openStart(){return(1&this.open)>0}
method openEnd (line 1) | get openEnd(){return(2&this.open)>0}
method addTree (line 1) | static addTree(t,e=[],i=!1){let n=[new Vo(0,t.length,t,0,!1,i)];for(le...
method applyChanges (line 1) | static applyChanges(t,e,i=128){if(!e.length)return t;let n=[],s=1,r=t....
class Wo (line 1) | class Wo{startParse(t,e,i){return"string"==typeof t&&(t=new zo(t)),i=i?i...
method startParse (line 1) | startParse(t,e,i){return"string"==typeof t&&(t=new zo(t)),i=i?i.length...
method parse (line 1) | parse(t,e,i){let n=this.startParse(t,e,i);for(;;){let t=n.advance();if...
class zo (line 1) | class zo{constructor(t){this.string=t}get length(){return this.string.le...
method constructor (line 1) | constructor(t){this.string=t}
method length (line 1) | get length(){return this.string.length}
method chunk (line 1) | chunk(t){return this.string.slice(t)}
method lineChunks (line 1) | get lineChunks(){return!1}
method read (line 1) | read(t,e){return this.string.slice(t,e)}
class Fo (line 1) | class Fo{constructor(t,e,i){this.set=t,this.base=e,this.modified=i,this....
method constructor (line 1) | constructor(t,e,i){this.set=t,this.base=e,this.modified=i,this.id=Ho++}
method define (line 1) | static define(t){if(null==t?void 0:t.base)throw new Error("Can not der...
method defineModifier (line 1) | static defineModifier(){let t=new _o;return e=>e.modified.indexOf(t)>-...
class _o (line 1) | class _o{constructor(){this.instances=[],this.id=qo++}static get(t,e){if...
method constructor (line 1) | constructor(){this.instances=[],this.id=qo++}
method get (line 1) | static get(t,e){if(!e.length)return t;let i=e[0].instances.find((i=>{r...
function jo (line 1) | function jo(t){let e=Object.create(null);for(let i in t){let n=t[i];Arra...
class $o (line 1) | class $o{constructor(t,e,i,n){this.tags=t,this.mode=e,this.context=i,thi...
method constructor (line 1) | constructor(t,e,i,n){this.tags=t,this.mode=e,this.context=i,this.next=n}
method opaque (line 1) | get opaque(){return 0==this.mode}
method inherit (line 1) | get inherit(){return 1==this.mode}
method sort (line 1) | sort(t){return!t||t.depth<this.depth?(this.next=t,this):(t.next=this.s...
method depth (line 1) | get depth(){return this.context?this.context.length:0}
function Qo (line 1) | function Qo(t,e){let i=Object.create(null);for(let e of t)if(Array.isArr...
function Ko (line 1) | function Ko(t,e,i,n=0,s=t.length){let r=new Go(n,Array.isArray(e)?e:[e],...
class Go (line 1) | class Go{constructor(t,e,i){this.at=t,this.highlighters=e,this.span=i,th...
method constructor (line 1) | constructor(t,e,i){this.at=t,this.highlighters=e,this.span=i,this.clas...
method startSpan (line 1) | startSpan(t,e){e!=this.class&&(this.flush(t),t>this.at&&(this.at=t),th...
method flush (line 1) | flush(t){t>this.at&&this.class&&this.span(this.at,t,this.class)}
method highlightRange (line 1) | highlightRange(t,e,i,n,s){let{type:r,from:o,to:l}=t;if(o>=i||l<=e)retu...
class pl (line 1) | class pl{constructor(t,e,i=[],n=""){this.data=t,this.name=n,St.prototype...
method constructor (line 1) | constructor(t,e,i=[],n=""){this.data=t,this.name=n,St.prototype.hasOwn...
method isActiveAt (line 1) | isActiveAt(t,e,i=-1){return ml(t,e,i)==this.data}
method findRegions (line 1) | findRegions(t){let e=t.facet(Ol);if((null==e?void 0:e.data)==this.data...
method allowsNesting (line 1) | get allowsNesting(){return!0}
function ml (line 1) | function ml(t,e,i){let n=t.facet(Ol);if(!n)return null;let s=n.data;if(n...
class gl (line 1) | class gl extends pl{constructor(t,e,i){super(t,e,[],i),this.parser=e}sta...
method constructor (line 1) | constructor(t,e,i){super(t,e,[],i),this.parser=e}
method define (line 1) | static define(t){let e=(i=t.languageData,N.define({combine:i?t=>t.conc...
method configure (line 1) | configure(t,e){return new gl(this.data,this.parser.configure(t),e||thi...
method allowsNesting (line 1) | get allowsNesting(){return this.parser.hasWrappers()}
function vl (line 1) | function vl(t){let e=t.field(pl.state,!1);return e?e.tree:xo.empty}
class wl (line 1) | class wl{constructor(t,e=t.length){this.doc=t,this.length=e,this.cursorP...
method constructor (line 1) | constructor(t,e=t.length){this.doc=t,this.length=e,this.cursorPos=0,th...
method syncTo (line 1) | syncTo(t){return this.string=this.cursor.next(t-this.cursorPos).value,...
method chunk (line 1) | chunk(t){return this.syncTo(t),this.string}
method lineChunks (line 1) | get lineChunks(){return!0}
method read (line 1) | read(t,e){let i=this.cursorPos-this.string.length;return t<i||e>=this....
class bl (line 1) | class bl{constructor(t,e,i=[],n,s,r,o,l){this.parser=t,this.state=e,this...
method constructor (line 1) | constructor(t,e,i=[],n,s,r,o,l){this.parser=t,this.state=e,this.fragme...
method create (line 1) | static create(t,e,i){return new bl(t,e,[],xo.empty,0,i,[],null)}
method startParse (line 1) | startParse(){return this.parser.startParse(new wl(this.state.doc),this...
method work (line 1) | work(t,e){return null!=e&&e>=this.state.doc.length&&(e=void 0),this.tr...
method takeTree (line 1) | takeTree(){let t,e;this.parse&&(t=this.parse.parsedPos)>=this.treeLen&...
method withContext (line 1) | withContext(t){let e=yl;yl=this;try{return t()}finally{yl=e}}
method withoutTempSkipped (line 1) | withoutTempSkipped(t){for(let e;e=this.tempSkipped.pop();)t=xl(t,e.fro...
method changes (line 1) | changes(t,e){let{fragments:i,tree:n,treeLen:s,viewport:r,skipped:o}=th...
method updateViewport (line 1) | updateViewport(t){if(this.viewport.from==t.from&&this.viewport.to==t.t...
method reset (line 1) | reset(){this.parse&&(this.takeTree(),this.parse=null)}
method skipUntilInView (line 1) | skipUntilInView(t,e){this.skipped.push({from:t,to:e})}
method getSkippingParser (line 1) | static getSkippingParser(t){return new class extends Wo{createParse(e,...
method isDone (line 1) | isDone(t){t=Math.min(t,this.state.doc.length);let e=this.fragments;ret...
method get (line 1) | static get(){return yl}
function xl (line 1) | function xl(t,e,i){return Vo.applyChanges(t,[{fromA:e,toA:i,fromB:e,toB:...
class kl (line 1) | class kl{constructor(t){this.context=t,this.tree=t.tree}apply(t){if(!t.d...
method constructor (line 1) | constructor(t){this.context=t,this.tree=t.tree}
method apply (line 1) | apply(t){if(!t.docChanged&&this.tree==this.context.tree)return this;le...
method init (line 1) | static init(t){let e=Math.min(3e3,t.doc.length),i=bl.create(t.facet(Ol...
method update (line 1) | update(t,e){for(let t of e.effects)if(t.is(pl.setState))return t.value;r...
method constructor (line 1) | constructor(t){this.view=t,this.working=null,this.workScheduled=0,this.c...
method update (line 1) | update(t){let e=this.view.state.field(pl.state).context;(e.updateViewpor...
method scheduleWork (line 1) | scheduleWork(){if(this.working)return;let{state:t}=this.view,e=t.field(p...
method work (line 1) | work(t){this.working=null;let e=Date.now();if(this.chunkEnd<e&&(this.chu...
method checkAsyncSchedule (line 1) | checkAsyncSchedule(t){t.scheduleOn&&(this.workScheduled++,t.scheduleOn.t...
method destroy (line 1) | destroy(){this.working&&this.working()}
method isWorking (line 1) | isWorking(){return!!(this.working||this.workScheduled>0)}
method focus (line 1) | focus(){this.scheduleWork()}
class Ml (line 1) | class Ml{constructor(t,e=[]){this.language=t,this.support=e,this.extensi...
method constructor (line 1) | constructor(t,e=[]){this.language=t,this.support=e,this.extension=[t,e]}
function Pl (line 1) | function Pl(t){let e=t.facet(Tl);return 9==e.charCodeAt(0)?t.tabSize*e.l...
function Rl (line 1) | function Rl(t,e){let i="",n=t.tabSize;if(9==t.facet(Tl).charCodeAt(0))fo...
function El (line 1) | function El(t,e){t instanceof St&&(t=new Bl(t));for(let i of t.state.fac...
class Bl (line 1) | class Bl{constructor(t,e={}){this.state=t,this.options=e,this.unit=Pl(t)...
method constructor (line 1) | constructor(t,e={}){this.state=t,this.options=e,this.unit=Pl(t)}
method lineAt (line 1) | lineAt(t,e=1){let i=this.state.doc.lineAt(t),{simulateBreak:n,simulate...
method textAfterPos (line 1) | textAfterPos(t,e=1){if(this.options.simulateDoubleBreak&&t==this.optio...
method column (line 1) | column(t,e=1){let{text:i,from:n}=this.lineAt(t,e),s=this.countColumn(i...
method countColumn (line 1) | countColumn(t,e=t.length){return Ft(t,this.state.tabSize,e)}
method lineIndent (line 1) | lineIndent(t,e=1){let{text:i,from:n}=this.lineAt(t,e),s=this.options.o...
method simulatedBreak (line 1) | get simulatedBreak(){return this.options.simulateBreak||null}
function Nl (line 1) | function Nl(t){let e=t.type.prop(Ll);if(e)return e;let i,n=t.firstChild;...
function Il (line 1) | function Il(t,e,i){for(;t;t=t.parent){let n=Nl(t);if(n)return n(Wl.creat...
function Vl (line 1) | function Vl(){return 0}
class Wl (line 1) | class Wl extends Bl{constructor(t,e,i){super(t.state,t.options),this.bas...
method constructor (line 1) | constructor(t,e,i){super(t.state,t.options),this.base=t,this.pos=e,thi...
method create (line 1) | static create(t,e,i){return new Wl(t,e,i)}
method textAfter (line 1) | get textAfter(){return this.textAfterPos(this.pos)}
method baseIndent (line 1) | get baseIndent(){let t=this.state.doc.lineAt(this.node.from);for(;;){l...
method continue (line 1) | continue(){let t=this.node.parent;return t?Il(t,this.pos,this.base):0}
function zl (line 1) | function zl(t,e){for(let i=e;i;i=i.parent)if(t==i)return!0;return!1}
function Hl (line 1) | function Hl({except:t,units:e=1}={}){return i=>{let n=t&&t.test(i.textAf...
function _l (line 1) | function _l(t){let e=t.lastChild;return e&&e.to==t.to&&e.type.isError}
function jl (line 1) | function jl(t,e,i){for(let n of t.facet(Fl)){let s=n(t,e,i);if(s)return ...
function Ul (line 1) | function Ul(t,e){let i=e.mapPos(t.from,1),n=e.mapPos(t.to,-1);return i>=...
function Kl (line 1) | function Kl(t){let e=[];for(let{head:i}of t.state.selection.ranges)e.som...
method update (line 1) | update(t,e){t=t.map(e.changes);for(let i of e.effects)i.is($l)&&!Xl(t,i....
method toJSON (line 1) | toJSON(t,e){let i=[];return t.between(0,e.doc.length,((t,e)=>{i.push(t,e...
method fromJSON (line 1) | fromJSON(t){if(!Array.isArray(t)||t.length%2)throw new RangeError("Inval...
function Jl (line 1) | function Jl(t,e,i){var n;let s=null;return null===(n=t.field(Gl,!1))||vo...
function Xl (line 1) | function Xl(t,e,i){let n=!1;return t.between(e,e,((t,s)=>{t==e&&s==i&&(n...
function Zl (line 1) | function Zl(t,e){return t.field(Gl,!1)?e:e.concat(ut.appendConfig.of(na(...
function Yl (line 1) | function Yl(t,e,i=!0){let n=t.state.doc.lineAt(e.from).number,s=t.state....
function na (line 1) | function na(t){let e=[Gl,aa];return t&&e.push(ia.of(t)),e}
method toDOM (line 1) | toDOM(t){let{state:e}=t,i=e.facet(ia),n=e=>{let i=t.lineBlockAt(t.posAtD...
class oa (line 1) | class oa extends qr{constructor(t,e){super(),this.config=t,this.open=e}e...
method constructor (line 1) | constructor(t,e){super(),this.config=t,this.open=e}
method eq (line 1) | eq(t){return this.config==t.config&&this.open==t.open}
method toDOM (line 1) | toDOM(t){if(this.config.markerDOM)return this.config.markerDOM(this.op...
function la (line 1) | function la(t={}){let e=Object.assign(Object.assign({},ra),t),i=new oa(e...
class ha (line 1) | class ha{constructor(t,e){let i;function n(t){let e=$t.newName();return(...
method constructor (line 1) | constructor(t,e){let i;function n(t){let e=$t.newName();return(i||(i=O...
method define (line 1) | static define(t,e){return new ha(t,e||{})}
function fa (line 1) | function fa(t){let e=t.facet(ca);return e.length?e:t.facet(ua)}
function da (line 1) | function da(t,e){let i,n=[ma];return t instanceof ha&&(t.module&&n.push(...
class pa (line 1) | class pa{constructor(t){this.markCache=Object.create(null),this.tree=vl(...
method constructor (line 1) | constructor(t){this.markCache=Object.create(null),this.tree=vl(t.state...
method update (line 1) | update(t){let e=vl(t.state),i=fa(t.state),n=i!=fa(t.startState);e.leng...
method buildDeco (line 1) | buildDeco(t,e){if(!e||!this.tree.length)return ii.none;let i=new Pt;fo...
function ka (line 1) | function ka(t){let e=[],i=t.matched?ba:xa;return e.push(i.range(t.start....
method update (line 1) | update(t,e){if(!e.docChanged&&!e.selection)return t;let i=[],n=e.state.f...
function Aa (line 1) | function Aa(t={}){return[ya.of(t),Ca]}
function Oa (line 1) | function Oa(t,e,i){let n=t.prop(e<0?po.openedBy:po.closedBy);if(n)return...
function Ma (line 1) | function Ma(t,e,i,n={}){let s=n.maxScanDistance||1e4,r=n.brackets||wa,o=...
function Da (line 1) | function Da(t,e,i,n,s,r){let o=n.parent,l={from:n.from,to:n.to},a=0,h=nu...
function Ba (line 1) | function Ba(t,e){Ra.indexOf(t)>-1||(Ra.push(t),console.warn(e))}
function La (line 1) | function La(t,e){let i=null;for(let n of e.split(".")){let e=t[n]||ul[n]...
function Na (line 1) | function Na(t,e){return({state:i,dispatch:n})=>{if(i.readOnly)return!1;l...
function za (line 1) | function za(t,e=t.selection.main.head){let i=t.languageDataAt("commentTo...
function Ha (line 1) | function Ha(t,e,i=e.selection.ranges){let n=i.map((t=>za(e,t.from).block...
function Fa (line 1) | function Fa(t,e,i=e.selection.ranges){let n=[],s=-1;for(let{from:t,to:r}...
method update (line 1) | update(t,e){let i=e.state.facet(Ua),n=e.annotation(qa);if(n){let s=e.doc...
function Qa (line 1) | function Qa(t={}){return[$a,Ua.of(t),Ds.domEventHandlers({beforeinput(t,...
function Ka (line 1) | function Ka(t,e){return function({state:i,dispatch:n}){if(!e&&i.readOnly...
class Ya (line 1) | class Ya{constructor(t,e,i,n,s){this.changes=t,this.effects=e,this.mappe...
method constructor (line 1) | constructor(t,e,i,n,s){this.changes=t,this.effects=e,this.mapped=i,thi...
method setSelAfter (line 1) | setSelAfter(t){return new Ya(this.changes,this.effects,this.mapped,thi...
method toJSON (line 1) | toJSON(){var t,e,i;return{changes:null===(t=this.changes)||void 0===t?...
method fromJSON (line 1) | static fromJSON(t){return new Ya(t.changes&&C.fromJSON(t.changes),[],t...
method fromTransaction (line 1) | static fromTransaction(t,e){let i=ih;for(let e of t.startState.facet(j...
method selection (line 1) | static selection(t){return new Ya(void 0,ih,void 0,void 0,t)}
function th (line 1) | function th(t,e,i,n){let s=e+1>i+20?e-i-1:0,r=t.slice(s,e);return r.push...
function eh (line 1) | function eh(t,e){return t.length?e.length?t.concat(e):t:e}
function nh (line 1) | function nh(t,e){if(t.length){let i=t[t.length-1],n=i.selectionsAfter.sl...
function sh (line 1) | function sh(t){let e=t[t.length-1],i=t.slice();return i[t.length-1]=e.se...
function rh (line 1) | function rh(t,e){if(!t.length)return t;let i=t.length,n=ih;for(;i;){let ...
function oh (line 1) | function oh(t,e,i){let n=eh(t.selectionsAfter.length?t.selectionsAfter.m...
class ah (line 1) | class ah{constructor(t,e,i=0,n){this.done=t,this.undone=e,this.prevTime=...
method constructor (line 1) | constructor(t,e,i=0,n){this.done=t,this.undone=e,this.prevTime=i,this....
method isolate (line 1) | isolate(){return this.prevTime?new ah(this.done,this.undone):this}
method addChanges (line 1) | addChanges(t,e,i,n,s){let r=this.done,o=r[r.length-1];return r=o&&o.ch...
method addSelection (line 1) | addSelection(t,e,i,n){let s=this.done.length?this.done[this.done.lengt...
method addMapping (line 1) | addMapping(t){return new ah(rh(this.done,t),rh(this.undone,t),this.pre...
method pop (line 1) | pop(t,e,i){let n=0==t?this.done:this.undone;if(0==n.length)return null...
function ch (line 1) | function ch(t,e){return E.create(t.ranges.map(e),t.mainIndex)}
function uh (line 1) | function uh(t,e){return t.update({selection:e,scrollIntoView:!0,userEven...
function fh (line 1) | function fh({state:t,dispatch:e},i){let n=ch(t.selection,i);return!n.eq(...
function dh (line 1) | function dh(t,e){return E.cursor(e?t.to:t.from)}
function ph (line 1) | function ph(t,e){return fh(t,(i=>i.empty?t.moveByChar(i,e):dh(i,e)))}
function mh (line 1) | function mh(t){return t.textDirectionAt(t.state.selection.main.head)==Vi...
function wh (line 1) | function wh(t,e){return fh(t,(i=>i.empty?t.moveByGroup(i,e):dh(i,e)))}
function yh (line 1) | function yh(t,e,i){if(e.type.prop(i))return!0;let n=e.to-e.from;return n...
function bh (line 1) | function bh(t,e,i){let n,s,r=vl(t).resolveInner(e.head),o=i?po.closedBy:...
function xh (line 1) | function xh(t,e){return fh(t,(i=>{if(!i.empty)return dh(i,e);let n=t.mov...
function Ch (line 1) | function Ch(t){return Math.max(t.defaultLineHeight,Math.min(t.dom.client...
function Ah (line 1) | function Ah(t,e){let{state:i}=t,n=ch(i.selection,(i=>i.empty?t.moveVerti...
function Dh (line 1) | function Dh(t,e,i){let n=t.lineBlockAt(e.head),s=t.moveToLineBoundary(e,...
function Th (line 1) | function Th(t,e){let i=ch(t.state.selection,(t=>{let i=e(t);return E.ran...
function Ph (line 1) | function Ph(t,e){return Th(t,(i=>t.moveByChar(i,e)))}
function Bh (line 1) | function Bh(t,e){return Th(t,(i=>t.moveByGroup(i,e)))}
function Lh (line 1) | function Lh(t,e){return Th(t,(i=>t.moveVertically(i,e)))}
function Vh (line 1) | function Vh(t,e){return Th(t,(i=>t.moveVertically(i,e,Ch(t))))}
function jh (line 1) | function jh(t,e){if(t.state.readOnly)return!1;let i="delete.selection",{...
function Uh (line 1) | function Uh(t,e,i){if(t instanceof Ds)for(let n of t.state.facet(Ds.atom...
function Zh (line 1) | function Zh(t){let e=[],i=-1;for(let n of t.selection.ranges){let s=t.do...
function Yh (line 1) | function Yh(t,e,i){if(t.readOnly)return!1;let n=[],s=[];for(let e of Zh(...
function tc (line 1) | function tc(t,e,i){if(t.readOnly)return!1;let n=[];for(let e of Zh(t))i?...
function ic (line 1) | function ic(t){return({state:i,dispatch:n})=>{if(i.readOnly)return!1;let...
function nc (line 1) | function nc(t,e){let i=-1;return t.changeByRange((n=>{let s=[];for(let r...
function rc (line 1) | function rc(){var t=arguments[0];"string"==typeof t&&(t=document.createE...
function oc (line 1) | function oc(t,e){if("string"==typeof e)t.appendChild(document.createText...
class ac (line 1) | class ac{constructor(t,e,i=0,n=t.length,s,r){this.test=r,this.value={fro...
method constructor (line 1) | constructor(t,e,i=0,n=t.length,s,r){this.test=r,this.value={from:0,to:...
method peek (line 1) | peek(){if(this.bufferPos==this.buffer.length){if(this.bufferStart+=thi...
method next (line 1) | next(){for(;this.matches.length;)this.matches.pop();return this.nextOv...
method nextOverlapping (line 1) | nextOverlapping(){for(;;){let t=this.peek();if(t<0)return this.done=!0...
method match (line 1) | match(t,e){let i=null;for(let n=0;n<this.matches.length;n+=2){let s=th...
class uc (line 1) | class uc{constructor(t,e,i,n=0,s=t.length){if(this.text=t,this.to=s,this...
method constructor (line 1) | constructor(t,e,i,n=0,s=t.length){if(this.text=t,this.to=s,this.curLin...
method getLine (line 1) | getLine(t){this.iter.next(t),this.iter.lineBreak?this.curLine="":(this...
method nextLine (line 1) | nextLine(){this.curLineStart=this.curLineStart+this.curLine.length+1,t...
method next (line 1) | next(){for(let t=this.matchPos-this.curLineStart;;){this.re.lastIndex=...
class dc (line 1) | class dc{constructor(t,e){this.from=t,this.text=e}get to(){return this.f...
method constructor (line 1) | constructor(t,e){this.from=t,this.text=e}
method to (line 1) | get to(){return this.from+this.text.length}
method get (line 1) | static get(t,e,i){let n=fc.get(t);if(!n||n.from>=i||n.to<=e){let n=new...
class pc (line 1) | class pc{constructor(t,e,i,n,s){this.text=t,this.to=s,this.done=!1,this....
method constructor (line 1) | constructor(t,e,i,n,s){this.text=t,this.to=s,this.done=!1,this.value=h...
method chunkEnd (line 1) | chunkEnd(t){return t>=this.to?this.to:this.text.lineAt(t).to}
method next (line 1) | next(){for(;;){let t=this.re.lastIndex=this.matchPos-this.flat.from,e=...
function mc (line 1) | function mc(t,e){if(e>=t.length)return e;let i,n=t.lineAt(e);for(;e<n.to...
function gc (line 1) | function gc(t){let e=rc("input",{class:"cm-textfield",name:"line"});func...
method update (line 1) | update(t,e){for(let i of e.effects)i.is(vc)&&(t=i.value);return t}
function kc (line 1) | function kc(t){let e=[Mc,Oc];return t&&e.push(xc.of(t)),e}
function Ac (line 1) | function Ac(t,e,i,n){return!(0!=i&&t(e.sliceDoc(i-1,i))==yt.Word||n!=e.d...
method constructor (line 1) | constructor(t){this.decorations=this.getDeco(t)}
method update (line 1) | update(t){(t.selectionSet||t.docChanged||t.viewportChanged)&&(this.decor...
method getDeco (line 1) | getDeco(t){let e=t.state.facet(xc),{state:i}=t,n=i.selection;if(n.ranges...
class Tc (line 1) | class Tc{constructor(t){this.search=t.search,this.caseSensitive=!!t.case...
method constructor (line 1) | constructor(t){this.search=t.search,this.caseSensitive=!!t.caseSensiti...
method unquote (line 1) | unquote(t){return this.literal?t:t.replace(/\\([nrt\\])/g,((t,e)=>"n"=...
method eq (line 1) | eq(t){return this.search==t.search&&this.replace==t.replace&&this.case...
method create (line 1) | create(){return this.regexp?new Ic(this):new Ec(this)}
method getCursor (line 1) | getCursor(t,e=0,i){let n=t.doc?t:St.create({doc:t});return null==i&&(i...
class Pc (line 1) | class Pc{constructor(t){this.spec=t}}
method constructor (line 1) | constructor(t){this.spec=t}
function Rc (line 1) | function Rc(t,e,i,n){return new ac(e.doc,t.unquoted,i,n,t.caseSensitive?...
class Ec (line 1) | class Ec extends Pc{constructor(t){super(t)}nextMatch(t,e,i){let n=Rc(th...
method constructor (line 1) | constructor(t){super(t)}
method nextMatch (line 1) | nextMatch(t,e,i){let n=Rc(this.spec,t,i,t.doc.length).nextOverlapping(...
method prevMatchInRange (line 1) | prevMatchInRange(t,e,i){for(let n=i;;){let i=Math.max(e,n-1e4-this.spe...
method prevMatch (line 1) | prevMatch(t,e,i){return this.prevMatchInRange(t,0,e)||this.prevMatchIn...
method getReplacement (line 1) | getReplacement(t){return this.spec.unquote(this.spec.replace)}
method matchAll (line 1) | matchAll(t,e){let i=Rc(this.spec,t,0,t.doc.length),n=[];for(;!i.next()...
method highlight (line 1) | highlight(t,e,i,n){let s=Rc(this.spec,t,Math.max(0,e-this.spec.unquote...
function Bc (line 1) | function Bc(t,e,i,n){return new uc(e.doc,t.search,{ignoreCase:!t.caseSen...
function Lc (line 1) | function Lc(t,e){return t.slice(d(t,e,!1),e)}
function Nc (line 1) | function Nc(t,e){return t.slice(e,d(t,e))}
class Ic (line 1) | class Ic extends Pc{nextMatch(t,e,i){let n=Bc(this.spec,t,i,t.doc.length...
method nextMatch (line 1) | nextMatch(t,e,i){let n=Bc(this.spec,t,i,t.doc.length).next();return n....
method prevMatchInRange (line 1) | prevMatchInRange(t,e,i){for(let n=1;;n++){let s=Math.max(e,i-1e4*n),r=...
method prevMatch (line 1) | prevMatch(t,e,i){return this.prevMatchInRange(t,0,e)||this.prevMatchIn...
method getReplacement (line 1) | getReplacement(t){return this.spec.unquote(this.spec.replace.replace(/...
method matchAll (line 1) | matchAll(t,e){let i=Bc(this.spec,t,0,t.doc.length),n=[];for(;!i.next()...
method highlight (line 1) | highlight(t,e,i,n){let s=Bc(this.spec,t,Math.max(0,e-250),Math.min(i+2...
method update (line 1) | update(t,e){for(let i of e.effects)i.is(Vc)?t=new Hc(i.value.create(),t....
class Hc (line 1) | class Hc{constructor(t,e){this.query=t,this.panel=e}}
method constructor (line 1) | constructor(t,e){this.query=t,this.panel=e}
method constructor (line 1) | constructor(t){this.view=t,this.decorations=this.highlight(t.state.field...
method update (line 1) | update(t){let e=t.state.field(zc);(e!=t.startState.field(zc)||t.docChang...
method highlight (line 1) | highlight({query:t,panel:e}){if(!e||!t.spec.valid)return ii.none;let{vie...
function jc (line 1) | function jc(t){return e=>{let i=e.state.field(zc,!1);return i&&i.query.s...
function Jc (line 1) | function Jc(t){return t.state.facet(Dc).createPanel(t)}
function Xc (line 1) | function Xc(t,e){var i,n,s,r;let o=t.selection.main,l=o.empty||o.to>o.fr...
class eu (line 1) | class eu{constructor(t){this.view=t;let e=this.query=t.state.field(zc).q...
method constructor (line 1) | constructor(t){this.view=t;let e=this.query=t.state.field(zc).query.sp...
method commit (line 1) | commit(){let t=new Tc({search:this.searchField.value,caseSensitive:thi...
method keydown (line 1) | keydown(t){var e,i,n;e=this.view,i=t,n="search-panel",Hs(Ws(e.state),i...
method update (line 1) | update(t){for(let e of t.transactions)for(let t of e.effects)t.is(Vc)&...
method setQuery (line 1) | setQuery(t){this.query=t,this.searchField.value=t.search,this.replaceF...
method mount (line 1) | mount(){this.searchField.select()}
method pos (line 1) | get pos(){return 80}
method top (line 1) | get top(){return this.view.state.facet(Dc).top}
function iu (line 1) | function iu(t,e){return t.state.phrase(e)}
function su (line 1) | function su(t,{from:e,to:i}){let n=t.state.doc.lineAt(e),s=t.state.doc.l...
class lu (line 1) | class lu{constructor(t,e,i){this.state=t,this.pos=e,this.explicit=i,this...
method constructor (line 1) | constructor(t,e,i){this.state=t,this.pos=e,this.explicit=i,this.abortL...
method tokenBefore (line 1) | tokenBefore(t){let e=vl(this.state).resolveInner(this.pos,-1);for(;e&&...
method matchBefore (line 1) | matchBefore(t){let e=this.state.doc.lineAt(this.pos),i=Math.max(e.from...
method aborted (line 1) | get aborted(){return null==this.abortListeners}
method addEventListener (line 1) | addEventListener(t,e){"abort"==t&&this.abortListeners&&this.abortListe...
function au (line 1) | function au(t){let e=Object.keys(t).join(""),i=/\w/.test(e);return i&&(e...
function hu (line 1) | function hu(t){let e=t.map((t=>"string"==typeof t?{label:t}:t)),[i,n]=e....
class cu (line 1) | class cu{constructor(t,e,i){this.completion=t,this.source=e,this.match=i}}
method constructor (line 1) | constructor(t,e,i){this.completion=t,this.source=e,this.match=i}
function uu (line 1) | function uu(t){return t.selection.main.head}
function fu (line 1) | function fu(t,e){var i;let{source:n}=t,s=e&&"^"!=n[0],r="$"!=n[n.length-...
function pu (line 1) | function pu(t,e){const i=e.completion.apply||e.completion.label;let n=e....
function gu (line 1) | function gu(t){if(!Array.isArray(t))return t;let e=mu.get(t);return e||m...
class vu (line 1) | class vu{constructor(t){this.pattern=t,this.chars=[],this.folded=[],this...
method constructor (line 1) | constructor(t){this.pattern=t,this.chars=[],this.folded=[],this.any=[]...
method match (line 1) | match(t){if(0==this.pattern.length)return[0];if(t.length<this.pattern....
method result (line 1) | result(t,e,i){let n=[t-i.length],s=1;for(let t of e){let e=t+(this.ast...
function yu (line 1) | function yu(t,e,i){if(t<=i)return{from:0,to:t};if(e<0&&(e=0),e<=t>>1){le...
class bu (line 1) | class bu{constructor(t,e){this.view=t,this.stateField=e,this.info=null,t...
method constructor (line 1) | constructor(t,e){this.view=t,this.stateField=e,this.info=null,this.pla...
method mount (line 1) | mount(){this.updateSel()}
method update (line 1) | update(t){var e,i,n;let s=t.state.field(this.stateField),r=t.startStat...
method positioned (line 1) | positioned(t){this.space=t,this.info&&this.view.requestMeasure(this.pl...
method updateSel (line 1) | updateSel(){let t=this.view.state.field(this.stateField),e=t.open;if((...
method addInfoPane (line 1) | addInfoPane(t){let e=this.info=document.createElement("div");e.classNa...
method updateSelectedOption (line 1) | updateSelectedOption(t){let e=null;for(let i=this.list.firstChild,n=th...
method measureInfo (line 1) | measureInfo(){let t=this.dom.querySelector("[aria-selected]");if(!t||!...
method positionInfo (line 1) | positionInfo(t){this.info&&(t?(this.info.style.top=t.top,this.info.sty...
method createListBox (line 1) | createListBox(t,e,i){const n=document.createElement("ul");n.id=e,n.set...
function xu (line 1) | function xu(t){return 100*(t.boost||0)+(t.apply?10:0)+(t.info?5:0)+(t.ty...
class ku (line 1) | class ku{constructor(t,e,i,n,s,r){this.options=t,this.attrs=e,this.toolt...
method constructor (line 1) | constructor(t,e,i,n,s,r){this.options=t,this.attrs=e,this.tooltip=i,th...
method setSelected (line 1) | setSelected(t,e){return t==this.selected||t>=this.options.length?this:...
method build (line 1) | static build(t,e,i,n,s){let r=function(t,e){let i=[],n=0;for(let s of ...
method map (line 1) | map(t){return new ku(this.options,this.attrs,Object.assign(Object.assi...
class Su (line 1) | class Su{constructor(t,e,i){this.active=t,this.id=e,this.open=i}static s...
method constructor (line 1) | constructor(t,e,i){this.active=t,this.id=e,this.open=i}
method start (line 1) | static start(){return new Su(Ou,"cm-ac-"+Math.floor(2e6*Math.random())...
method update (line 1) | update(t){let{state:e}=t,i=e.facet(wu),n=(i.override||e.languageDataAt...
method tooltip (line 1) | get tooltip(){return this.open?this.open.tooltip:null}
method attrs (line 1) | get attrs(){return this.open?this.open.attrs:Cu}
function Au (line 1) | function Au(t,e){let i={"aria-autocomplete":"list","aria-haspopup":"list...
function Mu (line 1) | function Mu(t){return t.isUserEvent("input.type")?"input":t.isUserEvent(...
class Du (line 1) | class Du{constructor(t,e,i=-1){this.source=t,this.state=e,this.explicitP...
method constructor (line 1) | constructor(t,e,i=-1){this.source=t,this.state=e,this.explicitPos=i}
method hasResult (line 1) | hasResult(){return!1}
method update (line 1) | update(t,e){let i=Mu(t),n=this;i?n=n.handleUserEvent(t,i,e):t.docChang...
method handleUserEvent (line 1) | handleUserEvent(t,e,i){return"delete"!=e&&i.activateOnTyping?new Du(th...
method handleChange (line 1) | handleChange(t){return t.changes.touchesRange(uu(t.startState))?new Du...
method map (line 1) | map(t){return t.empty||this.explicitPos<0?this:new Du(this.source,this...
class Tu (line 1) | class Tu extends Du{constructor(t,e,i,n,s){super(t,2,e),this.result=i,th...
method constructor (line 1) | constructor(t,e,i,n,s){super(t,2,e),this.result=i,this.from=n,this.to=s}
method hasResult (line 1) | hasResult(){return!0}
method handleUserEvent (line 1) | handleUserEvent(t,e,i){var n;let s=t.changes.mapPos(this.from),r=t.cha...
method handleChange (line 1) | handleChange(t){return t.changes.touchesRange(this.from,this.to)?new D...
method map (line 1) | map(t){return t.empty?this:new Tu(this.source,this.explicitPos<0?-1:t....
function Nu (line 1) | function Nu(t,e="option"){return i=>{let n=i.state.field(Lu,!1);if(!n||!...
class Iu (line 1) | class Iu{constructor(t,e){this.active=t,this.context=e,this.time=Date.no...
method constructor (line 1) | constructor(t,e){this.active=t,this.context=e,this.time=Date.now(),thi...
method constructor (line 1) | constructor(t){this.view=t,this.debounceUpdate=-1,this.running=[],this.d...
method update (line 1) | update(t){let e=t.state.field(Lu);if(!t.selectionSet&&!t.docChanged&&t.s...
method startUpdate (line 1) | startUpdate(){this.debounceUpdate=-1;let{state:t}=this.view,e=t.field(Lu...
method startQuery (line 1) | startQuery(t){let{state:e}=this.view,i=uu(e),n=new lu(e,i,t.explicitPos=...
method scheduleAccept (line 1) | scheduleAccept(){this.running.every((t=>void 0!==t.done))?this.accept():...
method accept (line 1) | accept(){var t;this.debounceAccept>-1&&clearTimeout(this.debounceAccept)...
method blur (line 1) | blur(){let t=this.view.state.field(Lu,!1);t&&t.tooltip&&this.view.state....
method compositionstart (line 1) | compositionstart(){this.composing=1}
method compositionend (line 1) | compositionend(){3==this.composing&&setTimeout((()=>this.view.dispatch({...
method map (line 1) | map(t,e){let i=e.mapPos(t,-1,k.TrackAfter);return null==i?void 0:i}
method update (line 1) | update(t,e){if(e.selection){let i=e.state.doc.lineAt(e.selection.main.he...
function Uu (line 1) | function Uu(t){for(let e=0;e<ju.length;e+=2)if(ju.charCodeAt(e)==t)retur...
function $u (line 1) | function $u(t,e){return t.languageDataAt("closeBrackets",e)[0]||zu}
function Ju (line 1) | function Ju(t,e){let i=!1;return t.field(_u).between(0,t.doc.length,(t=>...
function Xu (line 1) | function Xu(t,e){let i=t.sliceString(e,e+2);return i.slice(0,b(w(i,0)))}
function Zu (line 1) | function Zu(t,e,i,n){let s=null,r=t.changeByRange((r=>{if(!r.empty)retur...
function Yu (line 1) | function Yu(t,e,i){let n=null,s=t.selection.ranges.map((e=>e.empty&&Xu(t...
function tf (line 1) | function tf(t,e,i,n){let s=n.stringPrefixes||zu.stringPrefixes,r=null,o=...
function ef (line 1) | function ef(t,e){let i=vl(t).resolveInner(e+1);return i.parent&&i.from==e}
function nf (line 1) | function nf(t,e,i){let n=t.charCategorizer(e);if(n(t.sliceDoc(e-1,e))!=y...
function sf (line 1) | function sf(t={}){return[Lu,wu.of(t),Vu,of,Wu]}
class lf (line 1) | class lf{constructor(t,e,i){this.from=t,this.to=e,this.diagnostic=i}}
method constructor (line 1) | constructor(t,e,i){this.from=t,this.to=e,this.diagnostic=i}
class af (line 1) | class af{constructor(t,e,i){this.diagnostics=t,this.panel=e,this.selecte...
method constructor (line 1) | constructor(t,e,i){this.diagnostics=t,this.panel=e,this.selected=i}
method init (line 1) | static init(t,e,i){let n=t,s=i.facet(kf).markerFilter;s&&(n=s(n));let ...
function hf (line 1) | function hf(t,e=null,i=0){let n=null;return t.between(i,1e9,((t,i,{spec:...
function cf (line 1) | function cf(t,e){return!(!t.effects.some((t=>t.is(ff)))&&!t.changes.touc...
function uf (line 1) | function uf(t,e){return t.field(mf,!1)?e:e.concat(ut.appendConfig.of([mf...
method update (line 1) | update(t,e){if(e.docChanged){let i=t.diagnostics.map(e.changes),n=null;i...
function vf (line 1) | function vf(t,e,i){let{diagnostics:n}=t.state.field(mf),s=[],r=2e8,o=0;n...
function wf (line 1) | function wf(t,e){return rc("ul",{class:"cm-tooltip-lint"},e.map((e=>Cf(t...
method constructor (line 1) | constructor(t){this.view=t,this.timeout=-1,this.set=!0;let{delay:e}=t.st...
method run (line 1) | run(){let t=Date.now();if(t<this.lintTime-10)setTimeout(this.run,this.li...
method update (line 1) | update(t){let e=t.state.facet(kf);(t.docChanged||e!=t.startState.facet(k...
method force (line 1) | force(){this.set&&(this.lintTime=Date.now(),this.run())}
method destroy (line 1) | destroy(){clearTimeout(this.timeout)}
function Sf (line 1) | function Sf(t){let e=[];if(t)t:for(let{name:i}of t){for(let t=0;t<i.leng...
function Cf (line 1) | function Cf(t,e,i){var n;let s=i?Sf(e.actions):[];return rc("li",{class:...
class Af (line 1) | class Af extends ti{constructor(t){super(),this.diagnostic=t}eq(t){retur...
method constructor (line 1) | constructor(t){super(),this.diagnostic=t}
method eq (line 1) | eq(t){return t.diagnostic==this.diagnostic}
method toDOM (line 1) | toDOM(){return rc("span",{class:"cm-lintPoint cm-lintPoint-"+this.diag...
class Of (line 1) | class Of{constructor(t,e){this.diagnostic=e,this.id="item_"+Math.floor(4...
method constructor (line 1) | constructor(t,e){this.diagnostic=e,this.id="item_"+Math.floor(42949672...
class Mf (line 1) | class Mf{constructor(t){this.view=t,this.items=[];this.list=rc("ul",{tab...
method constructor (line 1) | constructor(t){this.view=t,this.items=[];this.list=rc("ul",{tabIndex:0...
method selectedIndex (line 1) | get selectedIndex(){let t=this.view.state.field(mf).selected;if(!t)ret...
method update (line 1) | update(){let{diagnostics:t,selected:e}=this.view.state.field(mf),i=0,n...
method sync (line 1) | sync(){let t=this.list.firstChild;function e(){let e=t;t=e.nextSibling...
method moveSelection (line 1) | moveSelection(t){if(this.selectedIndex<0)return;let e=hf(this.view.sta...
method open (line 1) | static open(t){return new Mf(t)}
function Df (line 1) | function Df(t){return function(t,e='viewBox="0 0 40 40"'){return`url('da...
class Rf (line 1) | class Rf{constructor(t,e,i,n,s,r,o,l,a,h=0,c){this.p=t,this.stack=e,this...
method constructor (line 1) | constructor(t,e,i,n,s,r,o,l,a,h=0,c){this.p=t,this.stack=e,this.state=...
method toString (line 1) | toString(){return`[${this.stack.filter(((t,e)=>e%3==0)).concat(this.st...
method start (line 1) | static start(t,e,i=0){let n=t.parser.context;return new Rf(t,[],e,i,i,...
method context (line 1) | get context(){return this.curContext?this.curContext.context:null}
method pushState (line 1) | pushState(t,e){this.stack.push(this.state,e,this.bufferBase+this.buffe...
method reduce (line 1) | reduce(t){let e=t>>19,i=65535&t,{parser:n}=this.p,s=n.dynamicPrecedenc...
method storeNode (line 1) | storeNode(t,e,i,n=4,s=!1){if(0==t&&(!this.stack.length||this.stack[thi...
method shift (line 1) | shift(t,e,i){let n=this.pos;if(131072&t)this.pushState(65535&t,this.po...
method apply (line 1) | apply(t,e,i){65536&t?this.reduce(t):this.shift(t,e,i)}
method useNode (line 1) | useNode(t,e){let i=this.p.reused.length-1;(i<0||this.p.reused[i]!=t)&&...
method split (line 1) | split(){let t=this,e=t.buffer.length;for(;e>0&&t.buffer[e-2]>t.reduceP...
method recoverByDelete (line 1) | recoverByDelete(t,e){let i=t<=this.p.parser.maxNode;i&&this.storeNode(...
method canShift (line 1) | canShift(t){for(let e=new Lf(this);;){let i=this.p.parser.stateSlot(e....
method recoverByInsert (line 1) | recoverByInsert(t){if(this.stack.length>=300)return[];let e=this.p.par...
method forceReduce (line 1) | forceReduce(){let t=this.p.parser.stateSlot(this.state,5);if(0==(65536...
method forceAll (line 1) | forceAll(){for(;!this.p.parser.stateFlag(this.state,2);)if(!this.force...
method deadEnd (line 1) | get deadEnd(){if(3!=this.stack.length)return!1;let{parser:t}=this.p;re...
method restart (line 1) | restart(){this.state=this.stack[0],this.stack.length=0}
method sameState (line 1) | sameState(t){if(this.state!=t.state||this.stack.length!=t.stack.length...
method parser (line 1) | get parser(){return this.p.parser}
method dialectEnabled (line 1) | dialectEnabled(t){return this.p.parser.dialect.flags[t]}
method shiftContext (line 1) | shiftContext(t,e){this.curContext&&this.updateContext(this.curContext....
method reduceContext (line 1) | reduceContext(t,e){this.curContext&&this.updateContext(this.curContext...
method emitContext (line 1) | emitContext(){let t=this.buffer.length-1;(t<0||-3!=this.buffer[t])&&th...
method emitLookAhead (line 1) | emitLookAhead(){let t=this.buffer.length-1;(t<0||-4!=this.buffer[t])&&...
method updateContext (line 1) | updateContext(t){if(t!=this.curContext.context){let e=new Ef(this.curC...
method setLookAhead (line 1) | setLookAhead(t){t>this.lookAhead&&(this.emitLookAhead(),this.lookAhead...
method close (line 1) | close(){this.curContext&&this.curContext.tracker.strict&&this.emitCont...
class Ef (line 1) | class Ef{constructor(t,e){this.tracker=t,this.context=e,this.hash=t.stri...
method constructor (line 1) | constructor(t,e){this.tracker=t,this.context=e,this.hash=t.strict?t.ha...
class Lf (line 1) | class Lf{constructor(t){this.start=t,this.state=t.state,this.stack=t.sta...
method constructor (line 1) | constructor(t){this.start=t,this.state=t.state,this.stack=t.stack,this...
method reduce (line 1) | reduce(t){let e=65535&t,i=t>>19;0==i?(this.stack==this.start.stack&&(t...
class Nf (line 1) | class Nf{constructor(t,e,i){this.stack=t,this.pos=e,this.index=i,this.bu...
method constructor (line 1) | constructor(t,e,i){this.stack=t,this.pos=e,this.index=i,this.buffer=t....
method create (line 1) | static create(t,e=t.bufferBase+t.buffer.length){return new Nf(t,e,e-t....
method maybeNext (line 1) | maybeNext(){let t=this.stack.parent;null!=t&&(this.index=this.stack.bu...
method id (line 1) | get id(){return this.buffer[this.index-4]}
method start (line 1) | get start(){return this.buffer[this.index-3]}
method end (line 1) | get end(){return this.buffer[this.index-2]}
method size (line 1) | get size(){return this.buffer[this.index-1]}
method next (line 1) | next(){this.index-=4,this.pos-=4,0==this.index&&this.maybeNext()}
method fork (line 1) | fork(){return new Nf(this.stack,this.pos,this.index)}
class If (line 1) | class If{constructor(){this.start=-1,this.value=-1,this.end=-1,this.exte...
method constructor (line 1) | constructor(){this.start=-1,this.value=-1,this.end=-1,this.extended=-1...
class Wf (line 1) | class Wf{constructor(t,e){this.input=t,this.ranges=e,this.chunk="",this....
method constructor (line 1) | constructor(t,e){this.input=t,this.ranges=e,this.chunk="",this.chunkOf...
method resolveOffset (line 1) | resolveOffset(t,e){let i=this.range,n=this.rangeIndex,s=this.pos+t;for...
method clipPos (line 1) | clipPos(t){if(t>=this.range.from&&t<this.range.to)return t;for(let e o...
method peek (line 1) | peek(t){let e,i,n=this.chunkOff+t;if(n>=0&&n<this.chunk.length)e=this....
method acceptToken (line 1) | acceptToken(t,e=0){let i=e?this.resolveOffset(e,-1):this.pos;if(null==...
method getChunk (line 1) | getChunk(){if(this.pos>=this.chunk2Pos&&this.pos<this.chunk2Pos+this.c...
method readNext (line 1) | readNext(){return this.chunkOff>=this.chunk.length&&(this.getChunk(),t...
method advance (line 1) | advance(t=1){for(this.chunkOff+=t;this.pos+t>=this.range.to;){if(this....
method setDone (line 1) | setDone(){return this.pos=this.chunkPos=this.end,this.range=this.range...
method reset (line 1) | reset(t,e){if(e?(this.token=e,e.start=t,e.lookAhead=t+1,e.value=e.exte...
method read (line 1) | read(t,e){if(t>=this.chunkPos&&e<=this.chunkPos+this.chunk.length)retu...
class zf (line 1) | class zf{constructor(t,e){this.data=t,this.id=e}token(t,e){!function(t,e...
method constructor (line 1) | constructor(t,e){this.data=t,this.id=e}
method token (line 1) | token(t,e){!function(t,e,i,n){let s=0,r=1<<n,{parser:o}=i.p,{dialect:l...
class Hf (line 1) | class Hf{constructor(t,e={}){this.token=t,this.contextual=!!e.contextual...
method constructor (line 1) | constructor(t,e={}){this.token=t,this.contextual=!!e.contextual,this.f...
function Ff (line 1) | function Ff(t,e=Uint16Array){if("string"!=typeof t)return t;let i=null;f...
function $f (line 1) | function $f(t,e,i){let n=t.cursor(bo.IncludeAnonymous);for(n.moveTo(e);;...
class Qf (line 1) | class Qf{constructor(t,e){this.fragments=t,this.nodeSet=e,this.i=0,this....
method constructor (line 1) | constructor(t,e){this.fragments=t,this.nodeSet=e,this.i=0,this.fragmen...
method nextFragment (line 1) | nextFragment(){let t=this.fragment=this.i==this.fragments.length?null:...
method nodeAt (line 1) | nodeAt(t){if(t<this.nextStart)return null;for(;this.fragment&&this.saf...
class Kf (line 1) | class Kf{constructor(t,e){this.stream=e,this.tokens=[],this.mainToken=nu...
method constructor (line 1) | constructor(t,e){this.stream=e,this.tokens=[],this.mainToken=null,this...
method getActions (line 1) | getActions(t){let e=0,i=null,{parser:n}=t.p,{tokenizers:s}=n,r=n.state...
method getMainToken (line 1) | getMainToken(t){if(this.mainToken)return this.mainToken;let e=new If,{...
method updateCachedToken (line 1) | updateCachedToken(t,e,i){let n=this.stream.clipPos(i.pos);if(e.token(t...
method putAction (line 1) | putAction(t,e,i,n){for(let e=0;e<n;e+=3)if(this.actions[e]==t)return n...
method addActions (line 1) | addActions(t,e,i,n){let{state:s}=t,{parser:r}=t.p,{data:o}=r;for(let t...
class Gf (line 1) | class Gf{constructor(t,e,i,n){this.parser=t,this.input=e,this.ranges=n,t...
method constructor (line 1) | constructor(t,e,i,n){this.parser=t,this.input=e,this.ranges=n,this.rec...
method parsedPos (line 1) | get parsedPos(){return this.minStackPos}
method advance (line 1) | advance(){let t,e,i=this.stacks,n=this.minStackPos,s=this.stacks=[];fo...
method stopAt (line 1) | stopAt(t){if(null!=this.stoppedAt&&this.stoppedAt<t)throw new RangeErr...
method advanceStack (line 1) | advanceStack(t,e,i){let n=t.pos,{parser:s}=this,r=qf?this.stackID(t)+"...
method advanceFully (line 1) | advanceFully(t,e){let i=t.pos;for(;;){if(!this.advanceStack(t,null,nul...
method runRecovery (line 1) | runRecovery(t,e,i){let n=null,s=!1;for(let r=0;r<t.length;r++){let o=t...
method stackToTree (line 1) | stackToTree(t){return t.close(),xo.build({buffer:Nf.create(t),nodeSet:...
method stackID (line 1) | stackID(t){let e=(_f||(_f=new WeakMap)).get(t);return e||_f.set(t,e=St...
function Jf (line 1) | function Jf(t,e){for(let i=0;i<e.length;i++){let n=e[i];if(n.pos==t.pos&...
class Xf (line 1) | class Xf{constructor(t,e,i){this.source=t,this.flags=e,this.disabled=i}a...
method constructor (line 1) | constructor(t,e,i){this.source=t,this.flags=e,this.disabled=i}
method allows (line 1) | allows(t){return!this.disabled||0==this.disabled[t]}
class Zf (line 1) | class Zf extends Wo{constructor(t){if(super(),this.wrappers=[],14!=t.ver...
method constructor (line 1) | constructor(t){if(super(),this.wrappers=[],14!=t.version)throw new Ran...
method createParse (line 1) | createParse(t,e,i){let n=new Gf(this,t,e,i);for(let s of this.wrappers...
method getGoto (line 1) | getGoto(t,e,i=!1){let n=this.goto;if(e>=n[0])return-1;for(let s=n[e+1]...
method hasAction (line 1) | hasAction(t,e){let i=this.data;for(let n=0;n<2;n++)for(let s,r=this.st...
method stateSlot (line 1) | stateSlot(t,e){return this.states[6*t+e]}
method stateFlag (line 1) | stateFlag(t,e){return(this.stateSlot(t,0)&e)>0}
method validAction (line 1) | validAction(t,e){if(e==this.stateSlot(t,4))return!0;for(let i=this.sta...
method nextStates (line 1) | nextStates(t){let e=[];for(let i=this.stateSlot(t,1);;i+=3){if(65535==...
method overrides (line 1) | overrides(t,e){let i=td(this.data,this.tokenPrecTable,e);return i<0||t...
method configure (line 1) | configure(t){let e=Object.assign(Object.create(Zf.prototype),this);if(...
method hasWrappers (line 1) | hasWrappers(){return this.wrappers.length>0}
method getName (line 1) | getName(t){return this.termNames?this.termNames[t]:String(t<=this.maxN...
method eofTerm (line 1) | get eofTerm(){return this.maxNode+1}
method topNode (line 1) | get topNode(){return this.nodeSet.types[this.top[1]]}
method dynamicPrecedence (line 1) | dynamicPrecedence(t){let e=this.dynamicPrecedences;return null==e?0:e[...
method parseDialect (line 1) | parseDialect(t){let e=Object.keys(this.dialects),i=e.map((()=>!1));if(...
method deserialize (line 1) | static deserialize(t){return new Zf(t)}
function Yf (line 1) | function Yf(t,e){return t[e]|t[e+1]<<16}
function td (line 1) | function td(t,e,i){for(let n,s=e;65535!=(n=t[s]);s++)if(n==i)return s-e;...
function ed (line 1) | function ed(t){if(t.external){let e=t.extend?1:0;return(i,n)=>t.external...
function id (line 1) | function id(t){return t>=65&&t<=90||t>=97&&t<=122||t>=48&&t<=57}
function nd (line 1) | function nd(t,e,i){for(let n=!1;;){if(t.next<0)return;if(t.next==e&&!n)r...
function sd (line 1) | function sd(t,e){for(;95==t.next||id(t.next);)null!=e&&(e+=String.fromCh...
function rd (line 1) | function rd(t,e){for(;48==t.next||49==t.next;)t.advance();e&&t.next==e&&...
function od (line 1) | function od(t,e){for(;;){if(46==t.next){if(e)break;e=!0}else if(t.next<4...
function ld (line 1) | function ld(t){for(;!(t.next<0||10==t.next);)t.advance()}
function ad (line 1) | function ad(t,e){for(let i=0;i<e.length;i++)if(e.charCodeAt(i)==t)return...
function cd (line 1) | function cd(t,e,i){let n=Object.create(null);n.true=n.false=5,n.null=n.u...
function fd (line 1) | function fd(t){return new Hf((e=>{var i;let{next:n}=e;if(e.advance(),ad(...
function md (line 1) | function md(t){let e=t.cursor().moveTo(t.from,-1);for(;/Comment/.test(e....
function gd (line 1) | function gd(t,e){let i=t.sliceString(e.from,e.to),n=/^([`'"])(.*)\1$/.ex...
function vd (line 1) | function vd(t){return t&&("Identifier"==t.name||"QuotedIdentifier"==t.na...
function wd (line 1) | function wd(t,e){if("CompositeIdentifier"==e.name){let i=[];for(let n=e....
function yd (line 1) | function yd(t,e){for(let i=[];;){if(!e||"."!=e.name)return i;let n=md(e)...
function bd (line 1) | function bd(t,e){let i=vl(t).resolveInner(e,-1),n=function(t,e){let i;fo...
class Cd (line 1) | class Cd{constructor(){this.list=[],this.children=void 0}child(t){let e=...
method constructor (line 1) | constructor(){this.list=[],this.children=void 0}
method child (line 1) | child(t){let e=this.children||(this.children=Object.create(null));retu...
method childCompletions (line 1) | childCompletions(t){return this.children?Object.keys(this.children).fi...
function Ad (line 1) | function Ad(t,e){let i=Object.keys(t).map((i=>({label:e?i.toUpperCase():...
class Md (line 1) | class Md{constructor(t,e){this.dialect=t,this.language=e}get extension()...
method constructor (line 1) | constructor(t,e){this.dialect=t,this.language=e}
method extension (line 1) | get extension(){return this.language.extension}
method define (line 1) | static define(t){let e=function(t,e,i,n){let s={};for(let e in ud)s[e]...
function Dd (line 1) | function Dd(t,e=!1){return Ad(t.dialect.words,e)}
function Td (line 1) | function Td(t,e=!1){return t.language.data.of({autocomplete:Dd(t,e)})}
function Pd (line 1) | function Pd(t){return t.schema?function(t,e,i,n){let s=new Cd,r=s.child(...
function Rd (line 1) | function Rd(t){return t.schema?(t.dialect||Bd).language.data.of({autocom...
function Ed (line 1) | function Ed(t={}){let e=t.dialect||Bd;return new Ml(e.language,[Rd(t),Td...
FILE: datasette/static/cm-editor-6.0.1.js
function editorFromTextArea (line 20) | function editorFromTextArea(textarea, conf = {}) {
FILE: datasette/static/column-chooser.js
class ColumnChooser (line 1) | class ColumnChooser extends HTMLElement {
method constructor (line 2) | constructor() {
method open (line 407) | open({ columns, selected = [], onApply }) {
method _close (line 422) | _close() {
method _selectAll (line 430) | _selectAll() {
method _deselectAll (line 438) | _deselectAll() {
method _apply (line 446) | _apply() {
method _render (line 454) | _render() {
method _updateCounts (line 494) | _updateCounts() {
method _startDrag (line 502) | _startDrag(e, idx) {
method _positionGhost (line 531) | _positionGhost(cx, cy) {
method _onMove (line 536) | _onMove(e) {
method _onUp (line 544) | _onUp() {
method _updateDropTarget (line 603) | _updateDropTarget(clientY) {
method _clearDropIndicators (line 636) | _clearDropIndicators() {
method _updateAutoScroll (line 642) | _updateAutoScroll(clientY) {
method _stopAutoScroll (line 689) | _stopAutoScroll() {
FILE: datasette/static/datasette-manager.js
constant DATASETTE_EVENTS (line 2) | const DATASETTE_EVENTS = {
constant DOM_SELECTORS (line 9) | const DOM_SELECTORS = {
FILE: datasette/static/json-format-highlight-1.0.1.js
function index (line 24) | function index(json, colorOptions) {
FILE: datasette/static/mobile-column-actions.js
function mobileColumnHeaders (line 5) | function mobileColumnHeaders(manager) {
function mobileColumnMetaText (line 11) | function mobileColumnMetaText(th) {
function createMobileColumnActionNode (line 25) | function createMobileColumnActionNode(itemConfig, closeDialog) {
function initMobileColumnActions (line 49) | function initMobileColumnActions(manager) {
FILE: datasette/static/navigation-search.js
class NavigationSearch (line 1) | class NavigationSearch extends HTMLElement {
method constructor (line 2) | constructor() {
method render (line 13) | render() {
method setupEventListeners (line 189) | setupEventListeners() {
method isInputFocused (line 244) | isInputFocused() {
method loadInitialData (line 254) | loadInitialData() {
method handleSearch (line 268) | handleSearch(query) {
method fetchResults (line 284) | async fetchResults(url, query) {
method filterLocalItems (line 299) | filterLocalItems(query) {
method renderResults (line 314) | renderResults() {
method moveSelection (line 357) | moveSelection(direction) {
method selectCurrentItem (line 365) | selectCurrentItem() {
method selectItem (line 371) | selectItem(index) {
method openMenu (line 390) | openMenu() {
method closeMenu (line 404) | closeMenu() {
method escapeHtml (line 409) | escapeHtml(text) {
FILE: datasette/static/table.js
function getParams (line 16) | function getParams() {
function paramsToUrl (line 20) | function paramsToUrl(params) {
function sortDescUrl (line 25) | function sortDescUrl(column) {
function sortAscUrl (line 33) | function sortAscUrl(column) {
function facetUrl (line 41) | function facetUrl(column) {
function hideColumnUrl (line 47) | function hideColumnUrl(column) {
function showAllColumnsUrl (line 53) | function showAllColumnsUrl() {
function notBlankUrl (line 60) | function notBlankUrl(column) {
function getDisplayedFacets (line 66) | function getDisplayedFacets() {
function getColumnClassName (line 72) | function getColumnClassName(th) {
function getColumnCells (line 78) | function getColumnCells(th) {
function getColumnMeta (line 87) | function getColumnMeta(th) {
function getColumnTypeText (line 96) | function getColumnTypeText(th) {
function getSetColumnTypeData (line 105) | function getSetColumnTypeData() {
function getSetColumnTypeConfig (line 109) | function getSetColumnTypeConfig(column) {
function canSetColumnType (line 117) | function canSetColumnType() {
function setColumnTypeActionLabel (line 121) | function setColumnTypeActionLabel(column) {
function createSetColumnTypeOption (line 131) | function createSetColumnTypeOption(value, name, description, checked) {
function setSetColumnTypeDialogBusy (line 159) | function setSetColumnTypeDialogBusy(state, isBusy) {
function clearSetColumnTypeDialogError (line 171) | function clearSetColumnTypeDialogError(state) {
function showSetColumnTypeDialogError (line 176) | function showSetColumnTypeDialogError(state, message) {
function ensureSetColumnTypeDialog (line 181) | function ensureSetColumnTypeDialog() {
function openSetColumnTypeDialog (line 293) | function openSetColumnTypeDialog(th) {
function canChooseColumns (line 358) | function canChooseColumns() {
function shouldShowShowAllColumns (line 364) | function shouldShowShowAllColumns() {
function hasMultipleVisibleColumns (line 369) | function hasMultipleVisibleColumns(manager) {
function buildColumnActionItems (line 377) | function buildColumnActionItems(manager, th, options) {
function buildColumnActionState (line 469) | function buildColumnActionState(manager, th, options) {
function initializeColumnActions (line 479) | function initializeColumnActions(manager) {
function renderActionLink (line 500) | function renderActionLink(itemConfig) {
function closeMenu (line 516) | function closeMenu() {
function onTableHeaderClick (line 536) | function onTableHeaderClick(ev) {
function addButtonsToFilterRows (line 638) | function addButtonsToFilterRows(manager) {
function initAutocompleteForFilterValues (line 666) | function initAutocompleteForFilterValues(manager) {
function openColumnChooser (line 703) | function openColumnChooser() {
FILE: datasette/tokens.py
class TokenRestrictions (line 22) | class TokenRestrictions:
method allow_all (line 38) | def allow_all(self, action: str) -> "TokenRestrictions":
method allow_database (line 43) | def allow_database(self, database: str, action: str) -> "TokenRestrict...
method allow_resource (line 48) | def allow_resource(
class TokenHandler (line 56) | class TokenHandler:
method create_token (line 66) | async def create_token(
method verify_token (line 77) | async def verify_token(self, datasette: "Datasette", token: str) -> Op...
class SignedTokenHandler (line 85) | class SignedTokenHandler(TokenHandler):
method create_token (line 92) | async def create_token(
method verify_token (line 134) | async def verify_token(self, datasette: "Datasette", token: str) -> Op...
FILE: datasette/tracer.py
function get_task_id (line 16) | def get_task_id():
function trace_child_tasks (line 28) | def trace_child_tasks():
function trace (line 35) | def trace(trace_type, **kwargs):
function capture_traces (line 69) | def capture_traces(tracer):
class AsgiTracer (line 80) | class AsgiTracer:
method __init__ (line 84) | def __init__(self, app):
method __call__ (line 87) | async def __call__(self, scope, receive, send):
FILE: datasette/url_builder.py
class Urls (line 5) | class Urls:
method __init__ (line 6) | def __init__(self, ds):
method path (line 9) | def path(self, path, format=None):
method instance (line 18) | def instance(self, format=None):
method static (line 21) | def static(self, path):
method static_plugins (line 24) | def static_plugins(self, plugin, path):
method logout (line 27) | def logout(self):
method database (line 30) | def database(self, database, format=None):
method database_query (line 34) | def database_query(self, database, sql, format=None):
method table (line 40) | def table(self, database, table, format=None):
method query (line 46) | def query(self, database, query, format=None):
method row (line 52) | def row(self, database, table, row_path, format=None):
method row_blob (line 58) | def row_blob(self, database, table, row_path, column):
FILE: datasette/utils/__init__.py
class PaginatedResources (line 35) | class PaginatedResources:
method all (line 48) | async def all(self):
function documented (line 158) | def documented(fn):
function await_me_maybe (line 164) | async def await_me_maybe(value: typing.Any) -> typing.Any:
function urlsafe_components (line 173) | def urlsafe_components(token):
function path_from_row_pks (line 178) | def path_from_row_pks(row, pks, use_rowid, quote=True):
function compound_keys_after_sql (line 195) | def compound_keys_after_sql(pks, start_index=0):
class CustomJSONEncoder (line 221) | class CustomJSONEncoder(json.JSONEncoder):
method default (line 222) | def default(self, obj):
function sqlite_timelimit (line 240) | def sqlite_timelimit(conn, ms):
class InvalidSql (line 262) | class InvalidSql(Exception):
function validate_sql_select (line 315) | def validate_sql_select(sql):
function append_querystring (line 327) | def append_querystring(url, querystring):
function path_with_added_args (line 332) | def path_with_added_args(request, args, path=None):
function path_with_removed_args (line 348) | def path_with_removed_args(request, args, path=None):
function path_with_replaced_args (line 377) | def path_with_replaced_args(request, args, path=None):
function escape_css_string (line 397) | def escape_css_string(s):
function escape_sqlite (line 404) | def escape_sqlite(s):
function make_dockerfile (line 411) | def make_dockerfile(
function temporary_docker_directory (line 497) | def temporary_docker_directory(
function detect_primary_keys (line 577) | def detect_primary_keys(conn, table):
function get_outbound_foreign_keys (line 585) | def get_outbound_foreign_keys(conn, table):
function get_all_foreign_keys (line 613) | def get_all_foreign_keys(conn):
function detect_spatialite (line 652) | def detect_spatialite(conn):
function detect_fts (line 659) | def detect_fts(conn, table):
function detect_fts_sql (line 668) | def detect_fts_sql(table):
function detect_json1 (line 683) | def detect_json1(conn=None):
function table_columns (line 693) | def table_columns(conn, table):
function table_column_details (line 697) | def table_column_details(conn, table):
function filters_should_redirect (line 722) | def filters_should_redirect(special_args):
function is_url (line 759) | def is_url(value):
function to_css_class (line 775) | def to_css_class(s):
function link_or_copy (line 798) | def link_or_copy(src, dst):
function link_or_copy_directory (line 808) | def link_or_copy_directory(src, dst):
function module_from_path (line 815) | def module_from_path(path, name):
function path_with_format (line 825) | def path_with_format(
class CustomRow (line 847) | class CustomRow(OrderedDict):
method __init__ (line 850) | def __init__(self, columns, values=None):
method __getitem__ (line 855) | def __getitem__(self, key):
method __iter__ (line 861) | def __iter__(self):
function value_as_boolean (line 866) | def value_as_boolean(value):
class ValueAsBooleanError (line 872) | class ValueAsBooleanError(ValueError):
class WriteLimi
Condensed preview — 268 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,082K chars).
[
{
"path": ".coveragerc",
"chars": 71,
"preview": "[run]\nomit = datasette/_version.py, datasette/utils/shutil_backport.py\n"
},
{
"path": ".dockerignore",
"chars": 112,
"preview": ".DS_Store\n.cache\n.eggs\n.gitignore\n.ipynb_checkpoints\nbuild\n*.spec\n*.egg-info\ndist\nscratchpad\nvenv\n*.db\n*.sqlite\n"
},
{
"path": ".git-blame-ignore-revs",
"chars": 140,
"preview": "# Applying Black\n35d6ee2790e41e96f243c1ff58be0c9c0519a8ce\n368638555160fb9ac78f462d0f79b1394163fa30\n2b344f6a34d2adaa30599"
},
{
"path": ".gitattributes",
"chars": 48,
"preview": "datasette/static/codemirror-* linguist-vendored\n"
},
{
"path": ".github/FUNDING.yml",
"chars": 17,
"preview": "github: [simonw]\n"
},
{
"path": ".github/dependabot.yml",
"chars": 173,
"preview": "version: 2\nupdates:\n- package-ecosystem: pip\n directory: \"/\"\n schedule:\n interval: daily\n time: \"13:00\"\n groups"
},
{
"path": ".github/workflows/deploy-branch-preview.yml",
"chars": 1024,
"preview": "name: Deploy a Datasette branch preview to Vercel\n\non:\n workflow_dispatch:\n inputs:\n branch:\n descriptio"
},
{
"path": ".github/workflows/deploy-latest.yml",
"chars": 5181,
"preview": "name: Deploy latest.datasette.io\n\non:\n workflow_dispatch:\n push:\n branches:\n - main\n # - 1.0-dev\n\npermissio"
},
{
"path": ".github/workflows/documentation-links.yml",
"chars": 294,
"preview": "name: Read the Docs Pull Request Preview\non:\n pull_request_target:\n types:\n - opened\n\npermissions:\n pull-reque"
},
{
"path": ".github/workflows/prettier.yml",
"chars": 565,
"preview": "name: Check JavaScript for conformance with Prettier\n\non: [push]\n\npermissions:\n contents: read\n\njobs:\n prettier:\n r"
},
{
"path": ".github/workflows/publish.yml",
"chars": 3092,
"preview": "name: Publish Python Package\n\non:\n release:\n types: [created]\n\npermissions:\n contents: read\n\njobs:\n test:\n runs"
},
{
"path": ".github/workflows/push_docker_tag.yml",
"chars": 739,
"preview": "name: Push specific Docker tag\n\non:\n workflow_dispatch:\n inputs:\n version_tag:\n description: Tag to buil"
},
{
"path": ".github/workflows/spellcheck.yml",
"chars": 800,
"preview": "name: Check spelling in documentation\n\non: [push, pull_request]\n\npermissions:\n contents: read\n\njobs:\n spellcheck:\n "
},
{
"path": ".github/workflows/stable-docs.yml",
"chars": 2508,
"preview": "name: Update Stable Docs\n\non:\n release:\n types: [published]\n push:\n branches:\n - main\n\npermissions:\n content"
},
{
"path": ".github/workflows/test-coverage.yml",
"chars": 990,
"preview": "name: Calculate test coverage\n\non:\n push:\n branches:\n - main\n pull_request:\n branches:\n - main\npermiss"
},
{
"path": ".github/workflows/test-pyodide.yml",
"chars": 750,
"preview": "name: Test in Pyodide with shot-scraper\n\non:\n push:\n pull_request:\n workflow_dispatch:\n\npermissions:\n contents: read"
},
{
"path": ".github/workflows/test-sqlite-support.yml",
"chars": 1740,
"preview": "name: Test SQLite versions\n\non: [push, pull_request]\n\npermissions:\n contents: read\n\njobs:\n test:\n runs-on: ${{ matr"
},
{
"path": ".github/workflows/test.yml",
"chars": 1463,
"preview": "name: Test\n\non: [push, pull_request]\n\npermissions:\n contents: read\n\njobs:\n test:\n runs-on: ubuntu-latest\n strate"
},
{
"path": ".github/workflows/tmate-mac.yml",
"chars": 236,
"preview": "name: tmate session mac\n\non:\n workflow_dispatch:\n\npermissions:\n contents: read\n\njobs:\n build:\n runs-on: macos-late"
},
{
"path": ".github/workflows/tmate.yml",
"chars": 309,
"preview": "name: tmate session\n\non:\n workflow_dispatch:\n\npermissions:\n contents: read\n models: read\n\njobs:\n build:\n runs-on:"
},
{
"path": ".gitignore",
"chars": 1528,
"preview": "build-metadata.json\ndatasets.json\n\nscratchpad\n\n.vscode\n\nuv.lock\ndata.db\n\n# test databases\n*.db\n\n# We don't use Pipfile, "
},
{
"path": ".isort.cfg",
"chars": 32,
"preview": "[settings]\nmulti_line_output=3\n\n"
},
{
"path": ".prettierrc",
"chars": 40,
"preview": "{\n \"tabWidth\": 2,\n \"useTabs\": false\n}\n"
},
{
"path": ".readthedocs.yaml",
"chars": 213,
"preview": "version: 2\n\nsphinx:\n configuration: docs/conf.py\n\nbuild:\n os: ubuntu-24.04\n tools:\n python: \"3.13\"\n jobs:\n ins"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 5249,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": "Dockerfile",
"chars": 553,
"preview": "FROM python:3.11.0-slim-bullseye as build\n\n# Version of Datasette to install, e.g. 0.55\n# docker build . -t datasette "
},
{
"path": "Justfile",
"chars": 1524,
"preview": "export DATASETTE_SECRET := \"not_a_secret\"\n\n# Run tests and linters\n@default: test lint\n\n# Setup project\n@init:\n uv sync"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "MANIFEST.in",
"chars": 145,
"preview": "recursive-include datasette/static *\nrecursive-include datasette/templates *\ninclude versioneer.py\ninclude datasette/_ve"
},
{
"path": "README.md",
"chars": 5050,
"preview": "<img src=\"https://datasette.io/static/datasette-logo.svg\" alt=\"Datasette\">\n\n[\n"
},
{
"path": "datasette/actor_auth_cookie.py",
"chars": 732,
"preview": "from datasette import hookimpl\nfrom itsdangerous import BadSignature\nfrom datasette.utils import baseconv\nimport time\n\n\n"
},
{
"path": "datasette/app.py",
"chars": 101745,
"preview": "from __future__ import annotations\n\nfrom asgi_csrf import Errors\nimport asyncio\nimport contextvars\nfrom typing import TY"
},
{
"path": "datasette/blob_renderer.py",
"chars": 1894,
"preview": "from datasette import hookimpl\nfrom datasette.utils.asgi import Response, BadRequest\nfrom datasette.utils import to_css_"
},
{
"path": "datasette/cli.py",
"chars": 26139,
"preview": "import asyncio\nimport uvicorn\nimport click\nfrom click import formatting\nfrom click.types import CompositeParamType\nfrom "
},
{
"path": "datasette/column_types.py",
"chars": 2492,
"preview": "from enum import Enum\n\n\nclass SQLiteType(Enum):\n TEXT = \"TEXT\"\n INTEGER = \"INTEGER\"\n REAL = \"REAL\"\n BLOB = \""
},
{
"path": "datasette/database.py",
"chars": 28075,
"preview": "import asyncio\nfrom collections import namedtuple\nfrom pathlib import Path\nimport janus\nimport queue\nimport sqlite_utils"
},
{
"path": "datasette/default_actions.py",
"chars": 2978,
"preview": "from datasette import hookimpl\nfrom datasette.permissions import Action\nfrom datasette.resources import (\n DatabaseRe"
},
{
"path": "datasette/default_column_types.py",
"chars": 2582,
"preview": "import json\nimport re\n\nimport markupsafe\n\nfrom datasette import hookimpl\nfrom datasette.column_types import ColumnType, "
},
{
"path": "datasette/default_magic_parameters.py",
"chars": 1374,
"preview": "from datasette import hookimpl\nimport datetime\nimport os\nimport time\n\n\ndef header(key, request):\n key = key.replace(\""
},
{
"path": "datasette/default_menu_links.py",
"chars": 1371,
"preview": "from datasette import hookimpl\n\n\n@hookimpl\ndef menu_links(datasette, actor):\n async def inner():\n if not await"
},
{
"path": "datasette/default_permissions/__init__.py",
"chars": 2106,
"preview": "\"\"\"\nDefault permission implementations for Datasette.\n\nThis module provides the built-in permission checking logic throu"
},
{
"path": "datasette/default_permissions/config.py",
"chars": 14564,
"preview": "\"\"\"\nConfig-based permission handling for Datasette.\n\nApplies permission rules from datasette.yaml configuration.\n\"\"\"\n\nfr"
},
{
"path": "datasette/default_permissions/defaults.py",
"chars": 1871,
"preview": "\"\"\"\nDefault permission settings for Datasette.\n\nProvides default allow rules for standard view/execute actions.\n\"\"\"\n\nfro"
},
{
"path": "datasette/default_permissions/helpers.py",
"chars": 2541,
"preview": "\"\"\"\nShared helper utilities for default permission implementations.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom datacl"
},
{
"path": "datasette/default_permissions/restrictions.py",
"chars": 6683,
"preview": "\"\"\"\nActor restriction handling for Datasette permissions.\n\nThis module handles the _r (restrictions) key in actor dictio"
},
{
"path": "datasette/default_permissions/root.py",
"chars": 756,
"preview": "\"\"\"\nRoot user permission handling for Datasette.\n\nGrants full permissions to the root user when --root flag is used.\n\"\"\""
},
{
"path": "datasette/default_permissions/tokens.py",
"chars": 1095,
"preview": "\"\"\"\nToken authentication for Datasette.\n\nRegisters the default SignedTokenHandler and delegates token verification\nto da"
},
{
"path": "datasette/events.py",
"chars": 5332,
"preview": "from abc import ABC, abstractproperty\nfrom dataclasses import asdict, dataclass, field\nfrom datasette.hookspecs import h"
},
{
"path": "datasette/facets.py",
"chars": 23008,
"preview": "import json\nimport urllib\nfrom datasette import hookimpl\nfrom datasette.database import QueryInterrupted\nfrom datasette."
},
{
"path": "datasette/filters.py",
"chars": 15261,
"preview": "from datasette import hookimpl\nfrom datasette.resources import DatabaseResource\nfrom datasette.views.base import Dataset"
},
{
"path": "datasette/forbidden.py",
"chars": 456,
"preview": "from datasette import hookimpl, Response\n\n\n@hookimpl(trylast=True)\ndef forbidden(datasette, request, message):\n async"
},
{
"path": "datasette/handle_exception.py",
"chars": 2214,
"preview": "from datasette import hookimpl, Response\nfrom .utils import add_cors_headers\nfrom .utils.asgi import (\n Base400,\n)\nfr"
},
{
"path": "datasette/hookspecs.py",
"chars": 6799,
"preview": "from pluggy import HookimplMarker\nfrom pluggy import HookspecMarker\n\nhookspec = HookspecMarker(\"datasette\")\nhookimpl = H"
},
{
"path": "datasette/inspect.py",
"chars": 3009,
"preview": "import hashlib\n\nfrom .utils import (\n detect_spatialite,\n detect_fts,\n detect_primary_keys,\n escape_sqlite,\n"
},
{
"path": "datasette/permissions.py",
"chars": 6762,
"preview": "from abc import ABC, abstractmethod\nfrom dataclasses import dataclass\nfrom typing import Any, NamedTuple\nimport contextv"
},
{
"path": "datasette/plugins.py",
"chars": 4251,
"preview": "import importlib\nimport os\nimport pluggy\nfrom pprint import pprint\nimport sys\nfrom . import hookspecs\n\nif sys.version_in"
},
{
"path": "datasette/publish/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "datasette/publish/cloudrun.py",
"chars": 9858,
"preview": "from datasette import hookimpl\nimport click\nimport json\nimport os\nimport re\nfrom subprocess import CalledProcessError, c"
},
{
"path": "datasette/publish/common.py",
"chars": 3696,
"preview": "from ..utils import StaticMount\nimport click\nimport os\nimport shutil\nimport sys\n\n\ndef add_common_publish_arguments_and_o"
},
{
"path": "datasette/publish/heroku.py",
"chars": 7897,
"preview": "from contextlib import contextmanager\nfrom datasette import hookimpl\nimport click\nimport json\nimport os\nimport pathlib\ni"
},
{
"path": "datasette/renderer.py",
"chars": 4292,
"preview": "import json\nfrom datasette.utils import (\n value_as_boolean,\n remove_infinites,\n CustomJSONEncoder,\n path_fr"
},
{
"path": "datasette/resources.py",
"chars": 3043,
"preview": "\"\"\"Core resource types for Datasette's permission system.\"\"\"\n\nfrom datasette.permissions import Resource\n\n\nclass Databas"
},
{
"path": "datasette/sql_functions.py",
"chars": 166,
"preview": "from datasette import hookimpl\nfrom datasette.utils import escape_fts\n\n\n@hookimpl\ndef prepare_connection(conn):\n conn"
},
{
"path": "datasette/static/app.css",
"chars": 29423,
"preview": "/* Reset and Page Setup ==================================================== */\n\n/* Reset from http://meyerweb.com/eric/"
},
{
"path": "datasette/static/cm-editor-6.0.1.bundle.js",
"chars": 367132,
"preview": "var cm=function(t){\"use strict\";class e{constructor(){}lineAt(t){if(t<0||t>this.length)throw new RangeError(`Invalid pos"
},
{
"path": "datasette/static/cm-editor-6.0.1.js",
"chars": 2928,
"preview": "import { EditorView, basicSetup } from \"codemirror\";\nimport { keymap } from \"@codemirror/view\";\nimport { sql, SQLDialect"
},
{
"path": "datasette/static/column-chooser.js",
"chars": 20992,
"preview": "class ColumnChooser extends HTMLElement {\n constructor() {\n super();\n this.attachShadow({ mode: \"open\" });\n\n /"
},
{
"path": "datasette/static/datasette-manager.js",
"chars": 7725,
"preview": "// Custom events for use with the native CustomEvent API\nconst DATASETTE_EVENTS = {\n INIT: \"datasette_init\", // returns"
},
{
"path": "datasette/static/json-format-highlight-1.0.1.js",
"chars": 1685,
"preview": "/*\nhttps://github.com/luyilin/json-format-highlight\nFrom https://unpkg.com/json-format-highlight@1.0.1/dist/json-format-"
},
{
"path": "datasette/static/mobile-column-actions.js",
"chars": 9689,
"preview": "var MOBILE_COLUMN_BREAKPOINT = 576;\nvar MOBILE_COLUMN_DIALOG_ID = \"mobile-column-actions-dialog\";\nvar MOBILE_COLUMN_DIAL"
},
{
"path": "datasette/static/navigation-search.js",
"chars": 12098,
"preview": "class NavigationSearch extends HTMLElement {\n constructor() {\n super();\n this.attachShadow({ mode: \"open\" });\n "
},
{
"path": "datasette/static/table.js",
"chars": 22962,
"preview": "var DROPDOWN_HTML = `<div class=\"dropdown-menu\">\n<div class=\"hook\"></div>\n<ul class=\"dropdown-actions\"></ul>\n<p class=\"d"
},
{
"path": "datasette/templates/_action_menu.html",
"chars": 1846,
"preview": "{% if action_links %}\n<div class=\"page-action-menu\">\n<details class=\"actions-menu-links details-menu\">\n <summary>\n "
},
{
"path": "datasette/templates/_close_open_menus.html",
"chars": 589,
"preview": "<script>\ndocument.body.addEventListener('click', (ev) => {\n /* Close any open details elements that this click is out"
},
{
"path": "datasette/templates/_codemirror.html",
"chars": 587,
"preview": "<script src=\"{{ base_url }}-/static/sql-formatter-2.3.3.min.js\" defer></script>\n<script src=\"{{ base_url }}-/static/cm-e"
},
{
"path": "datasette/templates/_codemirror_foot.html",
"chars": 1246,
"preview": "<script>\n {% if table_columns %}\n const schema = {{ table_columns|tojson(2) }};\n {% else %}\n const schema = {};\n {%"
},
{
"path": "datasette/templates/_crumbs.html",
"chars": 422,
"preview": "{% macro nav(request, database=None, table=None) -%}\n{% if crumb_items is defined %}\n {% set items=crumb_items(request="
},
{
"path": "datasette/templates/_debug_common_functions.html",
"chars": 1279,
"preview": "<script>\n// Common utility functions for debug pages\n\n// Populate form from URL parameters on page load\nfunction populat"
},
{
"path": "datasette/templates/_description_source_license.html",
"chars": 1441,
"preview": "{% if metadata.get(\"description_html\") or metadata.get(\"description\") %}\n <div class=\"metadata-description\">\n "
},
{
"path": "datasette/templates/_facet_results.html",
"chars": 1778,
"preview": "<div class=\"facet-results\">\n {% for facet_info in sorted_facet_results %}\n <div class=\"facet-info facet-{{ dat"
},
{
"path": "datasette/templates/_footer.html",
"chars": 1061,
"preview": "Powered by <a href=\"https://datasette.io/\" title=\"Datasette v{{ datasette_version }}\">Datasette</a>\n{% if query_ms %}&mi"
},
{
"path": "datasette/templates/_permission_ui_styles.html",
"chars": 2807,
"preview": "<style>\n.permission-form {\n background-color: #f5f5f5;\n border: 1px solid #ddd;\n border-radius: 5px;\n paddin"
},
{
"path": "datasette/templates/_permissions_debug_tabs.html",
"chars": 1884,
"preview": "{% if has_debug_permission %}\n{% set query_string = '?' + request.query_string if request.query_string else '' %}\n\n<styl"
},
{
"path": "datasette/templates/_suggested_facets.html",
"chars": 284,
"preview": "<p class=\"suggested-facets\">\n Suggested facets: {% for facet in suggested_facets %}<a href=\"{{ facet.toggle_url }}#fa"
},
{
"path": "datasette/templates/_table.html",
"chars": 1959,
"preview": "<!-- above-table-panel is a hook node for plugins to attach to . Displays even if no data available -->\n<div class=\"abov"
},
{
"path": "datasette/templates/allow_debug.html",
"chars": 1602,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}Debug allow rules{% endblock %}\n\n{% block extra_head %}\n<style>\ntextarea {\n "
},
{
"path": "datasette/templates/api_explorer.html",
"chars": 6579,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}API Explorer{% endblock %}\n\n{% block extra_head %}\n<script src=\"{{ base_url "
},
{
"path": "datasette/templates/base.html",
"chars": 3526,
"preview": "{% import \"_crumbs.html\" as crumbs with context %}<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <title>{% block title %}{%"
},
{
"path": "datasette/templates/create_token.html",
"chars": 3989,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}Create an API token{% endblock %}\n\n{% block extra_head %}\n<style type=\"text/"
},
{
"path": "datasette/templates/csrf_error.html",
"chars": 501,
"preview": "{% extends \"base.html\" %}\n{% block title %}CSRF check failed){% endblock %}\n{% block content %}\n<h1>Form origin check fa"
},
{
"path": "datasette/templates/database.html",
"chars": 3723,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}{{ database }}{% endblock %}\n\n{% block extra_head %}\n{{- super() -}}\n{% incl"
},
{
"path": "datasette/templates/debug_actions.html",
"chars": 1268,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}Registered Actions{% endblock %}\n\n{% block content %}\n<h1>Registered actions"
},
{
"path": "datasette/templates/debug_allowed.html",
"chars": 7958,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}Allowed Resources{% endblock %}\n\n{% block extra_head %}\n<script src=\"{{ base"
},
{
"path": "datasette/templates/debug_check.html",
"chars": 8339,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}Permission Check{% endblock %}\n\n{% block extra_head %}\n<script src=\"{{ base_"
},
{
"path": "datasette/templates/debug_permissions_playground.html",
"chars": 5939,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}Debug permissions{% endblock %}\n\n{% block extra_head %}\n{% include \"_permiss"
},
{
"path": "datasette/templates/debug_rules.html",
"chars": 7143,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}Permission Rules{% endblock %}\n\n{% block extra_head %}\n<script src=\"{{ base_"
},
{
"path": "datasette/templates/error.html",
"chars": 319,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}{% if title %}{{ title }}{% else %}Error {{ status }}{% endif %}{% endblock "
},
{
"path": "datasette/templates/index.html",
"chars": 2121,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}{{ metadata.title or \"Datasette\" }}: {% for database in databases %}{{ datab"
},
{
"path": "datasette/templates/logout.html",
"chars": 406,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}Log out{% endblock %}\n\n{% block content %}\n\n<h1>Log out</h1>\n\n<p>You are log"
},
{
"path": "datasette/templates/messages_debug.html",
"chars": 716,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}Debug messages{% endblock %}\n\n{% block content %}\n\n<h1>Debug messages</h1>\n\n"
},
{
"path": "datasette/templates/patterns.html",
"chars": 25082,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <title>Datasette: Pattern Portfolio</title>\n <link rel=\"stylesheet\" href="
},
{
"path": "datasette/templates/query.html",
"chars": 4504,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}{{ database }}{% if query and query.sql %}: {{ query.sql }}{% endif %}{% end"
},
{
"path": "datasette/templates/row.html",
"chars": 1573,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}{{ database }}: {{ table }}{% endblock %}\n\n{% block extra_head %}\n{{- super("
},
{
"path": "datasette/templates/schema.html",
"chars": 1366,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}{% if is_instance %}Schema for all databases{% elif table_name %}Schema for "
},
{
"path": "datasette/templates/show_json.html",
"chars": 209,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}{{ filename }}{% endblock %}\n\n{% block body_class %}show-json{% endblock %}\n"
},
{
"path": "datasette/templates/table.html",
"chars": 11318,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}{{ database }}: {{ table }}: {% if count or count == 0 %}{{ \"{:,}\".format(co"
},
{
"path": "datasette/tokens.py",
"chars": 5816,
"preview": "\"\"\"\nToken handler system for Datasette.\n\nProvides a base class for token handlers and the default signed token handler.\n"
},
{
"path": "datasette/tracer.py",
"chars": 5031,
"preview": "import asyncio\nfrom contextlib import contextmanager\nfrom contextvars import ContextVar\nfrom markupsafe import escape\nim"
},
{
"path": "datasette/url_builder.py",
"chars": 2146,
"preview": "from .utils import tilde_encode, path_with_format, PrefixedUrlString\nimport urllib\n\n\nclass Urls:\n def __init__(self, "
},
{
"path": "datasette/utils/__init__.py",
"chars": 46863,
"preview": "import asyncio\nfrom contextlib import contextmanager\nimport aiofiles\nimport click\nfrom collections import OrderedDict, n"
},
{
"path": "datasette/utils/actions_sql.py",
"chars": 21761,
"preview": "\"\"\"\nSQL query builder for hierarchical permission checking.\n\nThis module implements a cascading permission system based "
},
{
"path": "datasette/utils/asgi.py",
"chars": 18200,
"preview": "import json\nfrom typing import Optional\nfrom datasette.utils import MultiParams, calculate_etag\nfrom datasette.utils.mul"
},
{
"path": "datasette/utils/baseconv.py",
"chars": 1553,
"preview": "\"\"\"\nConvert numbers from base 10 integers to base X strings and back again.\n\nSample usage:\n\n>>> base20 = BaseConverter('"
},
{
"path": "datasette/utils/check_callable.py",
"chars": 684,
"preview": "import inspect\nimport types\nfrom typing import NamedTuple, Any\n\n\nclass CallableStatus(NamedTuple):\n is_callable: bool"
},
{
"path": "datasette/utils/internal_db.py",
"chars": 8147,
"preview": "import textwrap\nfrom datasette.utils import table_column_details\n\n\nasync def init_internal_db(db):\n create_tables_sql"
},
{
"path": "datasette/utils/multipart.py",
"chars": 26023,
"preview": "\"\"\"\nStreaming multipart/form-data parser for ASGI applications.\n\nSupports:\n- Streaming parsing without buffering entire "
},
{
"path": "datasette/utils/permissions.py",
"chars": 15806,
"preview": "# perm_utils.py\nfrom __future__ import annotations\n\nimport json\nfrom typing import Any, Dict, Iterable, List, Sequence, "
},
{
"path": "datasette/utils/shutil_backport.py",
"chars": 2953,
"preview": "\"\"\"\nBackported from Python 3.8.\n\nThis code is licensed under the Python License:\nhttps://github.com/python/cpython/blob/"
},
{
"path": "datasette/utils/sqlite.py",
"chars": 817,
"preview": "using_pysqlite3 = False\ntry:\n import pysqlite3 as sqlite3\n\n using_pysqlite3 = True\nexcept ImportError:\n import "
},
{
"path": "datasette/utils/testing.py",
"chars": 5036,
"preview": "from asgiref.sync import async_to_sync\nfrom urllib.parse import urlencode\nimport json\n\n# These wrapper classes pre-date "
},
{
"path": "datasette/version.py",
"chars": 72,
"preview": "__version__ = \"1.0a26\"\n__version_info__ = tuple(__version__.split(\".\"))\n"
},
{
"path": "datasette/views/__init__.py",
"chars": 60,
"preview": "class Context:\n \"Base class for all documented contexts\"\n"
},
{
"path": "datasette/views/base.py",
"chars": 21299,
"preview": "import asyncio\nimport csv\nimport hashlib\nimport sys\nimport textwrap\nimport time\nimport urllib\nfrom markupsafe import esc"
},
{
"path": "datasette/views/database.py",
"chars": 48498,
"preview": "from dataclasses import dataclass, field\nfrom urllib.parse import parse_qsl, urlencode\nimport asyncio\nimport hashlib\nimp"
},
{
"path": "datasette/views/index.py",
"chars": 7692,
"preview": "import json\n\nfrom datasette.plugins import pm\nfrom datasette.utils import (\n add_cors_headers,\n await_me_maybe,\n "
},
{
"path": "datasette/views/row.py",
"chars": 14737,
"preview": "from datasette.utils.asgi import NotFound, Forbidden, Response\nfrom datasette.database import QueryInterrupted\nfrom data"
},
{
"path": "datasette/views/special.py",
"chars": 41350,
"preview": "import json\nimport logging\nfrom datasette.events import LogoutEvent, LoginEvent, CreateTokenEvent\nfrom datasette.resourc"
},
{
"path": "datasette/views/table.py",
"chars": 74615,
"preview": "import asyncio\nimport itertools\nimport json\nimport urllib\n\nfrom asyncinject import Registry\nimport markupsafe\n\nfrom data"
},
{
"path": "demos/apache-proxy/000-default.conf",
"chars": 321,
"preview": "<Directory /app/html/>\n Options Indexes FollowSymLinks\n AllowOverride None\n Require all granted\n</Directory>\n\n<"
},
{
"path": "demos/apache-proxy/Dockerfile",
"chars": 2110,
"preview": "FROM python:3.11.0-slim-bullseye\n\nRUN apt-get update && \\\n apt-get install -y apache2 supervisor && \\\n apt clean &"
},
{
"path": "demos/apache-proxy/README.md",
"chars": 1387,
"preview": "# Datasette running behind an Apache proxy\n\nSee also [Running Datasette behind a proxy](https://docs.datasette.io/en/lat"
},
{
"path": "demos/apache-proxy/deploy-to-cloud-run.sh",
"chars": 760,
"preview": "#!/bin/bash\n# https://til.simonwillison.net/cloudrun/using-build-args-with-cloud-run\n\nif [[ -z \"$DATASETTE_REF\" ]]; then"
},
{
"path": "demos/apache-proxy/fly.toml",
"chars": 614,
"preview": "app = \"datasette-apache-proxy-demo\"\n\nkill_signal = \"SIGINT\"\nkill_timeout = 5\nprocesses = []\n\n[env]\n\n[experimental]\n all"
},
{
"path": "demos/plugins/example_js_manager_plugins.py",
"chars": 700,
"preview": "from datasette import hookimpl\n\n# Test command:\n# datasette fixtures.db \\ --plugins-dir=demos/plugins/\n# "
},
{
"path": "demos/plugins/static/table-example-plugins.js",
"chars": 2726,
"preview": "/**\n * Example usage of Datasette JS Manager API\n */\n\ndocument.addEventListener(\"datasette_init\", function (evt) {\n con"
},
{
"path": "docs/.gitignore",
"chars": 7,
"preview": "_build\n"
},
{
"path": "docs/Makefile",
"chars": 692,
"preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS =\nSPHI"
},
{
"path": "docs/_static/css/custom.css",
"chars": 161,
"preview": "a.external {\n overflow-wrap: anywhere;\n}\nbody[data-theme=\"dark\"] .sidebar-logo-container {\n background-color: whit"
},
{
"path": "docs/_static/js/custom.js",
"chars": 826,
"preview": "jQuery(function ($) {\n // Show banner linking to /stable/ if this is a /latest/ page\n if (!/\\/latest\\//.test(location."
},
{
"path": "docs/_templates/base.html",
"chars": 1199,
"preview": "{%- extends \"!base.html\" %}\n\n{% block site_meta %}\n{{ super() }}\n<script defer data-domain=\"docs.datasette.io\" src=\"http"
},
{
"path": "docs/_templates/sidebar/brand.html",
"chars": 490,
"preview": "<div class=\"sidebar-brand centered\">\n {% block brand_content %}\n <div class=\"sidebar-logo-container\">\n <a href=\"htt"
},
{
"path": "docs/_templates/sidebar/navigation.html",
"chars": 256,
"preview": "<div class=\"sidebar-tree\">\n <ul>\n <li class=\"toctree-l1\"><a class=\"reference internal\" href=\"{{ pathto(master_doc) }"
},
{
"path": "docs/authentication.rst",
"chars": 44063,
"preview": ".. _authentication:\n\n================================\n Authentication and permissions\n================================\n\n"
},
{
"path": "docs/auto-build.sh",
"chars": 31,
"preview": "sphinx-autobuild . _build/html\n"
},
{
"path": "docs/binary_data.rst",
"chars": 3092,
"preview": ".. _binary:\n\n=============\n Binary data\n=============\n\nSQLite tables can contain binary data in ``BLOB`` columns.\n\nDatas"
},
{
"path": "docs/changelog.rst",
"chars": 188912,
"preview": ".. _changelog:\n\n=========\nChangelog\n=========\n\n.. _v1_0_a26:\n\n1.0a26 (2026-03-18)\n-------------------\n\nNew ``column_type"
},
{
"path": "docs/cli-reference.rst",
"chars": 26302,
"preview": ".. _cli_reference:\n\n===============\n CLI reference\n===============\n\nThe ``datasette`` CLI tool provides a number of comm"
},
{
"path": "docs/codespell-ignore-words.txt",
"chars": 23,
"preview": "alls\nfo\nro\nte\nths\nnotin"
},
{
"path": "docs/conf.py",
"chars": 5580,
"preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Datasette documentation build configuration file, created by\n# sphinx"
},
{
"path": "docs/configuration.rst",
"chars": 30157,
"preview": ".. _configuration:\n\nConfiguration\n=============\n\nDatasette offers several ways to configure your Datasette instances: se"
},
{
"path": "docs/contributing.rst",
"chars": 15596,
"preview": ".. _contributing:\n\nContributing\n============\n\nDatasette is an open source project. We welcome contributions!\n\nThis docum"
},
{
"path": "docs/csv_export.rst",
"chars": 2553,
"preview": ".. _csv_export:\n\nCSV export\n==========\n\nAny Datasette table, view or custom SQL query can be exported as CSV.\n\nTo obtain"
},
{
"path": "docs/custom_templates.rst",
"chars": 13500,
"preview": ".. _customization:\n\nCustom pages and templates\n==========================\n\nDatasette provides a number of ways of custom"
},
{
"path": "docs/deploying.rst",
"chars": 9207,
"preview": ".. _deploying:\n\n=====================\n Deploying Datasette\n=====================\n\nThe quickest way to deploy a Datasette"
},
{
"path": "docs/ecosystem.rst",
"chars": 2117,
"preview": ".. _ecosystem:\n\n=======================\nThe Datasette Ecosystem\n=======================\n\nDatasette sits at the center of"
},
{
"path": "docs/events.md",
"chars": 656,
"preview": "(events)=\n# Events\n\nDatasette includes a mechanism for tracking events that occur while the software is running. This is"
},
{
"path": "docs/facets.rst",
"chars": 9484,
"preview": ".. _facets:\n\nFacets\n======\n\nDatasette facets can be used to add a faceted browse interface to any database table.\nWith f"
},
{
"path": "docs/full_text_search.rst",
"chars": 11840,
"preview": ".. _full_text_search:\n\nFull-text search\n================\n\nSQLite includes `a powerful mechanism for enabling full-text s"
},
{
"path": "docs/getting_started.rst",
"chars": 5086,
"preview": "Getting started\n===============\n\n.. _getting_started_demo:\n\nPlay with a live demo\n---------------------\n\nThe best way to"
},
{
"path": "docs/index.rst",
"chars": 2549,
"preview": "Datasette\n=========\n\n|PyPI| |Changelog| |Python 3.x| |Tests| |License| |docker:\ndatasette| |discord|\n\n.. |PyPI| image:: "
},
{
"path": "docs/installation.rst",
"chars": 9391,
"preview": ".. _installation:\n\n==============\n Installation\n==============\n\nThere are two main options for installing Datasette. You"
},
{
"path": "docs/internals.rst",
"chars": 87427,
"preview": ".. _internals:\n\n=======================\n Internals for plugins\n=======================\n\nMany :ref:`plugin_hooks` are pas"
},
{
"path": "docs/introspection.rst",
"chars": 6748,
"preview": ".. _introspection:\n\nIntrospection\n=============\n\nDatasette includes some pages and JSON API endpoints for introspecting "
},
{
"path": "docs/javascript_plugins.rst",
"chars": 6225,
"preview": ".. _javascript_plugins:\n\nJavaScript plugins\n==================\n\nDatasette can run custom JavaScript in several different"
},
{
"path": "docs/json_api.rst",
"chars": 33644,
"preview": ".. _json_api:\n\nJSON API\n========\n\nDatasette provides a JSON API for your SQLite databases. Anything you can do\nthrough t"
},
{
"path": "docs/metadata.rst",
"chars": 8411,
"preview": ".. _metadata:\n\nMetadata\n========\n\nData loves metadata. Any time you run Datasette you can optionally include a\nYAML or J"
},
{
"path": "docs/metadata_doc.py",
"chars": 1956,
"preview": "import json\nimport textwrap\nfrom yaml import safe_dump\nfrom ruamel.yaml import YAML\n\n\ndef metadata_example(cog, data=Non"
},
{
"path": "docs/pages.rst",
"chars": 7504,
"preview": ".. _pages:\n\n=========================\n Pages and API endpoints\n=========================\n\nThe Datasette web application "
},
{
"path": "docs/performance.rst",
"chars": 5408,
"preview": ".. _performance:\n\nPerformance and caching\n=======================\n\nDatasette runs on top of SQLite, and SQLite has excel"
},
{
"path": "docs/plugin_hooks.rst",
"chars": 91398,
"preview": ".. _plugin_hooks:\n\nPlugin hooks\n============\n\nDatasette :ref:`plugins <plugins>` use *plugin hooks* to customize Dataset"
},
{
"path": "docs/plugins.rst",
"chars": 16767,
"preview": ".. _plugins:\n\nPlugins\n=======\n\nDatasette's plugin system allows additional features to be implemented as Python\ncode (or"
},
{
"path": "docs/publish.rst",
"chars": 9349,
"preview": ".. _publishing:\n\n=================\n Publishing data\n=================\n\nDatasette includes tools for publishing and deplo"
},
{
"path": "docs/settings.rst",
"chars": 14243,
"preview": ".. _settings:\n\nSettings\n========\n\nUsing \\-\\-setting\n-----------------\n\nDatasette supports a number of settings. These ca"
},
{
"path": "docs/spatialite.rst",
"chars": 12387,
"preview": ".. _spatialite:\n\n============\n SpatiaLite\n============\n\nThe `SpatiaLite module <https://www.gaia-gis.it/fossil/libspatia"
},
{
"path": "docs/sql_queries.rst",
"chars": 23303,
"preview": ".. _sql:\n\nRunning SQL queries\n===================\n\nDatasette treats SQLite database files as read-only and immutable. Th"
},
{
"path": "docs/testing_plugins.rst",
"chars": 12065,
"preview": ".. _testing_plugins:\n\nTesting plugins\n===============\n\nWe recommend using `pytest <https://docs.pytest.org/>`__ to write"
},
{
"path": "docs/upgrade-1.0a20.md",
"chars": 8837,
"preview": "---\norphan: true\n---\n\n# Datasette 1.0a20 plugin upgrade guide\n\nDatasette 1.0a20 makes some breaking changes to Datasette"
},
{
"path": "docs/upgrade_guide.md",
"chars": 6634,
"preview": "(upgrade_guide)=\n# Upgrade guide\n\n(upgrade_guide_v1)=\n## Datasette 0.X -> 1.0\n\nThis section reviews breaking changes Dat"
},
{
"path": "docs/writing_plugins.rst",
"chars": 14989,
"preview": ".. _writing_plugins:\n\nWriting plugins\n===============\n\nYou can write one-off plugins that apply to just one Datasette in"
},
{
"path": "package.json",
"chars": 425,
"preview": "{\n \"name\": \"datasette\",\n \"private\": true,\n \"devDependencies\": {\n \"prettier\": \"^3.0.0\"\n },\n \"scripts\": {\n \"fix"
},
{
"path": "pyproject.toml",
"chars": 2574,
"preview": "[project]\nname = \"datasette\"\ndynamic = [\"version\"]\ndescription = \"An open source multi-tool for exploring and publishing"
},
{
"path": "pytest.ini",
"chars": 316,
"preview": "[pytest]\nfilterwarnings=\n # https://github.com/pallets/jinja/issues/927\n ignore:Using or importing the ABCs::jinja"
},
{
"path": "ruff.toml",
"chars": 42,
"preview": "line-length = 160\ntarget-version = \"py310\""
},
{
"path": "setup.cfg",
"chars": 22,
"preview": "[aliases]\ntest=pytest\n"
},
{
"path": "test-in-pyodide-with-shot-scraper.sh",
"chars": 1417,
"preview": "#!/bin/bash\nset -e\n# So the script fails if there are any errors\n\n# Build the wheel\npython3 -m build\n\n# Find name of whe"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/build_small_spatialite_db.py",
"chars": 796,
"preview": "import sqlite3\n\n# This script generates the spatialite.db file in our tests directory.\n\n\ndef generate_it(filename):\n "
},
{
"path": "tests/conftest.py",
"chars": 8142,
"preview": "import httpx\nimport os\nimport pathlib\nimport pytest\nimport pytest_asyncio\nimport re\nimport subprocess\nimport sys\nimport "
},
{
"path": "tests/ext.c",
"chars": 1548,
"preview": "/*\n** This file implements a SQLite extension with multiple entrypoints.\n**\n** The default entrypoint, sqlite3_ext_init,"
},
{
"path": "tests/fixtures.py",
"chars": 26794,
"preview": "from datasette.app import Datasette\nfrom datasette.utils.sqlite import sqlite3\nfrom datasette.utils.testing import TestC"
},
{
"path": "tests/plugins/messages_output_renderer.py",
"chars": 544,
"preview": "from datasette import hookimpl\n\n\ndef render_message_debug(datasette, request):\n if request.args.get(\"add_msg\"):\n "
},
{
"path": "tests/plugins/my_plugin.py",
"chars": 18592,
"preview": "import asyncio\nfrom datasette import hookimpl\nfrom datasette.facets import Facet\nfrom datasette.tokens import TokenHandl"
},
{
"path": "tests/plugins/my_plugin_2.py",
"chars": 6360,
"preview": "from datasette import hookimpl\nfrom datasette.utils.asgi import Response\nfrom functools import wraps\nimport markupsafe\ni"
},
{
"path": "tests/plugins/register_output_renderer.py",
"chars": 2209,
"preview": "from datasette import hookimpl\nfrom datasette.utils.asgi import Response\nimport json\n\n\nasync def can_render(\n dataset"
},
{
"path": "tests/plugins/sleep_sql_function.py",
"chars": 154,
"preview": "from datasette import hookimpl\nimport time\n\n\n@hookimpl\ndef prepare_connection(conn):\n conn.create_function(\"sleep\", 1"
},
{
"path": "tests/plugins/view_name.py",
"chars": 167,
"preview": "from datasette import hookimpl\n\n\n@hookimpl\ndef extra_template_vars(view_name, request):\n return {\n \"view_name\""
},
{
"path": "tests/test-datasette-load-plugins.sh",
"chars": 882,
"preview": "#!/bin/bash\n# This should only run in environments where both\n# datasette-init and datasette-json-html are installed\n\nPL"
}
]
// ... and 68 more files (download for full content)
About this extraction
This page contains the full source code of the simonw/datasette GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 268 files (2.8 MB), approximately 745.9k tokens, and a symbol index with 3597 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.