diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9c3bb2c04..1e42fe769 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -149,8 +149,10 @@ make build -C docs/mkdocs # strict build: fails on broken links, anchor make check_mermaid -C docs/mkdocs # checks the Mermaid diagrams (requires Node.js) ``` -A new API page also needs an entry in [`docs/docset/docSet.sql`](https://github.com/nlohmann/json/blob/develop/docs/docset/docSet.sql), -the search index of the docset; `make build` reports missing entries. +The search index of the docset is generated from [`mkdocs.yml`](https://github.com/nlohmann/json/blob/develop/docs/mkdocs/mkdocs.yml) +and each page's title (H1) and declaration by +[`docs/docset/generate_docset.py`](https://github.com/nlohmann/json/blob/develop/docs/docset/generate_docset.py); +`make build` reports API pages that cannot be classified. ### Amalgamate the source code diff --git a/docs/docset/Makefile b/docs/docset/Makefile index b638390db..0f86c57cb 100644 --- a/docs/docset/Makefile +++ b/docs/docset/Makefile @@ -1,48 +1,24 @@ SHELL=/usr/bin/env bash -SED ?= $(shell which gsed 2>/dev/null || which sed) - -MKDOCS_PAGES=$(shell cd ../mkdocs/docs/ && find * -type f -name '*.md' | sort) +PYTHON=../mkdocs/venv/bin/python3 .PHONY: all all: JSON_for_Modern_C++.tgz -docSet.dsidx: docSet.sql - # generate index - sqlite3 docSet.dsidx > "$$(ls JSON_for_Modern_C++.docset/Contents/Resources/Documents/assets/stylesheets/main.*.min.css)" - # fix spacing - echo -e "\n\ndiv.md-sidebar div.md-sidebar--secondary, div.md-main__inner { top: 0; margin-top: 0 }" >> "$$(ls JSON_for_Modern_C++.docset/Contents/Resources/Documents/assets/stylesheets/main.*.min.css)" - # remove "JSON for Modern C++" from page titles (fallback) - find JSON_for_Modern_C++.docset/Contents/Resources/Documents -type f -exec $(SED) -i 's| - JSON for Modern C++||' {} + - # replace page titles with name from index, if available - for page in $(MKDOCS_PAGES); do \ - case "$$page" in \ - */index.md) path=$${page/\/index.md/} ;; \ - *) path=$${page/.md/} ;; \ - esac; \ - title=$$(sqlite3 docSet.dsidx "SELECT name FROM searchIndex WHERE path='$$path/index.html'" | tr '\n' ',' | $(SED) -e 's/,/, /g' -e 's/, $$/\n/'); \ - if [ "x$$title" != "x" ]; then \ - $(SED) -i "s%.*%$$title%" "JSON_for_Modern_C++.docset/Contents/Resources/Documents/$$path/index.html"; \ - fi \ - done - # clean up - rm JSON_for_Modern_C++.docset/Contents/Resources/Documents/sitemap.* - # copy index - cp docSet.dsidx JSON_for_Modern_C++.docset/Contents/Resources/ + $(PYTHON) generate_docset.py docset ../mkdocs/site . +.PHONY: JSON_for_Modern_C++.tgz JSON_for_Modern_C++.tgz: JSON_for_Modern_C++.docset - tar --exclude='.DS_Store' -cvzf JSON_for_Modern_C++.tgz JSON_for_Modern_C++.docset + $(PYTHON) generate_docset.py tgz . # install docset for Zeal documentation browser (https://zealdocs.org/) .PHONY: install_docset_zeal @@ -52,26 +28,6 @@ install_docset_zeal: JSON_for_Modern_C++.docset mkdir -p $$docset_root; \ cp -r JSON_for_Modern_C++.docset $$docset_root/ -# both targets below compare the docset search index with the mkdocs page -# set. They share the same normalization (docs/foo/index.md and -# docs/foo.md both become foo/index.html, the URL mkdocs itself would -# give the page; the top-level index.md is excluded, as it is not part -# of the hand-curated docSet.sql) and use comm(1) on two sorted lists -# instead of running a sqlite3 query, or an O(n*m) nested shell loop, -# once per page. -DOCSET_INDEX_PATHS=$(shell sqlite3 docSet.dsidx "SELECT DISTINCT path FROM searchIndex" | sort) -DOCSET_PAGE_PATHS=$(shell echo '$(MKDOCS_PAGES)' | tr ' ' '\n' | grep -v '^index\.md$$' | $(SED) -E 's@/index\.md$$@/index.html@; s@\.md$$@/index.html@' | sort) - -# list mkdocs pages missing from the docset index -.PHONY: list_missing_pages -list_missing_pages: docSet.dsidx - @comm -23 <(echo '$(DOCSET_PAGE_PATHS)' | tr ' ' '\n') <(echo '$(DOCSET_INDEX_PATHS)' | tr ' ' '\n') - -# list paths in the docset index without a corresponding mkdocs page -.PHONY: list_removed_paths -list_removed_paths: docSet.dsidx - @comm -13 <(echo '$(DOCSET_PAGE_PATHS)' | tr ' ' '\n') <(echo '$(DOCSET_INDEX_PATHS)' | tr ' ' '\n') - .PHONY: clean clean: rm -f docSet.dsidx diff --git a/docs/docset/README.md b/docs/docset/README.md index 9d962a91c..38021f1fc 100644 --- a/docs/docset/README.md +++ b/docs/docset/README.md @@ -4,7 +4,8 @@ The folder contains the required files to create a [docset](https://kapeli.com/d documentation browsers like [Dash](https://kapeli.com/dash), [Velocity](https://velocity.silverlakesoftware.com), or [Zeal](https://zealdocs.org). -The docset can be created with +The docset (pages and search index) is generated by `generate_docset.py` from the mkdocs site and `mkdocs.yml`. It +can be created with ```sh make JSON_for_Modern_C++.docset @@ -12,6 +13,7 @@ make JSON_for_Modern_C++.docset The generated folder `JSON_for_Modern_C++.docset` can then be opened in the documentation browser. `make all` builds a `JSON_for_Modern_C++.tgz` archive instead, and `make install_docset_zeal` installs the docset for Zeal directly. +`make docSet.dsidx` builds only the search index. A recent version is also part of the [Dash user contributions](https://github.com/Kapeli/Dash-User-Contributions/tree/master/docsets/JSON_for_Modern_C%2B%2B). diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql deleted file mode 100644 index e6d079c37..000000000 --- a/docs/docset/docSet.sql +++ /dev/null @@ -1,345 +0,0 @@ -DROP TABLE IF EXISTS searchIndex; -CREATE TABLE searchIndex(id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT); -CREATE UNIQUE INDEX anchor ON searchIndex (name, type, path); - --- API -INSERT INTO searchIndex(name, type, path) VALUES ('adl_serializer', 'Class', 'api/adl_serializer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('adl_serializer::from_json', 'Function', 'api/adl_serializer/from_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('adl_serializer::to_json', 'Function', 'api/adl_serializer/to_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype', 'Class', 'api/byte_container_with_subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::byte_container_with_subtype', 'Constructor', 'api/byte_container_with_subtype/byte_container_with_subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::clear_subtype', 'Method', 'api/byte_container_with_subtype/clear_subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::has_subtype', 'Method', 'api/byte_container_with_subtype/has_subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::operator!=', 'Operator', 'api/byte_container_with_subtype/operator_ne/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::operator==', 'Operator', 'api/byte_container_with_subtype/operator_eq/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::set_subtype', 'Method', 'api/byte_container_with_subtype/set_subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::subtype', 'Method', 'api/byte_container_with_subtype/subtype/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json', 'Class', 'api/basic_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('format_as', 'Function', 'api/basic_json/format_as/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::accept', 'Function', 'api/basic_json/accept/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array', 'Function', 'api/basic_json/array/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array_t', 'Type', 'api/basic_json/array_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::as_base_class', 'Method', 'api/basic_json/as_base_class/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::at', 'Method', 'api/basic_json/at/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::back', 'Method', 'api/basic_json/back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::basic_json', 'Constructor', 'api/basic_json/basic_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::begin', 'Method', 'api/basic_json/begin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::binary', 'Function', 'api/basic_json/binary/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::binary_t', 'Type', 'api/basic_json/binary_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::boolean_t', 'Type', 'api/basic_json/boolean_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::cbegin', 'Method', 'api/basic_json/cbegin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::cbor_tag_handler_t', 'Enum', 'api/basic_json/cbor_tag_handler_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::cend', 'Method', 'api/basic_json/cend/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::clear', 'Method', 'api/basic_json/clear/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::contains', 'Method', 'api/basic_json/contains/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::count', 'Method', 'api/basic_json/count/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::crbegin', 'Method', 'api/basic_json/crbegin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::crend', 'Method', 'api/basic_json/crend/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::default_object_comparator_t', 'Type', 'api/basic_json/default_object_comparator_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::diff', 'Function', 'api/basic_json/diff/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::dump', 'Method', 'api/basic_json/dump/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::emplace', 'Method', 'api/basic_json/emplace/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::emplace_back', 'Method', 'api/basic_json/emplace_back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::empty', 'Method', 'api/basic_json/empty/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::end', 'Method', 'api/basic_json/end/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::end_pos', 'Method', 'api/basic_json/end_pos/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::erase', 'Method', 'api/basic_json/erase/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::error_handler_t', 'Enum', 'api/basic_json/error_handler_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::exception', 'Class', 'api/basic_json/exception/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::find', 'Method', 'api/basic_json/find/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::flatten', 'Method', 'api/basic_json/flatten/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bjdata', 'Function', 'api/basic_json/from_bjdata/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bson', 'Function', 'api/basic_json/from_bson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_cbor', 'Function', 'api/basic_json/from_cbor/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_msgpack', 'Function', 'api/basic_json/from_msgpack/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bon8', 'Function', 'api/basic_json/from_bon8/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_ubjson', 'Function', 'api/basic_json/from_ubjson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::front', 'Method', 'api/basic_json/front/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get', 'Method', 'api/basic_json/get/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get_allocator', 'Function', 'api/basic_json/get_allocator/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get_binary', 'Method', 'api/basic_json/get_binary/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get_ptr', 'Method', 'api/basic_json/get_ptr/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get_ref', 'Method', 'api/basic_json/get_ref/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get_to', 'Method', 'api/basic_json/get_to/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::input_format_t', 'Enum', 'api/basic_json/input_format_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::insert', 'Method', 'api/basic_json/insert/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::invalid_iterator', 'Class', 'api/basic_json/invalid_iterator/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_array', 'Method', 'api/basic_json/is_array/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_binary', 'Method', 'api/basic_json/is_binary/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_boolean', 'Method', 'api/basic_json/is_boolean/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_discarded', 'Method', 'api/basic_json/is_discarded/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_null', 'Method', 'api/basic_json/is_null/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_number', 'Method', 'api/basic_json/is_number/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_number_float', 'Method', 'api/basic_json/is_number_float/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_number_integer', 'Method', 'api/basic_json/is_number_integer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_number_unsigned', 'Method', 'api/basic_json/is_number_unsigned/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_object', 'Method', 'api/basic_json/is_object/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_primitive', 'Method', 'api/basic_json/is_primitive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_string', 'Method', 'api/basic_json/is_string/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::is_structured', 'Method', 'api/basic_json/is_structured/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::items', 'Method', 'api/basic_json/items/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::json_base_class_t', 'Type', 'api/basic_json/json_base_class_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::json_serializer', 'Class', 'api/basic_json/json_serializer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::max_size', 'Method', 'api/basic_json/max_size/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::merge_patch', 'Method', 'api/basic_json/merge_patch/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::meta', 'Function', 'api/basic_json/meta/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::number_float_t', 'Type', 'api/basic_json/number_float_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::number_integer_t', 'Type', 'api/basic_json/number_integer_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::number_unsigned_t', 'Type', 'api/basic_json/number_unsigned_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::object', 'Function', 'api/basic_json/object/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::object_comparator_t', 'Type', 'api/basic_json/object_comparator_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::object_t', 'Type', 'api/basic_json/object_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator ValueType', 'Operator', 'api/basic_json/operator_ValueType/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator value_t', 'Operator', 'api/basic_json/operator_value_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator[]', 'Operator', 'api/basic_json/operator[]/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator=', 'Operator', 'api/basic_json/operator=/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator+=', 'Operator', 'api/basic_json/operator+=/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator==', 'Operator', 'api/basic_json/operator_eq/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator!=', 'Operator', 'api/basic_json/operator_ne/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator<', 'Operator', 'api/basic_json/operator_lt/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator<=', 'Operator', 'api/basic_json/operator_le/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator>', 'Operator', 'api/basic_json/operator_gt/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator>=', 'Operator', 'api/basic_json/operator_ge/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::operator<=>', 'Operator', 'api/basic_json/operator_spaceship/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::out_of_range', 'Class', 'api/basic_json/out_of_range/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::other_error', 'Class', 'api/basic_json/other_error/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::parse', 'Function', 'api/basic_json/parse/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::parse_error', 'Class', 'api/basic_json/parse_error/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::parse_event_t', 'Enum', 'api/basic_json/parse_event_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::parser_callback_t', 'Type', 'api/basic_json/parser_callback_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::patch', 'Method', 'api/basic_json/patch/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::patch_inplace', 'Method', 'api/basic_json/patch_inplace/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::push_back', 'Method', 'api/basic_json/push_back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::rbegin', 'Method', 'api/basic_json/rbegin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::rend', 'Method', 'api/basic_json/rend/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::sax_parse', 'Function', 'api/basic_json/sax_parse/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::size', 'Method', 'api/basic_json/size/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::start_pos', 'Method', 'api/basic_json/start_pos/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::string_t', 'Type', 'api/basic_json/string_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::swap', 'Method', 'api/basic_json/swap/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::type', 'Method', 'api/basic_json/type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::type_error', 'Class', 'api/basic_json/type_error/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::type_name', 'Method', 'api/basic_json/type_name/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::unflatten', 'Method', 'api/basic_json/unflatten/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::update', 'Method', 'api/basic_json/update/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bjdata', 'Function', 'api/basic_json/to_bjdata/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bson', 'Function', 'api/basic_json/to_bson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_cbor', 'Function', 'api/basic_json/to_cbor/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_msgpack', 'Function', 'api/basic_json/to_msgpack/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bon8', 'Function', 'api/basic_json/to_bon8/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_string', 'Method', 'api/basic_json/to_string/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_ubjson', 'Function', 'api/basic_json/to_ubjson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value', 'Method', 'api/basic_json/value/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value_t', 'Enum', 'api/basic_json/value_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::with_t', 'Type', 'api/basic_json/with_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::~basic_json', 'Method', 'api/basic_json/~basic_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document', 'Class', 'api/basic_json_document/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::basic_json_document', 'Constructor', 'api/basic_json_document/basic_json_document/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::accept', 'Function', 'api/basic_json_document/accept/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::is_discarded', 'Method', 'api/basic_json_document/is_discarded/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::memory_usage', 'Method', 'api/basic_json_document/memory_usage/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::node_count', 'Method', 'api/basic_json_document/node_count/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::owns_source', 'Method', 'api/basic_json_document/owns_source/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::parse', 'Function', 'api/basic_json_document/parse/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::parse_copy', 'Function', 'api/basic_json_document/parse_copy/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::read', 'Method', 'api/basic_json_document/read/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::root', 'Method', 'api/basic_json_document/root/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::shrink_to_fit', 'Method', 'api/basic_json_document/shrink_to_fit/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::source', 'Method', 'api/basic_json_document/source/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view', 'Class', 'api/basic_json_view/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::basic_json_view', 'Constructor', 'api/basic_json_view/basic_json_view/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::at', 'Method', 'api/basic_json_view/at/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::back', 'Method', 'api/basic_json_view/back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::begin', 'Method', 'api/basic_json_view/begin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::cbegin', 'Method', 'api/basic_json_view/cbegin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::cend', 'Method', 'api/basic_json_view/cend/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::contains', 'Method', 'api/basic_json_view/contains/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::count', 'Method', 'api/basic_json_view/count/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::empty', 'Method', 'api/basic_json_view/empty/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::end', 'Method', 'api/basic_json_view/end/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::find', 'Method', 'api/basic_json_view/find/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::front', 'Method', 'api/basic_json_view/front/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get', 'Method', 'api/basic_json_view/get/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get_string', 'Method', 'api/basic_json_view/get_string/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get_to', 'Method', 'api/basic_json_view/get_to/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_array', 'Method', 'api/basic_json_view/is_array/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_binary', 'Method', 'api/basic_json_view/is_binary/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_boolean', 'Method', 'api/basic_json_view/is_boolean/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_discarded', 'Method', 'api/basic_json_view/is_discarded/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_null', 'Method', 'api/basic_json_view/is_null/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_number', 'Method', 'api/basic_json_view/is_number/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_number_float', 'Method', 'api/basic_json_view/is_number_float/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_number_integer', 'Method', 'api/basic_json_view/is_number_integer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_number_unsigned', 'Method', 'api/basic_json_view/is_number_unsigned/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_object', 'Method', 'api/basic_json_view/is_object/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_primitive', 'Method', 'api/basic_json_view/is_primitive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_string', 'Method', 'api/basic_json_view/is_string/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_structured', 'Method', 'api/basic_json_view/is_structured/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::items', 'Method', 'api/basic_json_view/items/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::materialize', 'Method', 'api/basic_json_view/materialize/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::number_token', 'Method', 'api/basic_json_view/number_token/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator[]', 'Operator', 'api/basic_json_view/operator[]/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::size', 'Method', 'api/basic_json_view/size/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::source_offset', 'Method', 'api/basic_json_view/source_offset/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::type', 'Method', 'api/basic_json_view/type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::type_name', 'Method', 'api/basic_json_view/type_name/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::value', 'Method', 'api/basic_json_view/value/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json', 'Class', 'api/json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_document', 'Class', 'api/json_document/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_view', 'Class', 'api/json_view/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer', 'Class', 'api/json_pointer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::back', 'Method', 'api/json_pointer/back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::empty', 'Method', 'api/json_pointer/empty/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::front', 'Method', 'api/json_pointer/front/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::json_pointer', 'Constructor', 'api/json_pointer/json_pointer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator==', 'Operator', 'api/json_pointer/operator_eq/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator!=', 'Operator', 'api/json_pointer/operator_ne/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator/', 'Operator', 'api/json_pointer/operator_slash/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator/=', 'Operator', 'api/json_pointer/operator_slasheq/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator string_t', 'Operator', 'api/json_pointer/operator_string_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator<=>', 'Operator', 'api/json_pointer/operator_spaceship/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::parent_pointer', 'Method', 'api/json_pointer/parent_pointer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::pop_back', 'Method', 'api/json_pointer/pop_back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::pop_front', 'Method', 'api/json_pointer/pop_front/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::push_back', 'Method', 'api/json_pointer/push_back/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::push_front', 'Method', 'api/json_pointer/push_front/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::string_t', 'Type', 'api/json_pointer/string_t/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::to_string', 'Method', 'api/json_pointer/to_string/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax', 'Class', 'api/json_sax/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::binary', 'Method', 'api/json_sax/binary/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::boolean', 'Method', 'api/json_sax/boolean/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::end_array', 'Method', 'api/json_sax/end_array/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::end_object', 'Method', 'api/json_sax/end_object/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::key', 'Method', 'api/json_sax/key/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::null', 'Method', 'api/json_sax/null/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::number_float', 'Method', 'api/json_sax/number_float/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::number_integer', 'Method', 'api/json_sax/number_integer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::number_unsigned', 'Method', 'api/json_sax/number_unsigned/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::parse_error', 'Method', 'api/json_sax/parse_error/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::start_array', 'Method', 'api/json_sax/start_array/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::start_object', 'Method', 'api/json_sax/start_object/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('json_sax::string', 'Method', 'api/json_sax/string/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('operator""_json', 'Literal', 'api/operator_literal_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('operator""_json_pointer', 'Literal', 'api/operator_literal_json_pointer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('operator<<', 'Operator', 'api/operator_ltlt/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('operator>>', 'Operator', 'api/operator_gtgt/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json', 'Class', 'api/ordered_json/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json_document', 'Class', 'api/ordered_json_document/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json_view', 'Class', 'api/ordered_json_view/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('ordered_map', 'Class', 'api/ordered_map/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('std::formatter', 'Class', 'api/basic_json/std_formatter/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('std::hash', 'Class', 'api/basic_json/std_hash/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('std::swap', 'Function', 'api/basic_json/std_swap/index.html'); - --- Features -INSERT INTO searchIndex(name, type, path) VALUES ('Arbitrary Type Conversions', 'Guide', 'features/arbitrary_types/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats', 'Guide', 'features/binary_formats/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BJData', 'Guide', 'features/binary_formats/bjdata/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BSON', 'Guide', 'features/binary_formats/bson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: CBOR', 'Guide', 'features/binary_formats/cbor/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: MessagePack', 'Guide', 'features/binary_formats/messagepack/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BON8', 'Guide', 'features/binary_formats/bon8/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: UBJSON', 'Guide', 'features/binary_formats/ubjson/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Binary Values', 'Guide', 'features/binary_values/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Comments', 'Guide', 'features/comments/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Element Access', 'Guide', 'features/element_access/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Element Access: Access with default value: value', 'Guide', 'features/element_access/default_value/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Element Access: Checked access: at', 'Guide', 'features/element_access/checked_access/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Element Access: Unchecked access: operator[]', 'Guide', 'features/element_access/unchecked_access/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Exceptions', 'Guide', 'home/exceptions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Integration: Migration Guide', 'Guide', 'integration/migration_guide/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Integration: CMake', 'Guide', 'integration/cmake/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Integration: Header only', 'Guide', 'integration/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Integration: Package Managers', 'Guide', 'integration/package_managers/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Integration: Pkg-config', 'Guide', 'integration/pkg-config/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Iterators', 'Guide', 'features/iterators/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON Merge Patch', 'Guide', 'features/merge_patch/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON Patch and Diff', 'Guide', 'features/json_patch/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON Pointer', 'Guide', 'features/json_pointer/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Zero-copy JSON views', 'Guide', 'features/json_view/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('nlohmann Namespace', 'Guide', 'features/namespace/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Types', 'Guide', 'features/types/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Types: Number Handling', 'Guide', 'features/types/number_handling/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Object Order', 'Guide', 'features/object_order/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Performance', 'Guide', 'features/performance/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing', 'Guide', 'features/parsing/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: JSON Lines', 'Guide', 'features/parsing/json_lines/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: Parser Callbacks', 'Guide', 'features/parsing/parser_callbacks/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: Parsing and Exceptions', 'Guide', 'features/parsing/parse_exceptions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: SAX Interface', 'Guide', 'features/parsing/sax_interface/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: Untrusted Input', 'Guide', 'features/parsing/untrusted_input/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Runtime Assertions', 'Guide', 'features/assertions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Specializing enum conversion', 'Guide', 'features/enum_conversion/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Supported Macros', 'Guide', 'features/macros/index.html'); - --- Macros -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_ASSERT', 'Macro', 'api/macros/json_assert/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_BRACE_INIT_COPY_SEMANTICS', 'Macro', 'api/macros/json_brace_init_copy_semantics/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'api/macros/json_throw_user/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DELETE_DEPRECATED_FUNCTIONS', 'Macro', 'api/macros/json_delete_deprecated_functions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_TUPLE_REFERENCE_CONVERSION', 'Macro', 'api/macros/json_disable_tuple_reference_conversion/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_11', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_14', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_17', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_20', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_23', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_26', 'Macro', 'api/macros/json_has_cpp_11/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_EXPERIMENTAL_FILESYSTEM', 'Macro', 'api/macros/json_has_filesystem/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_FILESYSTEM', 'Macro', 'api/macros/json_has_filesystem/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_RANGES', 'Macro', 'api/macros/json_has_ranges/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_STATIC_RTTI', 'Macro', 'api/macros/json_has_static_rtti/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_STD_FORMAT', 'Macro', 'api/macros/json_has_std_format/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_THREE_WAY_COMPARISON', 'Macro', 'api/macros/json_has_three_way_comparison/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NOEXCEPTION', 'Macro', 'api/macros/json_noexception/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NO_AUTOMATIC_UDLS', 'Macro', 'api/macros/json_no_automatic_udls/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NO_IO', 'Macro', 'api/macros/json_no_io/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NO_THREAD_LOCAL', 'Macro', 'api/macros/json_no_thread_local/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_PRECISE_STREAM_POSITION', 'Macro', 'api/macros/json_precise_stream_position/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_LIBRARY_VERSION_CHECK', 'Macro', 'api/macros/json_skip_library_version_check/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_UNSUPPORTED_COMPILER_CHECK', 'Macro', 'api/macros/json_skip_unsupported_compiler_check/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_STRICT_BINARY_UTF8', 'Macro', 'api/macros/json_strict_binary_utf8/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_STRICT_NUL_HANDLING', 'Macro', 'api/macros/json_strict_nul_handling/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_THROW_USER', 'Macro', 'api/macros/json_throw_user/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api/macros/json_throw_user/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_GLOBAL_UDLS', 'Macro', 'api/macros/json_use_global_udls/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_IMPLICIT_CONVERSIONS', 'Macro', 'api/macros/json_use_implicit_conversions/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON', 'Macro', 'api/macros/json_use_legacy_discarded_value_comparison/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS', 'Macro', 'api/macros/json_use_objects_for_enum_keyed_maps/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_SIMDUTF', 'Macro', 'api/macros/json_use_simdutf/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('Macros', 'Macro', 'api/macros/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_type_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_type_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_type_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_type_non_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_type_non_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_type_non_intrusive/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE', 'Macro', 'api/macros/nlohmann_json_namespace/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE_BEGIN', 'Macro', 'api/macros/nlohmann_json_namespace_begin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE_END', 'Macro', 'api/macros/nlohmann_json_namespace_begin/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE_NO_VERSION', 'Macro', 'api/macros/nlohmann_json_namespace_no_version/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_SERIALIZE_ENUM', 'Macro', 'api/macros/nlohmann_json_serialize_enum/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_SERIALIZE_ENUM_STRICT', 'Macro', 'api/macros/nlohmann_json_serialize_enum_strict/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_MAJOR', 'Macro', 'api/macros/nlohmann_json_version_major/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_MINOR', 'Macro', 'api/macros/nlohmann_json_version_major/index.html'); -INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_PATCH', 'Macro', 'api/macros/nlohmann_json_version_major/index.html'); diff --git a/docs/docset/generate_docset.py b/docs/docset/generate_docset.py new file mode 100755 index 000000000..e09e8d6d1 --- /dev/null +++ b/docs/docset/generate_docset.py @@ -0,0 +1,528 @@ +#!/usr/bin/env python3 + +"""Generate the Dash docset search index from the mkdocs sources.""" + +import argparse +import glob +import hashlib +import html +import os +import re +import shutil +import sqlite3 +import sys +import tarfile +import urllib.parse +import urllib.request + +import yaml + +HERE = os.path.dirname(os.path.abspath(__file__)) +MKDOCS_YML = os.path.join(HERE, '..', 'mkdocs', 'mkdocs.yml') +PAGES = os.path.join(HERE, '..', 'mkdocs', 'docs') + +# api pages whose (name, type) cannot be derived by the heuristics +OVERRIDES = { +} + +DOCSET = 'JSON_for_Modern_C++.docset' +TITLE_SUFFIX = ' - JSON for Modern C++' + +# CSS rules appended to the stylesheet: hide navigation items and fix spacing +# hide the navigation (the documentation browser has its own); Material's class selectors would win over element +# selectors, hence the classes and !important +CSS_PATCH = ( + '\n\n.md-header, .md-footer, .md-tabs, .md-sidebar--primary, .md-content__button { display: none !important; }' + '\n\n.md-sidebar--secondary, .md-main__inner { top: 0 !important; margin-top: 0 !important; }' +) + +USER_AGENT = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Chrome/124.0 Safari/537.36') +CONTENT_TYPE_EXT = {'image/svg+xml': '.svg', 'image/png': '.png', 'image/jpeg': '.jpg', + 'image/gif': '.gif', 'image/webp': '.webp'} + +# remote loads that are allowed to remain (URL -> reason) +ALLOWED_REMOTE = { + # Material only loads this polyfill if the browser has no ResizeObserver + 'https://unpkg.com/resize-observer-polyfill': 'fallback for browsers without ResizeObserver', +} + +problems = [] + + +def problem(page, reason) -> None: + """Record a problem; all problems are reported at the end.""" + problems.append(f'generate_docset.py: {page}: {reason}') + + +class Loader(yaml.SafeLoader): + """YAML loader that tolerates the custom tags used in mkdocs.yml.""" + + +Loader.add_multi_constructor('', lambda loader, suffix, node: None) + + +def walk_nav(items, groups=()): + """Yield (group titles, nav title or None, md path) for all nav leaves.""" + for item in items: + if isinstance(item, str): + yield list(groups), None, item + elif isinstance(item, dict): + for title, value in item.items(): + if isinstance(value, list): + yield from walk_nav(value, groups + (str(title),)) + else: + yield list(groups), str(title), value + + +def page_path(md_path) -> str: + """Map a markdown path to the HTML path of the rendered page.""" + if md_path.endswith('/index.md'): + return md_path[:-len('index.md')] + 'index.html' + return md_path[:-len('.md')] + '/index.html' + + +def read_page(md_path) -> str: + """Read a page (resolving a snippet include), return '' if it does not exist.""" + try: + with open(os.path.join(PAGES, md_path), encoding='utf-8') as f: + text = f.read() + m = re.match(r'--8<-- "(.+)"\s*$', text) + if m: + with open(os.path.join(PAGES, m.group(1)), encoding='utf-8') as f: + text = f.read() + return text + except OSError: + return '' + + +def clean(text) -> str: + """Strip tags and entities from a heading and normalize whitespace.""" + text = text.replace('\\>', '\x00') # escaped '>' is not the end of a tag + text = html.unescape(re.sub(r']*>', '', text)).replace('\x00', '>') + return re.sub(r'\s+', ' ', text).strip() + + +def strip_fences(text) -> str: + """Remove fenced code blocks (their lines may start with '# ').""" + return re.sub(r'^(```|~~~).*?^\1[^\n]*$', '', text, flags=re.M | re.S) + + +def get_h1(text): + """Return the cleaned first H1 of a page or None.""" + body = strip_fences(text) + m = re.search(r'^# (.+)$', body, flags=re.M) + if m: + return clean(m.group(1)) + m = re.search(r'

(.*?)

', body, flags=re.S) + return clean(m.group(1)) if m else None + + +def split_top_level(text, seps=','): + """Split at separators that are not nested in <>, () or [].""" + parts, depth, current = [], 0, '' + for c in text: + if c in '<([': + depth += 1 + elif c in '>)]': + depth -= 1 + if c in seps and depth <= 0: + parts.append(current) + current = '' + else: + current += c + parts.append(current) + return [p.strip() for p in parts if p.strip()] + + +OPERATOR_SYMBOLS = ('<=>', '<<', '>>', '<=', '>=', '<', '>') + + +def api_names(h1) -> list: + """Derive the entry names from the H1 of an api page.""" + # hide the angle brackets of operators from the nesting detection + for i, sym in enumerate(OPERATOR_SYMBOLS): + h1 = h1.replace('operator' + sym, f'operator\x01{i}\x01') + names = [] + for part in split_top_level(h1): + name = part.replace('\\', '').replace('nlohmann::', '') + name = re.sub(r'\x01(\d)\x01', lambda m: OPERATOR_SYMBOLS[int(m.group(1))], name) + # drop qualifiers like "operator<<(basic_json)", but keep "operator()" + if not name.endswith('operator()'): + name = re.sub(r'(?<=\w|[<>=!+\-*/\[\]])\([^()]*\)$', '', name) + if name not in names: + names.append(name) + return names + + +def first_cpp_block(text): + """Return the first ```cpp block following the H1.""" + m = re.search(r'^# .*?^```cpp\n(.*?)^```', text, flags=re.M | re.S) + return m.group(1) if m else None + + +def api_type(name, decl): + """Determine the Dash entry type from name and declaration.""" + parts = name.split('::') + last = parts[-1] + if name.startswith('operator""'): + return 'Literal' + if last.startswith('operator'): + return 'Operator' + if decl is None: + return None + if re.search(r'\benum\b', decl): + return 'Enum' + if re.search(r'^\s*(template\s*<.*>\s*)?(class|struct)\s+\w+\s*(final\b|[:{;<]|$)', decl, flags=re.M): + return 'Class' + if re.search(r'\busing\s+\w+\s*=', decl) or 'typedef' in decl: + return 'Type' + if len(parts) > 1 and last == parts[-2]: + return 'Constructor' + if last.startswith('~'): + return 'Method' + if re.search(r'\bstatic\b', decl) or len(parts) == 1 or parts[0] == 'std': + return 'Function' + return 'Method' + + +def macro_names(h1) -> list: + """Split the H1 of a macro page into macro names.""" + return [n.strip() for n in re.split(r'[,/]', h1) if n.strip()] + + +def api_entries(md_path): + """Return the (name, type) pairs of an api page.""" + if md_path in OVERRIDES: + return OVERRIDES[md_path] + if md_path == 'api/macros/index.md': + return [('Macros', 'Macro')] + text = read_page(md_path) + h1 = get_h1(text) + if not h1: + problem(md_path, 'no H1 found') + return [] + if md_path.startswith('api/macros/'): + return [(n, 'Macro') for n in macro_names(h1)] + names = api_names(h1) + if not names: + problem(md_path, 'no names found') + return [] + decl = first_cpp_block(text) + result = [] + for name in names: + kind = api_type(name, decl) + if kind is None: + problem(md_path, f'no type determinable for {name}') + else: + result.append((name, kind)) + return result + + +def guide_entries(nav): + """Yield (name, type, md path) for all non-api nav pages.""" + for groups, title, md_path in walk_nav(nav): + if md_path.startswith('api/') or md_path == 'index.md': + continue + if not md_path.endswith('.md'): + continue + groups = groups[1:] # drop the top-level tab + if title is None: + title = get_h1(read_page(md_path)) + if not title: + problem(md_path, 'no title found') + continue + # "Parsing: Parsing Untrusted Input" -> "Parsing: Untrusted Input" + if groups and title.startswith(groups[-1] + ' ') and title[len(groups[-1]) + 1:][:1].isupper(): + title = title[len(groups[-1]) + 1:] + if md_path.endswith('/index.md') and groups and title == groups[-1]: + name = ': '.join(groups) + else: + name = ': '.join(groups + [title]) + yield name, 'Guide', md_path + + +def build_entries() -> list: + """Return the sorted list of (name, type, path) index entries.""" + nav = load_mkdocs_yml()['nav'] + + entries = set() + for name, kind, md_path in guide_entries(nav): + entries.add((name, kind, page_path(md_path))) + + api_root = os.path.join(PAGES, 'api') + on_disk = set() + for root, _, files in os.walk(api_root): + for file in files: + if file.endswith('.md'): + rel = os.path.relpath(os.path.join(root, file), PAGES) + on_disk.add(rel.replace(os.sep, '/')) + for _, _, md_path in walk_nav(nav): + if md_path.startswith('api/') and md_path not in on_disk: + problem(md_path, 'listed in nav but missing on disk') + for md_path in sorted(on_disk): + for name, kind in api_entries(md_path): + entries.add((name, kind, page_path(md_path))) + return sorted(entries) + + +def write_index(entries, out) -> None: + """Write the entries into a SQLite search index.""" + if os.path.exists(out): + os.remove(out) + con = sqlite3.connect(out) + con.execute('CREATE TABLE searchIndex(id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT)') + con.execute('CREATE UNIQUE INDEX anchor ON searchIndex (name, type, path)') + con.executemany('INSERT INTO searchIndex(name, type, path) VALUES (?, ?, ?)', entries) + con.commit() + con.close() + + +def html_files(root): + """Yield all HTML files below root.""" + for base, _, files in os.walk(root): + for file in files: + if file.endswith('.html'): + yield os.path.join(base, file) + + +def read(path) -> str: + with open(path, encoding='utf-8') as f: + return f.read() + + +def write(path, text) -> None: + with open(path, 'w', encoding='utf-8') as f: + f.write(text) + + +def remove_source_widget(docs) -> None: + """Drop data-md-component=source so Material does not query api.github.com for the stars and version.""" + pattern = re.compile(r'\sdata-md-component=(?:"source"|source\b)') + for path in html_files(docs): + text = read(path) + new = pattern.sub('', text) + if new != text: + write(path, new) + + +def patch_titles(docs, entries) -> None: + """Strip the site name from all titles; use the index names where available.""" + names = {} + for name, _, path in entries: + names.setdefault(path, []).append(name) + for file in html_files(docs): + rel = os.path.relpath(file, docs).replace(os.sep, '/') + text = read(file).replace(TITLE_SUFFIX, '') + if rel in names: + title = html.escape(', '.join(names[rel]), quote=False) + text = re.sub(r'.*?', lambda _: f'{title}', text, count=1, flags=re.S) + write(file, text) + + +IMG_RE = re.compile(r']*>', re.I) +ATTR_RE = r'''(?:{0})\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))''' + + +def attr(tag, name): + """Return the value of an attribute in a tag or None.""" + m = re.search(r'(? bool: + return re.match(r'(https?:)?//', url.strip(), flags=re.I) is not None + + +def download(url, docs) -> str: + """Download url into assets/external and return the path relative to docs.""" + u = urllib.parse.urlparse(url if not url.startswith('//') else 'https:' + url) + req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT}) + with urllib.request.urlopen(req, timeout=20) as r: + data = r.read() + ctype = r.headers.get_content_type() + path = urllib.parse.unquote(u.path).lstrip('/') + ext = os.path.splitext(path)[1] + if u.query or not ext or path.endswith('/'): + digest = hashlib.sha1(url.encode()).hexdigest()[:12] + path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin')) + rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path)) + out = os.path.join(docs, rel) + os.makedirs(os.path.dirname(out), exist_ok=True) + with open(out, 'wb') as f: + f.write(data) + return rel.replace(os.sep, '/') + + +def localize_images(docs) -> None: + """Download remote images and rewrite their src; drop them on failure.""" + cache = {} + for file in html_files(docs): + text = read(file) + + def repl(m): + tag = m.group(0) + src = attr(tag, 'src') + if src is None or not is_remote(src): + return tag + if src not in cache: + try: + cache[src] = download(src, docs) + except Exception as e: # noqa: BLE001 + print(f'generate_docset.py: warning: cannot download {src}: {e}', file=sys.stderr) + cache[src] = None + if cache[src] is None: + return attr(tag, 'alt') or '' + local = os.path.relpath(os.path.join(docs, cache[src]), os.path.dirname(file)) + local = local.replace(os.sep, '/') + return re.sub(ATTR_RE.format('src'), lambda _: f'src="{local}"', tag, count=1, flags=re.I) + + new = IMG_RE.sub(repl, text) + if new != text: + write(file, new) + + +def load_mkdocs_yml() -> dict: + """Load mkdocs.yml, ignoring tags like !ENV and !!python/name.""" + with open(MKDOCS_YML, encoding='utf-8') as f: + return yaml.load(f, Loader=Loader) + + +def localize_site_urls(docs, site_url) -> None: + """Load assets that the theme's JavaScript references by absolute site URL from the docset. + + The privacy plugin rewrites the mermaid loader to "assets/external/unpkg.com/mermaid@11/...", so the + docset would fetch mermaid from the live site. __md_scope is the site root that Material defines in every page. + """ + pattern = re.compile(r'"' + re.escape(site_url) + r'(assets/[^"]*)"') + for base, _, files in os.walk(docs): + for file in (f for f in files if f.endswith('.js')): + path = os.path.join(base, file) + text = read(path) + new = pattern.sub(r'new URL("\1",__md_scope).href', text) + if new != text: + write(path, new) + + +def remote_loads(docs, site_url) -> list: + """Return 'file: url' strings for resources that would be loaded remotely.""" + found = [] + cdn = re.compile(r'https://(?:unpkg\.com|cdn\.jsdelivr\.net|cdnjs\.cloudflare\.com|' + r'fonts\.googleapis\.com|fonts\.gstatic\.com)/[^\s"\'`)\\]*') + css_url = re.compile(r'url\(\s*["\']?((?:https?:)?//[^)"\']+)', re.I) + css_import = re.compile(r'@import\s+(?:url\(\s*)?["\']?((?:https?:)?//[^)"\'; ]+)', re.I) + for base, _, files in os.walk(docs): + for file in files: + path = os.path.join(base, file) + rel = os.path.relpath(path, docs) + urls = [] + if file.endswith('.html'): + text = read(path) + for m in re.finditer(r'<[a-zA-Z][^>]*>', text): + tag = m.group(0) + for name in ('src', 'poster'): + v = attr(tag, name) + if v and is_remote(v): + urls.append(v) + v = attr(tag, 'srcset') + if v: + urls += [c.split()[0] for c in v.split(',') if c.strip() and is_remote(c.strip())] + if re.match(r' int: + entries = build_entries() + if problems: + print('\n'.join(problems), file=sys.stderr) + return 1 + docset = os.path.join(out_dir, DOCSET) + docs = os.path.join(docset, 'Contents', 'Resources', 'Documents') + if os.path.exists(docset): + shutil.rmtree(docset) + shutil.copytree(site, docs) + for icon in ('icon.png', 'icon@2x.png'): + shutil.copy(os.path.join(HERE, icon), docset) + shutil.copy(os.path.join(HERE, 'Info.plist'), os.path.join(docset, 'Contents')) + write_index(entries, os.path.join(docset, 'Contents', 'Resources', 'docSet.dsidx')) + + # patch CSS to hide navigation items and fix spacing + css = glob.glob(os.path.join(docs, 'assets', 'stylesheets', 'main.*.min.css')) + if len(css) != 1: + print(f'generate_docset.py: expected exactly one main.*.min.css, found {len(css)}', file=sys.stderr) + return 1 + with open(css[0], 'a', encoding='utf-8') as f: + f.write(CSS_PATCH) + + patch_titles(docs, entries) + remove_source_widget(docs) + for sitemap in glob.glob(os.path.join(docs, 'sitemap.*')): + os.remove(sitemap) + + # make the docset self-contained + site_url = load_mkdocs_yml()['site_url'] + localize_images(docs) + localize_site_urls(docs, site_url) + remote = remote_loads(docs, site_url) + if remote: + print('generate_docset.py: remote resources remain in the docset:', file=sys.stderr) + print('\n'.join(' ' + r for r in remote), file=sys.stderr) + return 1 + return 0 + + +def make_tgz(out_dir) -> int: + docset = os.path.join(out_dir, DOCSET) + if not os.path.isdir(docset): + print(f'generate_docset.py: {docset} does not exist', file=sys.stderr) + return 1 + with tarfile.open(os.path.join(out_dir, 'JSON_for_Modern_C++.tgz'), 'w:gz') as tar: + tar.add(docset, arcname=DOCSET, filter=lambda i: None if os.path.basename(i.name) == '.DS_Store' else i) + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest='command', required=True) + sub.add_parser('list', help='print the index entries as TSV') + p = sub.add_parser('index', help='write the SQLite search index') + p.add_argument('out') + p = sub.add_parser('docset', help='build the docset from a built mkdocs site') + p.add_argument('site_dir') + p.add_argument('out_dir') + p = sub.add_parser('tgz', help='pack the docset into a tarball') + p.add_argument('out_dir') + args = parser.parse_args() + + if args.command == 'docset': + return make_docset(args.site_dir, args.out_dir) + if args.command == 'tgz': + return make_tgz(args.out_dir) + + entries = build_entries() + if problems: + print('\n'.join(problems), file=sys.stderr) + return 1 + if args.command == 'list': + for entry in entries: + print('\t'.join(entry)) + elif args.command == 'index': + write_index(entries, args.out) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/docs/mkdocs/Makefile b/docs/mkdocs/Makefile index 55ee0d509..c1a14e617 100644 --- a/docs/mkdocs/Makefile +++ b/docs/mkdocs/Makefile @@ -17,6 +17,7 @@ build: style_check style_check: @cd docs ; ../venv/bin/python3 ../scripts/check_structure.py + @venv/bin/python3 ../docset/generate_docset.py list > /dev/null # check that all Mermaid diagrams parse (needs Node.js) # This target is used in the CI (ci_test_documentation_mermaid). diff --git a/docs/mkdocs/scripts/check_structure.py b/docs/mkdocs/scripts/check_structure.py index dd76dbab0..c4ecddba3 100755 --- a/docs/mkdocs/scripts/check_structure.py +++ b/docs/mkdocs/scripts/check_structure.py @@ -288,36 +288,6 @@ def check_header_links() -> None: f'link to "{match.group(0)}" does not point to a documentation page') -def check_docset() -> None: - """Every API page and every macro has an entry in the docset index; no entry points to a missing page.""" - entry_re = re.compile(r"VALUES \('((?:[^']|'')*)', '(\w+)', '([^']*)'\);") - names_by_path = {} - with open("../../docset/docSet.sql", encoding="utf-8") as sql: - for name, _, path in entry_re.findall(sql.read()): - names_by_path.setdefault(path, set()).add(name.replace("''", "'")) - - def to_path(page): - if os.path.basename(page) == "index.md": - return page[:-len("index.md")] + "index.html" - return page[:-len(".md")] + "/index.html" - - pages = sorted(glob.glob("**/*.md", recursive=True)) - for path in sorted(set(names_by_path) - {to_path(p) for p in pages}): - report("docset/stale_entry", "../../docset/docSet.sql", f'entry "{path}" has no documentation page') - for page in (p for p in pages if p.startswith("api/")): - names = names_by_path.get(to_path(page)) - if not names: - report("docset/missing_entry", page, "page has no entry in docs/docset/docSet.sql") - elif page.startswith("api/macros/") and os.path.basename(page) != "index.md": - with open(page, encoding="utf-8") as content: - text = content.read() - match = re.search(r"^# (.+)$", text, re.MULTILINE) or re.search(r"

(.*?)

", text, re.DOTALL) - title = re.sub(r"<[^>]+>|\s+", " ", match.group(1)) - for macro in filter(None, (x.strip() for x in re.split(r"[,/]", title))): - if macro not in names: - report("docset/missing_macro", page, f'macro "{macro}" has no entry in docs/docset/docSet.sql') - - if __name__ == "__main__": print(120 * "-") check_structure() @@ -327,7 +297,6 @@ if __name__ == "__main__": check_heading_levels() check_image_alt_text() check_header_links() - check_docset() print(120 * "-") if warnings > 0: