diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
new file mode 100644
index 00000000..e049e9ed
--- /dev/null
+++ b/.github/workflows/docs.yaml
@@ -0,0 +1,55 @@
+name: Documentation
+
+on:
+ push:
+ pull_request:
+ branches:
+ - '**'
+
+permissions: {}
+
+concurrency:
+ group: pages
+ cancel-in-progress: false
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ with:
+ persist-credentials: false
+ - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
+ with:
+ python-version: '3.14'
+ cache: 'pip'
+ - name: Install documentation dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r docs/requirements.txt
+ - name: Build documentation
+ run: mkdocs build --strict
+ - name: Upload Pages artifact
+ if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
+ uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
+ with:
+ path: site
+
+ deploy:
+ if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
+ needs: build
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ permissions:
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
diff --git a/.gitignore b/.gitignore
index 51c08296..820ee873 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ build
cover
dist
docs/_build
+site/
lib/PyLD.egg-info
profiler
tests/test_caching.py
diff --git a/AGENTS.md b/AGENTS.md
index df89c8d6..e4187d8f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,6 +1,6 @@
# Agent guidelines
-Read [CONTRIBUTING.rst](CONTRIBUTING.rst) for code style, linting (e.g. `make lint`, `make fmt`), and release process.
+Read [CONTRIBUTING.md](CONTRIBUTING.md) for code style, linting (e.g. `make lint`, `make fmt`), and release process.
## Testing
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 80a614bb..12770a14 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,9 @@
class-based `DocumentLoader` instances while preserving the existing
callable factory API. The concrete `RequestsDocumentLoader` and
`AioHttpDocumentLoader` classes are also importable from `pyld`.
+- Convert `./README.rst` and `./CONTRIBUTING.rst` (reStructuredText) to
+ `./README.md` and `./CONTRIBUTING.md` (markdown). Also update their
+ contents to reflect the current state of the repo.
### Added
- `pyld.DocumentLoader` abstract base class for class-based document loaders,
@@ -258,7 +261,7 @@
- **1.0.0**!
- [Semantic Versioning](https://semver.org/) is now past the "initial
development" 0.x.y stage (after 6+ years!).
-- [Conformance](README.rst#conformance):
+- [Conformance](README.md#conformance):
- JSON-LD 1.0 + JSON-LD 1.0 errata
- JSON-LD 1.1 drafts
- Thanks to the JSON-LD and related communities and the many many people over
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..5f79a983
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,80 @@
+# Contributing to PyLD
+
+Want to contribute to PyLD? Great! Here are a few notes:
+
+## Code
+
+* In general, follow the common [PEP 8 Style Guide](https://www.python.org/dev/peps/pep-0008/).
+* Try to make the code pass [ruff](https://docs.astral.sh/ruff/) checks.
+
+ * `make lint` or `ruff check lib/pyld/*`
+ * You can also apply automatic fixing and formatting
+ using `make fmt`
+
+* Use version `X.Y.Z-dev` in dev mode.
+* Use version `X.Y.Z` for releases.
+
+## Documentation
+
+The public documentation site is built with MkDocs Material.
+
+* Install documentation dependencies:
+
+ * `pip install -r docs/requirements.txt`
+
+* Preview documentation locally:
+
+ * `mkdocs serve`
+
+* Check documentation before submitting changes:
+
+ * `mkdocs build --strict`
+
+* Refresh bundled JSON-LD context files:
+
+ * `make download-bundled-contexts`
+
+## Versioning
+
+* Follow the [Semantic Versioning](https://semver.org/) guidelines.
+
+## Release Process
+
+* `$EDITOR CHANGELOG.md`: update CHANGELOG with new notes, version, and date.
+* commit changes
+* `$EDITOR lib/pyld/__about__.py`: update to release version and remove `-dev` suffix.
+* `git commit CHANGELOG.md lib/pyld/__about__.py -m "Release {version}."`
+* `git tag {version}`
+* `$EDITOR lib/pyld/__about__.py`: update to next version and add `-dev` suffix.
+* `git commit lib/pyld/__about__.py -m "Start {next-version}."`
+* `git push --tags`
+
+To ensure a clean [package](https://pypi.org/project/PyLD/) upload to [PyPI](https://pypi.org/),
+use a clean checkout, and run the following:
+
+* For more info, look at the packaging
+ [guide](https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/).
+* Setup an [API token](https://pypi.org/help/#apitoken). Recommend using a
+ specific "PyLD" token and set it up as a "repository" in your
+ [`~/.pypirc`](https://packaging.python.org/en/latest/specifications/pypirc/)
+ for use in the upload command.
+* The below builds and uploads a sdist and wheel. Adjust as needed depending
+ on how you manage and clean "dist/" dir files.
+* `git checkout {version}`
+* `python3 -m build`
+* `twine check dist/*`
+* `twine upload -r PyLD dist/*`
+
+## Implementation Report Process
+
+As of early 2020, the process to generate an EARL report for the official
+[JSON-LD Processor Conformance](https://w3c.github.io/json-ld-api/reports/) page is:
+
+* Run the tests on the `json-ld-api` and `json-ld-framing` test repos to
+ generate a `.jsonld` test report as explained in [README.md](./README.md#tests)
+* Use the [rdf](https://rubygems.org/gems/rdf) tool to generate a `.ttl`:
+
+ * `rdf serialize pyld-earl.jsonld --output-format turtle -o pyld-earl.ttl`
+
+* Optionally follow the [report instructions](https://github.com/w3c/json-ld-api/tree/master/reports) to generate the HTML report for inspection.
+* Submit a PR to the [json-ld-api repository](https://github.com/w3c/json-ld-api/pulls) with at least the `.ttl`.
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
deleted file mode 100644
index 5053e549..00000000
--- a/CONTRIBUTING.rst
+++ /dev/null
@@ -1,76 +0,0 @@
-Contributing to PyLD
-====================
-
-Want to contribute to PyLD? Great! Here are a few notes:
-
-Code
-----
-
-* In general, follow the common `PEP 8 Style Guide`_.
-* Try to make the code pass ruff_ checks.
-
- * ``make lint`` or ``ruff check lib/pyld/*``
- * you can also apply automatic fixing and formatting
- using ``make fmt``
-
-* Use version X.Y.Z-dev in dev mode.
-* Use version X.Y.Z for releases.
-
-Versioning
-----------
-
-* Follow the `Semantic Versioning`_ guidelines.
-
-Release Process
----------------
-
-* ``$EDITOR CHANGELOG.md``: update CHANGELOG with new notes, version, and date.
-* commit changes
-* ``$EDITOR lib/pyld/__about__.py``: update to release version and remove ``-dev``
- suffix.
-* ``git commit CHANGELOG.md lib/pyld/__about__.py -m "Release {version}."``
-* ``git tag {version}``
-* ``$EDITOR lib/pyld/__about__.py``: update to next version and add ``-dev`` suffix.
-* ``git commit lib/pyld/__about__.py -m "Start {next-version}."``
-* ``git push --tags``
-
-To ensure a clean `package `_ upload to PyPI_,
-use a clean checkout, and run the following:
-
-* For more info, look at the packaging
- `guide `_.
-* Setup an `API token `_. Recommend using a
- specific "PyLD" token and set it up as a "repository" in your
- `~/.pypirc `_
- for use in the upload command.
-* The below builds and uploads a sdist and wheel. Adjust as needed depending
- on how you manage and clean "dist/" dir files.
-* ``git checkout {version}``
-* ``python3 -m build``
-* ``twine check dist/*``
-* ``twine upload -r PyLD dist/*``
-
-Implementation Report Process
------------------------------
-
-As of early 2020, the process to generate an EARL report for the official
-`JSON-LD Processor Conformance`_ page is:
-
-* Run the tests on the ``json-ld-api`` and ``json-ld-framing`` test repos to
- generate a ``.jsonld`` test report as explained in [README.rst](./README.rst#tests)
-* Use the rdf_ tool to generate a ``.ttl``:
-
- * ``rdf serialize pyld-earl.jsonld --output-format turtle -o pyld-earl.ttl``
-
-* Optionally follow the `report instructions`_ to generate the HTML report for
- inspection.
-* Submit a PR to the `json-ld-api repository`_ with at least the ``.ttl``:
-
-.. _JSON-LD Processor Conformance: https://w3c.github.io/json-ld-api/reports/
-.. _PEP 8 Style Guide: https://www.python.org/dev/peps/pep-0008/
-.. _Semantic Versioning: https://semver.org/
-.. _ruff: https://docs.astral.sh/ruff/
-.. _json-ld-api repository: https://github.com/w3c/json-ld-api/pulls
-.. _rdf: https://rubygems.org/gems/rdf
-.. _report instructions: https://github.com/w3c/json-ld-api/tree/master/reports
-.. _PyPI: https://pypi.org/
diff --git a/MANIFEST.in b/MANIFEST.in
index 8cd6fdad..96efd64f 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,2 @@
-include README.rst README.txt LICENSE CHANGELOG.md
+include README.md README.txt LICENSE CHANGELOG.md
recursive-include lib/pyld/documentloader/frozen/bundled *.jsonld
diff --git a/Makefile b/Makefile
index 28da5f38..e16887e0 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: install test upgrade-submodules download-bundled-contexts
+.PHONY: install test serve upgrade-submodules download-bundled-contexts
install:
pip install -e .
@@ -6,6 +6,9 @@ install:
test:
pytest --cov=pyld
+serve:
+ mkdocs serve --dev-addr 127.0.0.1:8008
+
upgrade-submodules:
git submodule update --remote --init --recursive
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..a4ead101
--- /dev/null
+++ b/README.md
@@ -0,0 +1,397 @@
+# PyLD
+
+## Introduction
+
+This library is an implementation of the JSON-LD specification in
+[Python](https://www.python.org/).
+
+JSON, as specified in [RFC7159](http://tools.ietf.org/html/rfc7159), is a simple
+language for representing objects on the Web. Linked Data is a way of describing
+content across different documents or Web sites. Web resources are described
+using IRIs, and typically are dereferencable entities that may be used to find
+more information, creating a "Web of Knowledge". [JSON-LD](https://json-ld.org/)
+is intended to be a simple publishing method for expressing not only Linked Data
+in JSON, but for adding semantics to existing JSON.
+
+JSON-LD is designed as a light-weight syntax that can be used to express Linked
+Data. It is primarily intended to be a way to express Linked Data in JavaScript
+and other Web-based programming environments. It is also useful when building
+interoperable Web Services and when storing Linked Data in JSON-based document
+storage engines. It is practical and designed to be as simple as possible,
+utilizing the large number of JSON parsers and existing code that is in use
+today. It is designed to be able to express key-value pairs, RDF data,
+[RDFa](http://www.w3.org/TR/rdfa-core/) data,
+[Microformats](http://microformats.org/) data, and
+[Microdata](http://www.w3.org/TR/microdata/). That is, it supports every major
+Web-based structured data model in use today.
+
+The syntax does not require many applications to change their JSON, but easily
+add meaning by adding context in a way that is either in-band or out-of-band.
+The syntax is designed to not disturb already deployed systems running on JSON,
+but provide a smooth migration path from JSON to JSON with added semantics.
+Finally, the format is intended to be fast to parse, fast to generate,
+stream-based and document-based processing compatible, and require a very small
+memory footprint in order to operate.
+
+## Conformance
+
+This library aims to conform with the following W3C Recommendations:
+
+| Standard | Status |
+| :--- | :--- |
+| [JSON-LD 1.1](https://www.w3.org/TR/json-ld11/) | W3C Recommendation |
+| [JSON-LD 1.1 Processing Algorithms and API](https://www.w3.org/TR/json-ld11-api/) | W3C Recommendation |
+| [JSON-LD 1.1 Framing](https://www.w3.org/TR/json-ld11-framing/) | W3C Recommendation |
+| [RDF Dataset Canonicalization](https://www.w3.org/TR/rdf-canon/) | W3C Recommendation |
+
+
+The [`test
+runner`](https://github.com/digitalbazaar/pyld/blob/master/tests/runtests.py) is
+often updated to note or skip newer tests that are not yet supported.
+
+## Requirements
+
+* Python (3.10 or later)
+* [Requests](http://docs.python-requests.org/) (optional)
+* [aiohttp](https://aiohttp.readthedocs.io/) (optional)
+
+## Installation
+
+PyLD can be installed with a [pip](http://www.pip-installer.org/)
+[package](https://pypi.org/project/PyLD/):
+
+```bash
+pip install PyLD
+```
+
+Defining a dependency on pyld will not pull in
+[Requests](http://docs.python-requests.org/) or
+[aiohttp](https://aiohttp.readthedocs.io/). If you need one of these for a
+[Document Loader](#document-loader) then either depend on the desired external library directly
+or define the requirement as `PyLD[requests]` or `PyLD[aiohttp]`.
+
+## Quick Examples
+
+```python
+from pyld import jsonld
+import json
+
+doc = {
+ "http://schema.org/name": "Manu Sporny",
+ "http://schema.org/url": {"@id": "http://manu.sporny.org/"},
+ "http://schema.org/image": {"@id": "http://manu.sporny.org/images/manu.png"}
+}
+
+context = {
+ "name": "http://schema.org/name",
+ "homepage": {"@id": "http://schema.org/url", "@type": "@id"},
+ "image": {"@id": "http://schema.org/image", "@type": "@id"}
+}
+
+# compact a document according to a particular context
+# see: https://json-ld.org/spec/latest/json-ld/#compacted-document-form
+compacted = jsonld.compact(doc, context)
+
+print(json.dumps(compacted, indent=2))
+# Output:
+# {
+# "@context": {...},
+# "image": "http://manu.sporny.org/images/manu.png",
+# "homepage": "http://manu.sporny.org/",
+# "name": "Manu Sporny"
+# }
+
+# compact using URLs
+jsonld.compact('http://example.org/doc', 'http://example.org/context')
+
+# expand a document, removing its context
+# see: https://json-ld.org/spec/latest/json-ld/#expanded-document-form
+expanded = jsonld.expand(compacted)
+
+print(json.dumps(expanded, indent=2))
+# Output:
+# [{
+# "http://schema.org/image": [{"@id": "http://manu.sporny.org/images/manu.png"}],
+# "http://schema.org/name": [{"@value": "Manu Sporny"}],
+# "http://schema.org/url": [{"@id": "http://manu.sporny.org/"}]
+# }]
+
+# expand using URLs
+jsonld.expand('http://example.org/doc')
+
+# flatten a document
+# see: https://json-ld.org/spec/latest/json-ld/#flattened-document-form
+flattened = jsonld.flatten(doc)
+# all deep-level trees flattened to the top-level
+
+# frame a document
+# see: https://json-ld.org/spec/latest/json-ld-framing/#introduction
+framed = jsonld.frame(doc, frame)
+# document transformed into a particular tree structure per the given frame
+
+# normalize a document using the RDF Dataset Normalization Algorithm
+# (URDNA2015), see: https://www.w3.org/TR/rdf-canon/
+normalized = jsonld.normalize(
+ doc, {'algorithm': 'URDNA2015', 'format': 'application/n-quads'})
+# normalized is a string that is a canonical representation of the document
+# that can be used for hashing, comparison, etc.
+```
+
+## Document Loader
+
+The default document loader for PyLD uses
+[Requests](http://docs.python-requests.org/). In a production environment you
+may want to setup a custom loader that, at a minimum, sets a timeout value. You
+can also force requests to use https, set client certs, disable verification, or
+set other Requests parameters.
+
+```python
+jsonld.set_document_loader(jsonld.requests_document_loader(timeout=...))
+```
+
+The factory remains the compatibility API, and the concrete class is also
+available when class-based construction is preferred:
+
+```python
+from pyld import RequestsDocumentLoader
+
+jsonld.set_document_loader(RequestsDocumentLoader(timeout=...))
+```
+
+An asynchronous document loader using aiohttp is also available. Please note
+that this document loader limits asynchronicity to fetching documents only. The
+processing loops remain synchronous.
+
+```python
+jsonld.set_document_loader(jsonld.aiohttp_document_loader(timeout=...))
+```
+
+The concrete aiohttp loader class is available from `pyld` as well:
+
+```python
+from pyld import AioHttpDocumentLoader
+
+jsonld.set_document_loader(AioHttpDocumentLoader(timeout=...))
+```
+
+When no document loader is specified, the default loader is set to
+[Requests](http://docs.python-requests.org/). If Requests is not available, the
+loader is set to aiohttp. The fallback document loader is a dummy document
+loader that raises an exception on every invocation.
+
+## Frozen Document Loader
+
+For air-gapped runs, reproducible builds, and security-hardened deployments that
+must not perform any remote context fetches at all, PyLD ships
+`FrozenDocumentLoader`: a class-based loader that serves only the URLs in its
+`documents` allowlist and refuses everything else with
+`JsonLdError(code='loading document failed')`.
+
+Instantiating with no arguments serves the curated `BUNDLED_CONTEXTS` set
+(ActivityStreams, DID v1, Verifiable Credentials v1 and v2, Linked Data Security
+v1/v2, Ed25519-2020, and JWS-2020). To extend the bundle with additional
+pre-vetted contexts, pass a merged mapping:
+
+```python
+from pyld import jsonld, FrozenDocumentLoader, BUNDLED_CONTEXTS
+
+loader = FrozenDocumentLoader(documents=dict(
+ BUNDLED_CONTEXTS,
+ **{'https://example.com/my-ctx': Path('contexts/my-ctx.jsonld')},
+))
+jsonld.expand(doc, options={'documentLoader': loader})
+```
+
+This honors the W3C *JSON-LD Best Practices* recommendation that clients SHOULD
+attempt to use a locally cached version of contexts (see
+[§ Cache JSON-LD Contexts](https://w3c.github.io/json-ld-bp/#cache-json-ld-contexts)).
+Refresh the bundled copies with `make download-bundled-contexts`.
+
+## Customizing the ContextLoader
+
+You can customize the way contexts are loaded and cached by passing an instance
+of `ContextResolver`. The following example implements a loader with a prefilled
+custom document cache and uses a custom LRU cache for resolved contexts:
+
+```python
+from pyld.jsonld import compact, expand, set_document_loader, ContextResolver
+import json
+from cachetools import LRUCache
+
+# Load the Linked Art context from file-system
+fh = open('linked-art.json')
+js = json.load(fh)
+fh.close()
+
+# Add to document cache
+docCache = {
+ "https://linked.art/ns/v1/linked-art.json": {
+ "contextUrl": None,
+ "documentUrl": "https://linked.art/ns/v1/linked-art.json",
+ "document": js
+ }
+}
+
+# Custom loader that uses the document cache
+def load_document_and_cache(url, options={}):
+ if url in docCache:
+ return docCache[url]
+ doc = {"contextUrl": None, "documentUrl": url, "document": ""}
+ resp = requests.get(url)
+ doc["document"] = resp.json()
+ docCache[url] = doc
+ return doc
+
+# Set the custom loader as global document loader
+set_document_loader(load_document_and_cache)
+# Create custom context resolver with custom LRU cache and custom loader
+resolved_context_cache = LRUCache(maxsize=1000)
+resolver = ContextResolver(resolved_context_cache, load_document_and_cache)
+
+# Expand JSON-LD document using custom context resolver
+input = {"@context":"https://linked.art/ns/v1/linked-art.json", "id": "tag:foo", "type": "Person"}
+output = expand(input, options={'contextResolver': resolver})
+```
+
+It is also possible to change the maximum number of times that the loader
+recursively fetches contexts, by passing the `max_context_urls` parameter:
+
+```python
+resolver = ContextResolver(resolved_context_cache, load_document_and_cache, max_context_urls=20)
+# Or you can do...
+# resolver = ContextResolver(resolved_context_cache, load_document_and_cache)
+# resolver.max_context_urls = 20
+output = expand(input, options={'contextResolver': resolver})
+```
+
+## Handling ignored properties during JSON-LD expansion
+
+If a property in a JSON-LD document does not map to an absolute IRI then it is
+ignored. You can customize this behaviour by passing a customizable handler to
+`on_property_dropped` parameter of `jsonld.expand()`.
+
+For example, you can introduce a strict mode by raising a ValueError on every
+dropped property:
+
+```python
+def raise_this(value):
+ raise ValueError(value)
+
+jsonld.expand(doc, None, on_property_dropped=raise_this)
+```
+
+## Commercial Support
+
+Commercial support for this library is available upon request from [`Digital
+Bazaar`](mailto:support@digitalbazaar.com).
+
+## Source
+
+The source code for the Python implementation of the JSON-LD API is available
+at:
+
+[https://github.com/digitalbazaar/pyld](https://github.com/digitalbazaar/pyld)
+
+## Tests
+
+This library includes a sample testing utility which may be used to verify that
+changes to the processor maintain the correct output.
+
+To run the sample tests you will need to get the test suite files, which by
+default, are stored in the `specifications/` folder. The test suites can be
+obtained by either using git submodules or by cloning them manually.
+
+### Using git submodules
+
+The test suites are included as git submodules to ensure versions are in sync.
+When cloning the repository, use the `--recurse-submodules` flag to
+automatically clone the submodules. If you have cloned the repository without
+the submodules, you can initialize them with the following commands:
+
+```bash
+git submodule init
+git submodule update
+```
+
+### Cloning manually
+
+You can also avoid using git submodules by manually cloning the `json-ld-api`,
+`json-ld-framing`, and `normalization` repositories hosted on GitHub using the
+following commands:
+
+```bash
+git clone https://github.com/w3c/json-ld-api ./specifications/json-ld-api
+git clone https://github.com/w3c/json-ld-framing ./specifications/json-ld-framing
+git clone https://github.com/json-ld/normalization ./specifications/normalization
+```
+
+Note that you can clone these repositories into any location you wish; however,
+if you do not clone them into the default `specifications/` folder, you will
+need to provide the paths to the test runner as arguments when running the
+tests, as explained below.
+
+### Running the sample test suites and unit tests using pytest
+
+If the suites repositories are available in the `specifications/` folder of the
+PyLD source directory, then all unittests, including the sample test suites, can
+be run with `pytest`:
+
+```bash
+pytest
+```
+
+If you wish to store the test suites in a different location than the default
+`specifications/` folder, or you want to test individual manifest `.jsonld`
+files or directories containing a `manifest.jsonld`, then you can supply these
+files or directories as arguments:
+
+```bash
+# use: pytest --tests=TEST_PATH [--tests=TEST_PATH...]
+pytest --tests=./specifications/json-ld-api/tests
+```
+
+The test runner supports different document loaders by setting `--loader
+requests` or `--loader aiohttp`. The default document loader is set to
+[Requests](http://docs.python-requests.org/).
+
+```bash
+pytest --loader=requests --tests=./specifications/json-ld-api/tests
+```
+
+An EARL report can be generated using the `--earl` option.
+
+```bash
+pytest --earl=./earl-report.json
+```
+
+### Running the sample test suites using the original test runner
+
+You can also run the JSON-LD test suites using the original test runner script
+provided:
+
+```bash
+python tests/runtests.py
+```
+
+If you wish to store the test suites in a different location than the default
+`specifications/` folder, or you want to test individual manifest `.jsonld`
+files or directories containing a `manifest.jsonld`, then you can supply these
+files or directories as arguments:
+
+```bash
+python tests/runtests.py TEST_PATH [TEST_PATH...]
+```
+
+The test runner supports different document loaders by setting `-l requests` or
+`-l aiohttp`. The default document loader is set to
+[Requests](http://docs.python-requests.org/).
+
+```bash
+python tests/runtests.py -l requests ./specifications/json-ld-api/tests
+```
+
+An EARL report can be generated using the `-e` or `--earl` option.
+
+```bash
+python tests/runtests.py -e ./earl-report.json
+```
\ No newline at end of file
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 1fe27f40..00000000
--- a/README.rst
+++ /dev/null
@@ -1,446 +0,0 @@
-PyLD
-====
-
-.. image:: https://travis-ci.org/digitalbazaar/pyld.png?branch=master
- :target: https://travis-ci.org/digitalbazaar/pyld
- :alt: Build Status
-
-Introduction
-------------
-
-This library is an implementation of the JSON-LD_ specification in Python_.
-
-JSON, as specified in RFC7159_, is a simple language for representing
-objects on the Web. Linked Data is a way of describing content across
-different documents or Web sites. Web resources are described using
-IRIs, and typically are dereferencable entities that may be used to find
-more information, creating a "Web of Knowledge". JSON-LD_ is intended
-to be a simple publishing method for expressing not only Linked Data in
-JSON, but for adding semantics to existing JSON.
-
-JSON-LD is designed as a light-weight syntax that can be used to express
-Linked Data. It is primarily intended to be a way to express Linked Data
-in JavaScript and other Web-based programming environments. It is also
-useful when building interoperable Web Services and when storing Linked
-Data in JSON-based document storage engines. It is practical and
-designed to be as simple as possible, utilizing the large number of JSON
-parsers and existing code that is in use today. It is designed to be
-able to express key-value pairs, RDF data, RDFa_ data,
-Microformats_ data, and Microdata_. That is, it supports every
-major Web-based structured data model in use today.
-
-The syntax does not require many applications to change their JSON, but
-easily add meaning by adding context in a way that is either in-band or
-out-of-band. The syntax is designed to not disturb already deployed
-systems running on JSON, but provide a smooth migration path from JSON
-to JSON with added semantics. Finally, the format is intended to be fast
-to parse, fast to generate, stream-based and document-based processing
-compatible, and require a very small memory footprint in order to operate.
-
-Conformance
------------
-
-This library aims to conform with the following:
-
-- `JSON-LD 1.1 `_,
- W3C Candidate Recommendation,
- 2019-12-12 or `newer `_
-- `JSON-LD 1.1 Processing Algorithms and API `_,
- W3C Candidate Recommendation,
- 2019-12-12 or `newer `_
-- `JSON-LD 1.1 Framing `_,
- W3C Candidate Recommendation,
- 2019-12-12 or `newer `_
-- Working Group `test suite `_
-
-The `test runner`_ is often updated to note or skip newer tests that are not
-yet supported.
-
-Requirements
-------------
-
-- Python_ (3.10 or later)
-- Requests_ (optional)
-- aiohttp_ (optional, Python 3.5 or later)
-
-Installation
-------------
-
-PyLD can be installed with a pip_ `package `_
-
-.. code-block:: bash
-
- pip install PyLD
-
-Defining a dependency on pyld will not pull in Requests_ or aiohttp_. If you
-need one of these for a `Document Loader`_ then either depend on the desired
-external library directly or define the requirement as ``PyLD[requests]`` or
-``PyLD[aiohttp]``.
-
-Quick Examples
---------------
-
-.. code-block:: Python
-
- from pyld import jsonld
- import json
-
- doc = {
- "http://schema.org/name": "Manu Sporny",
- "http://schema.org/url": {"@id": "http://manu.sporny.org/"},
- "http://schema.org/image": {"@id": "http://manu.sporny.org/images/manu.png"}
- }
-
- context = {
- "name": "http://schema.org/name",
- "homepage": {"@id": "http://schema.org/url", "@type": "@id"},
- "image": {"@id": "http://schema.org/image", "@type": "@id"}
- }
-
- # compact a document according to a particular context
- # see: https://json-ld.org/spec/latest/json-ld/#compacted-document-form
- compacted = jsonld.compact(doc, context)
-
- print(json.dumps(compacted, indent=2))
- # Output:
- # {
- # "@context": {...},
- # "image": "http://manu.sporny.org/images/manu.png",
- # "homepage": "http://manu.sporny.org/",
- # "name": "Manu Sporny"
- # }
-
- # compact using URLs
- jsonld.compact('http://example.org/doc', 'http://example.org/context')
-
- # expand a document, removing its context
- # see: https://json-ld.org/spec/latest/json-ld/#expanded-document-form
- expanded = jsonld.expand(compacted)
-
- print(json.dumps(expanded, indent=2))
- # Output:
- # [{
- # "http://schema.org/image": [{"@id": "http://manu.sporny.org/images/manu.png"}],
- # "http://schema.org/name": [{"@value": "Manu Sporny"}],
- # "http://schema.org/url": [{"@id": "http://manu.sporny.org/"}]
- # }]
-
- # expand using URLs
- jsonld.expand('http://example.org/doc')
-
- # flatten a document
- # see: https://json-ld.org/spec/latest/json-ld/#flattened-document-form
- flattened = jsonld.flatten(doc)
- # all deep-level trees flattened to the top-level
-
- # frame a document
- # see: https://json-ld.org/spec/latest/json-ld-framing/#introduction
- framed = jsonld.frame(doc, frame)
- # document transformed into a particular tree structure per the given frame
-
- # normalize a document using the RDF Dataset Normalization Algorithm
- # (URDNA2015), see: https://www.w3.org/TR/rdf-canon/
- normalized = jsonld.normalize(
- doc, {'algorithm': 'URDNA2015', 'format': 'application/n-quads'})
- # normalized is a string that is a canonical representation of the document
- # that can be used for hashing, comparison, etc.
-
-Document Loader
----------------
-
-The default document loader for PyLD uses Requests_. In a production
-environment you may want to setup a custom loader that, at a minimum, sets a
-timeout value. You can also force requests to use https, set client certs,
-disable verification, or set other Requests_ parameters.
-
-.. code-block:: Python
-
- jsonld.set_document_loader(jsonld.requests_document_loader(timeout=...))
-
-The factory remains the compatibility API, and the concrete class is also
-available when class-based construction is preferred:
-
-.. code-block:: Python
-
- from pyld import RequestsDocumentLoader
-
- jsonld.set_document_loader(RequestsDocumentLoader(timeout=...))
-
-An asynchronous document loader using aiohttp_ is also available. Please note
-that this document loader limits asynchronicity to fetching documents only.
-The processing loops remain synchronous.
-
-.. code-block:: Python
-
- jsonld.set_document_loader(jsonld.aiohttp_document_loader(timeout=...))
-
-The concrete aiohttp loader class is available from ``pyld`` as well:
-
-.. code-block:: Python
-
- from pyld import AioHttpDocumentLoader
-
- jsonld.set_document_loader(AioHttpDocumentLoader(timeout=...))
-
-When no document loader is specified, the default loader is set to Requests_.
-If Requests_ is not available, the loader is set to aiohttp_. The fallback
-document loader is a dummy document loader that raises an exception on every
-invocation.
-
-Frozen Document Loader
-######################
-
-For air-gapped runs, reproducible builds, and security-hardened deployments
-that must not perform any remote context fetches at all, PyLD ships
-``FrozenDocumentLoader``: a class-based loader that serves only the URLs in
-its ``documents`` allowlist and refuses everything else with
-``JsonLdError(code='loading document failed')``.
-
-Instantiating with no arguments serves the curated ``BUNDLED_CONTEXTS`` set
-(ActivityStreams, DID v1, Verifiable Credentials v1 and v2, Linked Data
-Security v1/v2, Ed25519-2020, and JWS-2020). To extend the bundle with
-additional pre-vetted contexts, pass a merged mapping:
-
-.. code-block:: Python
-
- from pyld import jsonld, FrozenDocumentLoader, BUNDLED_CONTEXTS
-
- loader = FrozenDocumentLoader(documents=dict(
- BUNDLED_CONTEXTS,
- **{'https://example.com/my-ctx': Path('contexts/my-ctx.jsonld')},
- ))
- jsonld.expand(doc, options={'documentLoader': loader})
-
-This honors the W3C *JSON-LD Best Practices* recommendation that clients
-SHOULD attempt to use a locally cached version of contexts (see
-`§ Cache JSON-LD Contexts `_).
-Refresh the bundled copies with ``make download-bundled-contexts``.
-
-Customizing the ContextLoader
------------------------------
-
-You can customize the way contexts are loaded and cached by passing an instance
-of ``ContextResolver``. The following example implements a loader with a
-prefilled custom document cache and uses a custom LRU cache for resolved
-contexts:
-
-.. code-block:: Python
-
- from pyld.jsonld import compact, expand, set_document_loader, ContextResolver
- import json
- from cachetools import LRUCache
-
- # Load the Linked Art context from file-system
- fh = open('linked-art.json')
- js = json.load(fh)
- fh.close()
-
- # Add to document cache
- docCache = {
- "https://linked.art/ns/v1/linked-art.json": {
- "contextUrl": None,
- "documentUrl": "https://linked.art/ns/v1/linked-art.json",
- "document": js
- }
- }
-
- # Custom loader that uses the document cache
- def load_document_and_cache(url, options={}):
- if url in docCache:
- return docCache[url]
- doc = {"contextUrl": None, "documentUrl": url, "document": ""}
- resp = requests.get(url)
- doc["document"] = resp.json()
- docCache[url] = doc
- return doc
-
- # Set the custom loader as global document loader
- set_document_loader(load_document_and_cache)
- # Create custom context resolver with custom LRU cache and custom loader
- resolved_context_cache = LRUCache(maxsize=1000)
- resolver = ContextResolver(resolved_context_cache, load_document_and_cache)
-
- # Expand JSON-LD document using custom context resolver
- input = {"@context":"https://linked.art/ns/v1/linked-art.json", "id": "tag:foo", "type": "Person"}
- output = expand(input, options={'contextResolver': resolver})
-
-
-It is also possible to change the maximum number of times that the loader recusively fetches contexts,
-by passing the ``max_context_urls`` parameter:
-
-.. code-block:: Python
-
- resolver = ContextResolver(resolved_context_cache, load_document_and_cache, max_context_urls=20)
- # Or you can do...
- # resolver = ContextResolver(resolved_context_cache, load_document_and_cache)
- # resolver.max_context_urls = 20
- output = expand(input, options={'contextResolver': resolver})
-
-
-Handling ignored properties during JSON-LD expansion
-----------------------------------------------------
-
-If a property in a JSON-LD document does not map to an absolute IRI then it is
-ignored. You can customize this behaviour by passing a customizable handler to
-`on_property_dropped` parameter of `jsonld.expand()`.
-
-For example, you can introduce a strict mode by raising a ValueError on every
-dropped property:
-
-.. code-block:: Python
-
- def raise_this(value):
- raise ValueError(value)
-
- jsonld.expand(doc, None, on_property_dropped=raise_this)
-
-Commercial Support
-------------------
-
-Commercial support for this library is available upon request from
-`Digital Bazaar`_: support@digitalbazaar.com.
-
-Source
-------
-
-The source code for the Python implementation of the JSON-LD API
-is available at:
-
-https://github.com/digitalbazaar/pyld
-
-Tests
------
-
-This library includes a sample testing utility which may be used to verify
-that changes to the processor maintain the correct output.
-
-To run the sample tests you will need to get the test suite files, which
-by default, are stored in the `specifications/` folder.
-The test suites can be obtained by either using git submodules or by cloning
-them manually.
-
-Using git submodules
-####################
-
-The test suites are included as git submodules to ensure versions are in sync.
-When cloning the repository, use the ``--recurse-submodules`` flag to
-automatically clone the submodules.
-If you have cloned the repository without the submodules, you can initialize
-them with the following commands:
-
-.. code-block:: bash
-
- git submodule init
- git submodule update
-
-Cloning manually
-####################
-
-You can also avoid using git submodules by manually cloning
-the ``json-ld-api``, ``json-ld-framing``, and ``normalization`` repositories
-hosted on GitHub using the following commands:
-
-.. code-block:: bash
-
- git clone https://github.com/w3c/json-ld-api ./specifications/json-ld-api
- git clone https://github.com/w3c/json-ld-framing ./specifications/json-ld-framing
- git clone https://github.com/json-ld/normalization ./specifications/normalization
-
-Note that you can clone these repositories into any location you wish; however,
-if you do not clone them into the default ``specifications/`` folder, you will
-need to provide the paths to the test runner as arguments when running the
-tests, as explained below
-
-Running the sample test suites and unittests using pytest
-#########################################################
-
-If the suites repositories are available in the `specifications/` folder of the
-PyLD source directory, then all unittests, including the sample test suites,
-can be run with `pytest`:
-
-.. code-block:: bash
-
- pytest
-
-If you wish to store the test suites in a different location than the default
-``specifications/`` folder, or you want to test individual manifest ``.jsonld``
-files or directories containing a ``manifest.jsonld``, then you can supply
-these files or directories as arguments:
-
-.. code-block:: bash
-
- # use: pytest --tests=TEST_PATH [--tests=TEST_PATH...]
- pytest --tests=./specifications/json-ld-api/tests
-
-The test runner supports different document loaders by setting
-``--loader requests`` or ``--loader aiohttp``. The default document loader is
-set to Requests_.
-
-.. code-block:: bash
-
- pytest --loader=requests --tests=./specifications/json-ld-api/tests
-
-An EARL report can be generated using the ``--earl`` option.
-
-.. code-block:: bash
-
- pytest --earl=./earl-report.json
-
-Running the sample test suites using the original test runner
-#############################################################
-
-You can also run the JSON-LD test suites using the original test runner script
-provided:
-
-.. code-block:: bash
-
- python tests/runtests.py
-
-If you wish to store the test suites in a different location than the default
-``specifications/`` folder, or you want to test individual manifest ``.jsonld``
-files or directories containing a ``manifest.jsonld``, then you can supply
-these files or directories as arguments:
-
-.. code-block:: bash
-
- python tests/runtests.py TEST_PATH [TEST_PATH...]
-
-The test runner supports different document loaders by setting ``-l requests``
-or ``-l aiohttp``. The default document loader is set to Requests_.
-
-.. code-block:: bash
-
- python tests/runtests.py -l requests ./specifications/json-ld-api/tests
-
-An EARL report can be generated using the ``-e`` or ``--earl`` option.
-
-.. code-block:: bash
-
- python tests/runtests.py -e ./earl-report.json
-
-
-.. _Digital Bazaar: https://digitalbazaar.com/
-
-.. _JSON-LD WG 1.1 API: https://www.w3.org/TR/json-ld11-api/
-.. _JSON-LD WG 1.1 Framing: https://www.w3.org/TR/json-ld11-framing/
-.. _JSON-LD WG 1.1: https://www.w3.org/TR/json-ld11/
-
-.. _JSON-LD WG API latest: https://w3c.github.io/json-ld-api/
-.. _JSON-LD WG Framing latest: https://w3c.github.io/json-ld-framing/
-.. _JSON-LD WG latest: https://w3c.github.io/json-ld-syntax/
-
-.. _JSON-LD Benchmarks: https://json-ld.org/benchmarks/
-.. _JSON-LD WG: https://www.w3.org/2018/json-ld-wg/
-.. _JSON-LD: https://json-ld.org/
-.. _Microdata: http://www.w3.org/TR/microdata/
-.. _Microformats: http://microformats.org/
-.. _Python: https://www.python.org/
-.. _Requests: http://docs.python-requests.org/
-.. _aiohttp: https://aiohttp.readthedocs.io/
-.. _RDFa: http://www.w3.org/TR/rdfa-core/
-.. _RFC7159: http://tools.ietf.org/html/rfc7159
-.. _WG test suite: https://github.com/w3c/json-ld-api/tree/master/tests
-.. _errata: http://www.w3.org/2014/json-ld-errata
-.. _pip: http://www.pip-installer.org/
-.. _test runner: https://github.com/digitalbazaar/pyld/blob/master/tests/runtests.py
-.. _test suite: https://github.com/json-ld/json-ld.org/tree/master/test-suite
diff --git a/README.txt b/README.txt
index 92cacd28..42061c01 120000
--- a/README.txt
+++ b/README.txt
@@ -1 +1 @@
-README.rst
\ No newline at end of file
+README.md
\ No newline at end of file
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index 88452c53..00000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,136 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = _build
-HOST = 127.0.0.1
-PORT = 8000
-
-# Internal variables.
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html dirhtml singlehtml serve pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
-
-help:
- @echo "Please use \`make ' where is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " singlehtml to make a single large HTML file"
- @echo " serve to serve HTML files with auto rebuild"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " devhelp to make HTML files and a Devhelp project"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " latexpdf to make LaTeX files and run them through pdflatex"
- @echo " text to make text files"
- @echo " man to make manual pages"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
-
-clean:
- -rm -rf $(BUILDDIR)/*
-
-html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-serve:
- sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html --host $(HOST) --port $(PORT)
-
-pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
-
-json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyLD.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyLD.qhc"
-
-devhelp:
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
- @echo
- @echo "Build finished."
- @echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/PyLD"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyLD"
- @echo "# devhelp"
-
-epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through pdflatex..."
- make -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/api-reference.md b/docs/api-reference.md
new file mode 100644
index 00000000..6b802e99
--- /dev/null
+++ b/docs/api-reference.md
@@ -0,0 +1,135 @@
+# API Reference
+
+This page lists the public APIs that are intended for direct user code. PyLD
+also contains internal processor classes and helper functions that are not
+documented here.
+
+## Module `pyld.jsonld`
+
+### `compact(input_, ctx, options=None)`
+
+Compact a JSON-LD document using the provided context.
+
+### `expand(input_, options=None, on_property_dropped=noop)`
+
+Expand a JSON-LD document, removing context aliases and producing expanded
+JSON-LD form. `on_property_dropped` can be used to observe or reject properties
+that do not expand to absolute IRIs.
+
+### `flatten(input_, ctx=None, options=None)`
+
+Flatten a JSON-LD document. If `ctx` is supplied, compact the flattened output
+with that context.
+
+### `frame(input_, frame, options=None)`
+
+Frame a JSON-LD document according to the supplied frame.
+
+### `link(input_, ctx, options=None)`
+
+Experimentally link a JSON-LD document's nodes in memory. This is equivalent to
+framing with `@embed: @link`.
+
+### `normalize(input_, options=None)`
+
+Normalize a JSON-LD document. Common options include `algorithm` and `format`.
+Use `{"algorithm": "URDNA2015", "format": "application/n-quads"}` to produce
+canonical N-Quads.
+
+### `from_rdf(input_, options=None)`
+
+Convert RDF input to JSON-LD.
+
+### `to_rdf(input_, options=None)`
+
+Convert JSON-LD input to RDF dataset form.
+
+### `set_document_loader(load_document_)`
+
+Set the global document loader callable.
+
+### `get_document_loader()`
+
+Return the current global document loader callable.
+
+### `load_document(url, options, base=None, profile=None, request_profile=None)`
+
+Load a remote document using the configured or supplied document loader.
+
+### `requests_document_loader(**kwargs)`
+
+Create a `requests`-based document loader. Pass `secure=True` to require HTTPS.
+Other keyword arguments are forwarded to `requests.get()`.
+
+### `aiohttp_document_loader(**kwargs)`
+
+Create an `aiohttp`-based document loader. Pass `secure=True` to require HTTPS.
+Other keyword arguments are forwarded to `aiohttp` request calls.
+
+### `register_rdf_parser(content_type, parser)`
+
+Register an RDF parser for a content type.
+
+### `unregister_rdf_parser(content_type)`
+
+Remove a registered RDF parser for a content type.
+
+### `parse_link_header(header)`
+
+Parse an HTTP `Link` header.
+
+### `JsonLdProcessor`
+
+Processor class behind the module-level convenience functions. Most callers use
+the module-level functions directly.
+
+### `JsonLdError`
+
+Exception type raised for JSON-LD processing and loading errors.
+
+### `ContextResolver`
+
+Context resolver that can be supplied in operation options for custom context
+loading and caching behavior.
+
+### `freeze(value)`
+
+Return an immutable mapping for dictionary values. This is used by PyLD's
+context caches.
+
+## Top-Level Exports
+
+### `jsonld`
+
+The main JSON-LD processing module.
+
+### `DocumentLoader`
+
+Abstract base class for class-based document loaders. PyLD still accepts any
+callable with the loader signature.
+
+### `RemoteDocument`
+
+Typed mapping shape returned by document loaders.
+
+### `RequestsDocumentLoader`
+
+Class-based remote document loader implemented with `requests`.
+
+### `AioHttpDocumentLoader`
+
+Class-based remote document loader implemented with `aiohttp`.
+
+### `FrozenDocumentLoader`
+
+Allowlist-only document loader for deployments that must not fetch arbitrary
+remote contexts.
+
+### `BUNDLED_CONTEXTS`
+
+Mapping of selected common JSON-LD context URLs to bundled local context files.
+
+### `ContextResolver`
+
+Context resolver that can be supplied in operation options for custom context
+loading and caching behavior.
diff --git a/docs/conf.py b/docs/conf.py
deleted file mode 100644
index c4bf2b85..00000000
--- a/docs/conf.py
+++ /dev/null
@@ -1,223 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# PyLD documentation build configuration file, created by
-# sphinx-quickstart on Mon Aug 29 15:25:28 2011.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys, os
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-current_path = os.path.abspath(os.path.dirname(__file__))
-path = os.path.join(current_path, '..')
-
-sys.path[0:0] = [
- os.path.join(path, 'lib'),
-]
-
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'PyLD'
-copyright = u'2011, Digital Bazaar'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = '0.0'
-# The full version, including alpha/beta/rc tags.
-release = '0.0.1'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'default'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# " v documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'PyLDdoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
- ('index', 'PyLD.tex', u'PyLD Documentation',
- u'Digital Bazaar', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'pyld', u'PyLD Documentation',
- [u'Digital Bazaar'], 1)
-]
diff --git a/docs/document-loaders.md b/docs/document-loaders.md
new file mode 100644
index 00000000..70c01b90
--- /dev/null
+++ b/docs/document-loaders.md
@@ -0,0 +1,57 @@
+# Document Loaders
+
+Document loaders retrieve remote JSON-LD documents and contexts. PyLD accepts
+any callable with the loader signature, and also ships class-based loaders for
+common cases.
+
+## Built-In Loader Classes
+
+- [RequestsDocumentLoader](document-loaders/requests.md) uses `requests` for
+ synchronous remote document loading.
+- [AioHttpDocumentLoader](document-loaders/aiohttp.md) uses `aiohttp` for
+ asynchronous fetching while keeping JSON-LD processing synchronous.
+- [FrozenDocumentLoader](document-loaders/frozen.md) serves only documents from
+ an allowlist.
+
+The default document loader is selected at import time. PyLD uses
+`RequestsDocumentLoader` if `requests` is available, falls back to
+`AioHttpDocumentLoader` if `aiohttp` is available, and otherwise installs a
+dummy loader that raises when invoked.
+
+## Custom Loaders
+
+A custom loader returns a remote document mapping with `contextUrl`,
+`documentUrl`, and `document` keys:
+
+```python
+from pyld import jsonld
+
+document_cache = {
+ "https://example.com/context": {
+ "contextUrl": None,
+ "documentUrl": "https://example.com/context",
+ "document": {"@context": {"name": "https://schema.org/name"}},
+ }
+}
+
+
+def load_document(url, options=None):
+ return document_cache[url]
+
+
+jsonld.set_document_loader(load_document)
+```
+
+For advanced context caching, pass a `ContextResolver` in operation options:
+
+```python
+from cachetools import LRUCache
+from pyld import ContextResolver, jsonld
+
+resolver = ContextResolver(LRUCache(maxsize=1000), load_document)
+
+expanded = jsonld.expand(
+ doc,
+ options={"contextResolver": resolver},
+)
+```
diff --git a/docs/document-loaders/aiohttp.md b/docs/document-loaders/aiohttp.md
new file mode 100644
index 00000000..186d818f
--- /dev/null
+++ b/docs/document-loaders/aiohttp.md
@@ -0,0 +1,44 @@
+# AioHttpDocumentLoader
+
+`AioHttpDocumentLoader` retrieves JSON-LD documents with `aiohttp`.
+
+```python
+from pyld import jsonld
+
+jsonld.set_document_loader(jsonld.aiohttp_document_loader(timeout=10))
+```
+
+This loader uses asynchronous fetching internally, but JSON-LD processing itself
+remains synchronous.
+
+The concrete loader class is exported from `pyld`:
+
+```python
+from pyld import AioHttpDocumentLoader, jsonld
+
+jsonld.set_document_loader(AioHttpDocumentLoader(timeout=10))
+```
+
+Use `secure=True` to require HTTPS URLs:
+
+```python
+jsonld.set_document_loader(
+ jsonld.aiohttp_document_loader(secure=True, timeout=10)
+)
+```
+
+Extra keyword arguments are forwarded to `aiohttp` request calls:
+
+```python
+from pyld import AioHttpDocumentLoader, jsonld
+
+loader = AioHttpDocumentLoader(timeout=10)
+
+jsonld.set_document_loader(loader)
+```
+
+Install the optional dependency with:
+
+```bash
+pip install "PyLD[aiohttp]"
+```
diff --git a/docs/document-loaders/frozen.md b/docs/document-loaders/frozen.md
new file mode 100644
index 00000000..7b307c19
--- /dev/null
+++ b/docs/document-loaders/frozen.md
@@ -0,0 +1,41 @@
+# FrozenDocumentLoader
+
+`FrozenDocumentLoader` serves only URLs in an allowlist and refuses all other
+document loads. It is intended for air-gapped runs, reproducible builds, and
+deployments that must avoid remote context fetching.
+
+With no arguments, the loader serves the curated `BUNDLED_CONTEXTS` mapping:
+
+```python
+from pyld import FrozenDocumentLoader, jsonld
+
+jsonld.set_document_loader(FrozenDocumentLoader())
+```
+
+## Bundled Contexts
+
+{{ bundled_contexts_table() }}
+
+Extend the bundled mapping with additional vetted contexts:
+
+```python
+from pathlib import Path
+
+from pyld import BUNDLED_CONTEXTS, FrozenDocumentLoader, jsonld
+
+loader = FrozenDocumentLoader(
+ documents=dict(
+ BUNDLED_CONTEXTS,
+ **{"https://example.com/context": Path("contexts/example.jsonld")},
+ )
+)
+
+jsonld.expand(doc, options={"documentLoader": loader})
+```
+
+The `documents` mapping may contain parsed JSON-LD dictionaries or
+`pathlib.Path` instances pointing to JSON files. Path entries are read lazily
+and cached after the first request.
+
+Any URL outside the allowlist raises `JsonLdError` with code
+`loading document failed`.
diff --git a/docs/document-loaders/requests.md b/docs/document-loaders/requests.md
new file mode 100644
index 00000000..0ab4118f
--- /dev/null
+++ b/docs/document-loaders/requests.md
@@ -0,0 +1,48 @@
+# RequestsDocumentLoader
+
+`RequestsDocumentLoader` retrieves JSON-LD documents with `requests`.
+
+The default remote document loader uses `requests` when it is available.
+Production applications should usually set at least a timeout:
+
+```python
+from pyld import jsonld
+
+jsonld.set_document_loader(jsonld.requests_document_loader(timeout=10))
+```
+
+The concrete loader class is exported from `pyld`:
+
+```python
+from pyld import RequestsDocumentLoader, jsonld
+
+jsonld.set_document_loader(RequestsDocumentLoader(timeout=10))
+```
+
+Use `secure=True` to require HTTPS URLs:
+
+```python
+jsonld.set_document_loader(
+ jsonld.requests_document_loader(secure=True, timeout=10)
+)
+```
+
+Extra keyword arguments are forwarded to `requests.get()`:
+
+```python
+from pyld import RequestsDocumentLoader, jsonld
+
+loader = RequestsDocumentLoader(
+ timeout=10,
+ verify=True,
+ cert=("client.crt", "client.key"),
+)
+
+jsonld.set_document_loader(loader)
+```
+
+Install the optional dependency with:
+
+```bash
+pip install "PyLD[requests]"
+```
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..a083ad7c
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,40 @@
+# PyLD
+
+PyLD is a Python implementation of the [JSON-LD][] processor API.
+
+JSON-LD is a lightweight syntax for expressing Linked Data in JSON. It lets
+applications add meaning to existing JSON documents with in-band or out-of-band
+contexts, while keeping the document shape practical for web APIs, JavaScript,
+and JSON document stores.
+
+## Conformance
+
+PyLD aims to conform with:
+
+- [JSON-LD 1.1][json-ld-11]
+- [JSON-LD 1.1 Processing Algorithms and API][json-ld-11-api]
+- [JSON-LD 1.1 Framing][json-ld-11-framing]
+- The JSON-LD Working Group [test suite][wg-test-suite]
+
+The test runner is updated over time to note or skip newer tests that are not
+yet supported.
+
+## Requirements
+
+- Python 3.10 or later
+- `requests` for the default synchronous document loader, when installed with
+ the `requests` extra
+- `aiohttp` for the asynchronous document loader, when installed with the
+ `aiohttp` extra
+
+## Project Links
+
+- [Source code](https://github.com/digitalbazaar/pyld)
+- [Package on PyPI](https://pypi.org/project/PyLD/)
+- [JSON-LD](https://json-ld.org/)
+
+[JSON-LD]: https://json-ld.org/
+[json-ld-11]: https://www.w3.org/TR/json-ld11/
+[json-ld-11-api]: https://www.w3.org/TR/json-ld11-api/
+[json-ld-11-framing]: https://www.w3.org/TR/json-ld11-framing/
+[wg-test-suite]: https://github.com/w3c/json-ld-api/tree/master/tests
diff --git a/docs/index.rst b/docs/index.rst
deleted file mode 100644
index 89b11570..00000000
--- a/docs/index.rst
+++ /dev/null
@@ -1,68 +0,0 @@
-.. PyLD documentation master file, created by
- sphinx-quickstart on Mon Aug 29 15:25:28 2011.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
-
-Welcome to PyLD!
-================
-
-PyLD is a `Python`_ implementation of a `JSON-LD`_ processor.
-
-
-API Reference
--------------
-
-.. toctree::
- :maxdepth: 2
-
-.. module:: pyld.jsonld
-.. autofunction:: compact
-.. autofunction:: expand
-.. autofunction:: flatten
-.. autofunction:: frame
-.. autofunction:: normalize
-
-.. module:: pyld
-.. autoclass:: DocumentLoader
- :members:
-.. autoclass:: RequestsDocumentLoader
- :members:
-.. autoclass:: AioHttpDocumentLoader
- :members:
-.. autoclass:: FrozenDocumentLoader
- :members:
-.. autodata:: BUNDLED_CONTEXTS
-
-Indices and tables
-------------------
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
-Requirements
-------------
-
-PyLD is compatible with `Python`_ 2.5 and newer.
-
-Credits
--------
-
-Thanks to `Digital Bazaar`_, the JavaScript JSON-LD parser, and the `JSON-LD`_ community.
-
-Contribute
-----------
-
-Source code is available:
-
- https://github.com/digitalbazaar/pyld
-
-License
--------
-
-PyLD is licensed under a `BSD 3-Clause license`_.
-
-.. _JSON-LD: https://json-ld.org/
-.. _Digital Bazaar: https://digitalbazaar.com/
-.. _Python: https://www.python.org/
-.. _BSD 3-Clause License: https://opensource.org/licenses/BSD-3-Clause
diff --git a/docs/installation.md b/docs/installation.md
new file mode 100644
index 00000000..57b3cce2
--- /dev/null
+++ b/docs/installation.md
@@ -0,0 +1,41 @@
+# Installation
+
+Install PyLD from PyPI:
+
+```bash
+pip install PyLD
+```
+
+PyLD's core package does not install `requests` or `aiohttp` automatically. If
+your application needs one of the built-in remote document loaders, install the
+matching extra:
+
+```bash
+pip install "PyLD[requests]"
+pip install "PyLD[aiohttp]"
+```
+
+You can also depend on `requests` or `aiohttp` directly if your project already
+manages those dependencies.
+
+## Development Install
+
+From a local checkout:
+
+```bash
+pip install -e .
+```
+
+Run the project tests with:
+
+```bash
+pytest
+```
+
+The JSON-LD specification test suites are stored under `specifications/` and are
+usually initialized as git submodules:
+
+```bash
+git submodule init
+git submodule update
+```
diff --git a/docs/make.bat b/docs/make.bat
deleted file mode 100644
index a24b446a..00000000
--- a/docs/make.bat
+++ /dev/null
@@ -1,170 +0,0 @@
-@ECHO OFF
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set BUILDDIR=_build
-set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
-if NOT "%PAPER%" == "" (
- set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
-)
-
-if "%1" == "" goto help
-
-if "%1" == "help" (
- :help
- echo.Please use `make ^` where ^ is one of
- echo. html to make standalone HTML files
- echo. dirhtml to make HTML files named index.html in directories
- echo. singlehtml to make a single large HTML file
- echo. pickle to make pickle files
- echo. json to make JSON files
- echo. htmlhelp to make HTML files and a HTML help project
- echo. qthelp to make HTML files and a qthelp project
- echo. devhelp to make HTML files and a Devhelp project
- echo. epub to make an epub
- echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
- echo. text to make text files
- echo. man to make manual pages
- echo. changes to make an overview over all changed/added/deprecated items
- echo. linkcheck to check all external links for integrity
- echo. doctest to run all doctests embedded in the documentation if enabled
- goto end
-)
-
-if "%1" == "clean" (
- for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
- del /q /s %BUILDDIR%\*
- goto end
-)
-
-if "%1" == "html" (
- %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/html.
- goto end
-)
-
-if "%1" == "dirhtml" (
- %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
- goto end
-)
-
-if "%1" == "singlehtml" (
- %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
- goto end
-)
-
-if "%1" == "pickle" (
- %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the pickle files.
- goto end
-)
-
-if "%1" == "json" (
- %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the JSON files.
- goto end
-)
-
-if "%1" == "htmlhelp" (
- %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run HTML Help Workshop with the ^
-.hhp project file in %BUILDDIR%/htmlhelp.
- goto end
-)
-
-if "%1" == "qthelp" (
- %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run "qcollectiongenerator" with the ^
-.qhcp project file in %BUILDDIR%/qthelp, like this:
- echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyLD.qhcp
- echo.To view the help file:
- echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyLD.ghc
- goto end
-)
-
-if "%1" == "devhelp" (
- %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished.
- goto end
-)
-
-if "%1" == "epub" (
- %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The epub file is in %BUILDDIR%/epub.
- goto end
-)
-
-if "%1" == "latex" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "text" (
- %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The text files are in %BUILDDIR%/text.
- goto end
-)
-
-if "%1" == "man" (
- %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The manual pages are in %BUILDDIR%/man.
- goto end
-)
-
-if "%1" == "changes" (
- %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
- if errorlevel 1 exit /b 1
- echo.
- echo.The overview file is in %BUILDDIR%/changes.
- goto end
-)
-
-if "%1" == "linkcheck" (
- %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
- if errorlevel 1 exit /b 1
- echo.
- echo.Link check complete; look for any errors in the above output ^
-or in %BUILDDIR%/linkcheck/output.txt.
- goto end
-)
-
-if "%1" == "doctest" (
- %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
- if errorlevel 1 exit /b 1
- echo.
- echo.Testing of doctests in the sources finished, look at the ^
-results in %BUILDDIR%/doctest/output.txt.
- goto end
-)
-
-:end
diff --git a/docs/quick-examples.md b/docs/quick-examples.md
new file mode 100644
index 00000000..0a904804
--- /dev/null
+++ b/docs/quick-examples.md
@@ -0,0 +1,73 @@
+# Quick Examples
+
+```python
+from pyld import jsonld
+import json
+
+doc = {
+ "http://schema.org/name": "Manu Sporny",
+ "http://schema.org/url": {"@id": "http://manu.sporny.org/"},
+ "http://schema.org/image": {"@id": "http://manu.sporny.org/images/manu.png"},
+}
+
+context = {
+ "name": "http://schema.org/name",
+ "homepage": {"@id": "http://schema.org/url", "@type": "@id"},
+ "image": {"@id": "http://schema.org/image", "@type": "@id"},
+}
+
+compacted = jsonld.compact(doc, context)
+print(json.dumps(compacted, indent=2))
+```
+
+The compacted output uses terms from the supplied context:
+
+```json
+{
+ "@context": {
+ "name": "http://schema.org/name",
+ "homepage": {
+ "@id": "http://schema.org/url",
+ "@type": "@id"
+ },
+ "image": {
+ "@id": "http://schema.org/image",
+ "@type": "@id"
+ }
+ },
+ "image": "http://manu.sporny.org/images/manu.png",
+ "homepage": "http://manu.sporny.org/",
+ "name": "Manu Sporny"
+}
+```
+
+Expand a compacted document:
+
+```python
+expanded = jsonld.expand(compacted)
+print(json.dumps(expanded, indent=2))
+```
+
+Flatten a document:
+
+```python
+flattened = jsonld.flatten(doc)
+```
+
+Frame a document:
+
+```python
+framed = jsonld.frame(doc, frame)
+```
+
+Normalize a document using RDF Dataset Canonicalization:
+
+```python
+normalized = jsonld.normalize(
+ doc,
+ {"algorithm": "URDNA2015", "format": "application/n-quads"},
+)
+```
+
+The normalized value is a canonical N-Quads string that can be used for hashing,
+comparison, or signing workflows.
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 231419d2..845f4434 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1 +1,3 @@
-sphinx-autobuild
+-r ../requirements.txt
+mkdocs-material
+mkdocs-macros-plugin
diff --git a/docs_macros.py b/docs_macros.py
new file mode 100644
index 00000000..4b8f0fe2
--- /dev/null
+++ b/docs_macros.py
@@ -0,0 +1,21 @@
+from pathlib import Path
+import sys
+
+
+ROOT_DIR = Path(__file__).resolve().parent
+sys.path.insert(0, str(ROOT_DIR / 'lib'))
+
+
+def define_env(env):
+ @env.macro
+ def bundled_contexts_table():
+ from pyld import BUNDLED_CONTEXTS
+
+ rows = [
+ '| Context URL | Bundled file |',
+ '| --- | --- |',
+ ]
+ for url, path in sorted(BUNDLED_CONTEXTS.items()):
+ relative_path = Path(path).relative_to(ROOT_DIR)
+ rows.append(f'| `{url}` | `{relative_path}` |')
+ return '\n'.join(rows)
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 00000000..835940a7
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,41 @@
+site_name: PyLD
+site_description: Python implementation of the JSON-LD API
+site_url: https://digitalbazaar.github.io/pyld/
+repo_url: https://github.com/digitalbazaar/pyld
+repo_name: digitalbazaar/pyld
+
+theme:
+ name: material
+ features:
+ - content.code.copy
+ - navigation.footer
+ - navigation.sections
+ - navigation.top
+ - search.highlight
+ - search.suggest
+
+nav:
+ - Home: index.md
+ - Installation: installation.md
+ - Quick Examples: quick-examples.md
+ - Document Loaders:
+ - Overview: document-loaders.md
+ - RequestsDocumentLoader: document-loaders/requests.md
+ - AioHttpDocumentLoader: document-loaders/aiohttp.md
+ - FrozenDocumentLoader: document-loaders/frozen.md
+ - API Reference: api-reference.md
+
+markdown_extensions:
+ - admonition
+ - attr_list
+ - pymdownx.details
+ - pymdownx.highlight:
+ anchor_linenums: true
+ - pymdownx.superfences
+ - toc:
+ permalink: true
+
+plugins:
+ - macros:
+ module_name: docs_macros
+ - search
diff --git a/setup.py b/setup.py
index 41fe18e9..62f533b1 100644
--- a/setup.py
+++ b/setup.py
@@ -18,7 +18,7 @@
os.path.dirname(__file__), 'lib', 'pyld', '__about__.py')) as fp:
exec(fp.read(), about)
-with open('README.rst') as fp:
+with open('README.md') as fp:
long_description = fp.read()
setup(
@@ -26,7 +26,7 @@
version=about['__version__'],
description='Python implementation of the JSON-LD API',
long_description=long_description,
- long_description_content_type="text/x-rst",
+ long_description_content_type="text/markdown",
author='Digital Bazaar',
author_email='support@digitalbazaar.com',
url='https://github.com/digitalbazaar/pyld',