From 87badfba06eca3994644ecf54ae2476e4700d205 Mon Sep 17 00:00:00 2001 From: ansible-middleware-core Date: Fri, 24 Apr 2026 14:51:44 +0000 Subject: [PATCH] Update docs for main Signed-off-by: ansible-middleware-core --- main/.buildinfo | 2 +- main/CHANGELOG.html | 732 +++++------ main/README.html | 31 +- .../keycloak_client_scope.yml | 48 + .../keycloak_authentication_flow.yml | 27 + main/_sources/CHANGELOG.rst.txt | 5 + main/_sources/README.md.txt | 16 +- main/_sources/plugins/index.rst.txt | 2 + .../keycloak_authentication_flow.rst.txt | 621 ++++++++++ .../plugins/keycloak_client_scope.rst.txt | 632 ++++++++++ main/_sources/roles/keycloak_realm.md.txt | 57 +- main/_static/base-stemmer.js | 476 ++++++++ main/_static/doctools.js | 11 +- main/_static/english-stemmer.js | 1066 +++++++++++++++++ main/_static/language_data.js | 193 +-- main/_static/searchtools.js | 172 ++- main/_static/sphinx_highlight.js | 67 +- main/developing.html | 4 +- main/genindex.html | 4 +- main/index.html | 6 +- main/objects.inv | Bin 770 -> 884 bytes main/plugins/index.html | 32 +- .../plugins/keycloak_authentication_flow.html | 622 ++++++++++ main/plugins/keycloak_client.html | 14 +- main/plugins/keycloak_client_scope.html | 633 ++++++++++ main/plugins/keycloak_realm.html | 10 +- main/plugins/keycloak_role.html | 6 +- main/plugins/keycloak_user_federation.html | 6 +- main/releasing.html | 4 +- main/roles/index.html | 5 +- main/roles/keycloak.html | 4 +- main/roles/keycloak_quarkus.html | 4 +- main/roles/keycloak_realm.html | 74 +- main/search.html | 4 +- main/searchindex.js | 2 +- main/testing.html | 4 +- 36 files changed, 4904 insertions(+), 692 deletions(-) create mode 100644 main/_downloads/7a1ffec81340e26b924b5291048874ac/keycloak_client_scope.yml create mode 100644 main/_downloads/bf30f2df147c4911eb753f8cef6007e1/keycloak_authentication_flow.yml create mode 100644 main/_sources/plugins/keycloak_authentication_flow.rst.txt create mode 100644 main/_sources/plugins/keycloak_client_scope.rst.txt create mode 100644 main/_static/base-stemmer.js create mode 100644 main/_static/english-stemmer.js create mode 100644 main/plugins/keycloak_authentication_flow.html create mode 100644 main/plugins/keycloak_client_scope.html diff --git a/main/.buildinfo b/main/.buildinfo index a0d024e..adb6965 100644 --- a/main/.buildinfo +++ b/main/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file records the configuration used when building these files. When it is not found, a full rebuild will be done. -config: e985ada2ce50785bca1109d72ad71d01 +config: d467fc61b229e225ee7a5201e1d13e3b tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/main/CHANGELOG.html b/main/CHANGELOG.html index c7e6123..705d527 100644 --- a/main/CHANGELOG.html +++ b/main/CHANGELOG.html @@ -16,8 +16,8 @@ - - + + @@ -52,39 +52,40 @@
  • Changelog @@ -281,231 +282,232 @@
    -

    middleware_automation.keycloak Release Notes

    +

    middleware_automation.keycloak Release Notes

    This changelog describes changes after version 0.2.6.

    -

    v3.0.4-devel

    +

    v3.0.4-devel

    -

    Major Changes

    +

    Major Changes

    • AMW-467 Download keycloak binary from password protected HTTP location #321

    • v26.4.x compability #317

    +
    +

    Minor Changes

    +
      +
    • AMW-518 Validating arguments against arg spec ‘main’ fails unexpectedly. #324

    • +
    +
    -

    Bugfixes

    +

    Bugfixes

    • Removing parseable from lint file as Additional properties are not allowed #319

    -

    v3.0.3

    -
    -

    Major Changes

    +

    v3.0.3

    +
    +

    Major Changes

    • Update to keycloak 26.3.0 #293

    • ansible-core 2.19 compatibility #310

    -
    -

    Minor Changes

    +
    +

    Minor Changes

    • Allow to install provider jars from remote paths #303

    • Declared proxy_mode as deprecated, updated quarkus and realm readme #306

    • Fix config_key_store_file description to match variable name #308

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • keycloak collection CI label is showing no status #312

    • keycloak_realm: allow secret in keycloak_clients #304

    • @@ -555,9 +563,9 @@
    -

    v3.0.2

    -
    -

    Minor Changes

    +

    v3.0.2

    +
    +

    Minor Changes

    • New checksum property for keycloak_quarkus_providers #280

    • New parameter to set the jgroups host IP address #281

    • @@ -565,8 +573,8 @@
    • Update keycloak/RHBK to v26.2.4 #283

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Fix keycloak_quarkus_force_install parameter being ignored by install #296

    • Fix alternate download location being ignored (JBossNeworkAPI always used) #298

    • @@ -577,39 +585,39 @@
    -

    v3.0.1

    -
    -

    Minor Changes

    +

    v3.0.1

    +
    +

    Minor Changes

    • Version update to 26.0.8 / rhbk 26.0.11 #277

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Trigger rebuild handler on envvars file change #276

    -

    v3.0.0

    -
    -

    Minor Changes

    +

    v3.0.0

    +
    +

    Minor Changes

    • Add theme cache invalidation handler #252

    • keycloak_realm: change url variables to defaults #268

    -

    Breaking Changes / Porting Guide

    +

    Breaking Changes / Porting Guide

    • Bump major and ansible-core versions #266

    • Rename parameters to follow upstream #270

    • Update for keycloak v26 #254

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Access token lifespan is too short for ansible run #251

    • Load environment vars during kc rebuild #274

    • @@ -618,47 +626,47 @@
    -

    New Modules

    +

    New Modules

    • middleware_automation.keycloak.keycloak_realm - Allows administration of Keycloak realm via Keycloak API

    -

    v2.4.3

    -
    -

    Minor Changes

    +

    v2.4.3

    +
    +

    Minor Changes

    • Update keycloak to 24.0.5 #241

    -

    v2.4.2

    -
    -

    Minor Changes

    +

    v2.4.2

    +
    +

    Minor Changes

    • New parameter keycloak_quarkus_download_path #239

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Add wait_for_port number parameter #237

    -

    v2.4.1

    +

    v2.4.1

    -

    Release Summary

    +

    Release Summary

    Internal release, documentation or test changes only.

    -

    v2.4.0

    -
    -

    Major Changes

    +

    v2.4.0

    +
    +

    Major Changes

    • Enable by default health check on restart #234

    • Update minimum ansible-core version > 2.15 #232

    • @@ -666,16 +674,16 @@
    -

    v2.3.0

    -
    -

    Major Changes

    +

    v2.3.0

    +
    +

    Major Changes

    • Allow for custom providers hosted on maven repositories #223

    • Restart handler strategy behaviour #231

    -
    -

    Minor Changes

    +
    +

    Minor Changes

    • Add support for policy files #225

    • Allow to add extra custom env vars in sysconfig file #229

    • @@ -684,52 +692,52 @@
    • proxy-header enhancement #227

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • kc.sh build uses configured jdk #211

    -

    v2.2.2

    -
    -

    Minor Changes

    +

    v2.2.2

    +
    +

    Minor Changes

    • Copying of key material for TLS configuration #210

    • Validate certs parameter for JDBC driver downloads #207

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Turn off controller privilege escalation #209

    -

    v2.2.1

    -
    -

    Release Summary

    +

    v2.2.1

    +
    +

    Release Summary

    Internal release, documentation or test changes only.

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • JDBC provider: fix clause in argument validation #204

    -

    v2.2.0

    -
    -

    Major Changes

    +

    v2.2.0

    +
    +

    Major Changes

    • Support java keystore for configuration of sensitive options #189

    -
    -

    Minor Changes

    +
    +

    Minor Changes

    • Add wait_for_port and wait_for_log systemd unit logic #199

    • Customize jdbc driver downloads, optional authentication #202

    • @@ -742,24 +750,24 @@
    -

    v2.1.2

    -
    -

    Release Summary

    +

    v2.1.2

    +
    +

    Release Summary

    Internal release, documentation or test changes only.

    -

    v2.1.1

    -
    -

    Minor Changes

    +

    v2.1.1

    +
    +

    Minor Changes

    • Add reverse proxy_headers config, supersedes proxy_mode #187

    • Debian/Ubuntu compatibility #178

    • Use keycloak_realm as default for sub-entities #180

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Fix permissions on controller-side downloaded artifacts #184

    • JVM args moved to JAVA_OPTS envvar (instead of JAVA_OPTS_APPEND) #186

    • @@ -769,46 +777,46 @@
    -

    v2.1.0

    -
    -

    Major Changes

    +

    v2.1.0

    +
    +

    Major Changes

    • Implement infinispan TCPPING discovery protocol #159

    -
    -

    Minor Changes

    +
    +

    Minor Changes

    • Set enable-recovery when xa transactions are enabled #167

    • keycloak_quarkus: Allow configuring log rotate options in quarkus configuration #161

    • keycloak_quarkus: sticky-session for infinispan routes #163

    -
    -

    Breaking Changes / Porting Guide

    +
    +

    Breaking Changes / Porting Guide

    • keycloak_quarkus: renamed infinispan host list configuration #157

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • keycloak_quarkus: fix custom JAVA_HOME parameter name #171

    -

    v2.0.2

    -
    -

    Minor Changes

    +

    v2.0.2

    +
    +

    Minor Changes

    • keycloak_quarkus: Add support for sqlserver jdbc driver #148

    • keycloak_quarkus: allow configuration of hostname-strict-backchannel #152

    • keycloak_quarkus: systemd restart behavior #145

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • keycloak_quarkus: Use keycloak_quarkus_java_opts #154

    • keycloak_quarkus: allow ports <1024 (e.g. :443) in systemd unit #150

    • @@ -816,32 +824,32 @@
    -

    v2.0.1

    -
    -

    Minor Changes

    +

    v2.0.1

    +
    +

    Minor Changes

    • keycloak_quarkus: add hostname-strict parameter #139

    • keycloak_quarkus: update to version 23.0.1 #133

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • keycloak_quarkus: template requires lowercase boolean values #138

    -

    v2.0.0

    -
    -

    Minor Changes

    +

    v2.0.0

    +
    +

    Minor Changes

    • Add new parameter for port offset configuration #124

    • Update Keycloak to version 22.0.5 #122

    -
    -

    Breaking Changes / Porting Guide

    +
    +

    Breaking Changes / Porting Guide

    • Add support for more http-related configs #115

    • Update minimum ansible-core version > 2.14 #119

    • @@ -850,39 +858,39 @@
    -

    v1.3.0

    -
    -

    Major Changes

    +

    v1.3.0

    +
    +

    Major Changes

    • Run service as keycloak_service_user #106

    -
    -

    Minor Changes

    +
    +

    Minor Changes

    • keycloak_quarkus: Update Keycloak to version 22.0.3 #112

    • keycloak_quarkus: fix admin console redirect when running locally #111

    • keycloak_quarkus: skip proxy config if keycloak_quarkus_proxy_mode is none #109

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • keycloak_quarkus: fix validation failure upon port configuration change #113

    -

    v1.2.8

    -
    -

    Minor Changes

    +

    v1.2.8

    +
    +

    Minor Changes

    • keycloak_quarkus: set openjdk 17 as default #103

    • keycloak_quarkus: update to version 22.0.1 #107

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Fix incorrect checks for keycloak_jgroups_subnet #98

    • Undefine keycloak_db_valid_conn_sql default #91

    • @@ -891,9 +899,9 @@
    -

    v1.2.7

    -
    -

    Minor Changes

    +

    v1.2.7

    +
    +

    Minor Changes

    • Allow to override jgroups subnet #93

    • keycloak-quarkus: update keycloakx to v21.1.1 #92

    • @@ -901,26 +909,26 @@
    -

    v1.2.6

    -
    -

    Minor Changes

    +

    v1.2.6

    +
    +

    Minor Changes

    • Add profile features enabling/disabling #87

    • Improve service restart behavior configuration #88

    • Update default xa_datasource_class value for mariadb jdbc configuration #89

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Handle WFLYCTL0117 when background validation millis is 0 #90

    -

    v1.2.5

    -
    -

    Minor Changes

    +

    v1.2.5

    +
    +

    Minor Changes

    • Add configuration for database connection pool validation #85

    • Allow to configure administration endpoint URL #86

    • @@ -930,16 +938,16 @@
    -

    v1.2.4

    -
    -

    Minor Changes

    +

    v1.2.4

    +
    +

    Minor Changes

    • Add sqlserver to keycloak role jdbc configurations #78

    • Add configurability for XA transactions #73

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Fix deprecation warning for ipaddr #77

    • Fix undefined facts when offline patching sso #71

    • @@ -947,30 +955,30 @@
    -

    v1.2.1

    -
    -

    Minor Changes

    +

    v1.2.1

    +
    +

    Minor Changes

    • Allow to setup keycloak HA cluster without remote cache store #68

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Pass attributes to realm clients #69

    -

    v1.2.0

    -
    -

    Major Changes

    +

    v1.2.0

    +
    +

    Major Changes

    • Provide config for multiple modcluster proxies #60

    -
    -

    Minor Changes

    +
    +

    Minor Changes

    • Allow to configure TCPPING for cluster discovery #62

    • Drop community.general from dependencies #61

    • @@ -980,18 +988,18 @@
    -

    v1.1.1

    -
    -

    Bugfixes

    +

    v1.1.1

    +
    +

    Bugfixes

    • keycloak-quarkus: fix cache-config-file path in keycloak.conf.j2 template #53

    -

    v1.1.0

    -
    -

    Minor Changes

    +

    v1.1.0

    +
    +

    Minor Changes

    • Update keycloak to 18.0.2 - sso to 7.6.1 #46

    • Variable keycloak_no_log controls ansible no_log parameter (for debugging purposes) #47

    • @@ -999,38 +1007,38 @@
    • keycloak_quarkus: variable to enable development mode #45

    -
    -

    Breaking Changes / Porting Guide

    +
    +

    Breaking Changes / Porting Guide

    • Rename variables from infinispan_ prefix to keycloak_infinispan_ #42

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • keycloak_quarkus: fix /var/log/keycloak symlink to keycloak log directory #44

    -

    v1.0.7

    -
    -

    Breaking Changes / Porting Guide

    +

    v1.0.7

    +
    +

    Breaking Changes / Porting Guide

    • keycloak_quarkus: use absolute path for certificate files #39

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • keycloak_quarkus: use become for tasks that will otherwise fail #38

    -

    v1.0.6

    -
    -

    Bugfixes

    +

    v1.0.6

    +
    +

    Bugfixes

    • keycloak_quarkus: add selected java to PATH in systemd unit #34

    • keycloak_quarkus: set logfile path correctly under keycloak home #35

    • @@ -1038,81 +1046,81 @@
    -

    v1.0.5

    -
    -

    Minor Changes

    +

    v1.0.5

    +
    +

    Minor Changes

    • Update config options: keycloak and quarkus #32

    -

    v1.0.4

    -
    -

    Release Summary

    +

    v1.0.4

    +
    +

    Release Summary

    Internal release, documentation or test changes only.

    -

    v1.0.3

    -
    -

    Major Changes

    +

    v1.0.3

    +
    +

    Major Changes

    • New role for installing keycloak >= 17.0.0 (quarkus) #29

    -
    -

    Minor Changes

    +
    +

    Minor Changes

    • Add keycloak_config_override_template parameter for passing a custom xml config template #30

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Make sure systemd unit starts with selected java JVM #31

    -

    v1.0.2

    -
    -

    Minor Changes

    +

    v1.0.2

    +
    +

    Minor Changes

    • Make keycloak_admin_password a default with assert (was: role variable) #26

    • Simplify dependency install logic and reduce play execution time #19

    -
    -

    Bugfixes

    +
    +

    Bugfixes

    • Set keycloak_frontend_url default according to other defaults #25

    -

    v1.0.1

    -
    -

    Release Summary

    +

    v1.0.1

    +
    +

    Release Summary

    Minor enhancements, bug and documentation fixes.

    -
    -

    Major Changes

    +
    +

    Major Changes

    • Apply latest cumulative patch of RH-SSO automatically when new parameter keycloak_rhsso_apply_patches is true #18

    -
    -

    Minor Changes

    +
    +

    Minor Changes

    • Clustered installs now perform database initialization on first node to avoid locking issues #17

    -

    v1.0.0

    -
    -

    Release Summary

    +

    v1.0.0

    +
    +

    Release Summary

    This is the first stable release of the middleware_automation.keycloak collection.

    diff --git a/main/README.html b/main/README.html index 137050a..545c788 100644 --- a/main/README.html +++ b/main/README.html @@ -16,8 +16,8 @@ - - + + @@ -51,6 +51,7 @@
  • Installation
  • Usage
      @@ -63,7 +64,7 @@
  • Configuration
  • @@ -171,6 +172,17 @@
    +
    +

    Included modules

    +
      +
    • keycloak_realm: module for managing Keycloak realms (create/update/delete).

    • +
    • keycloak_client: module for managing Keycloak clients (create/update/delete).

    • +
    • keycloak_role: module for managing Keycloak roles — realm roles and client roles (create/update/delete).

    • +
    • keycloak_user_federation: module for managing user federations such as LDAP/AD (create/update/delete).

    • +
    • keycloak_client_scope: module for managing client scopes and protocol mappers (create/update/delete).

    • +
    • keycloak_authentication_flow: module for managing authentication flows and execution steps (create/delete, copy existing flows).

    • +
    +

    Usage

    @@ -219,11 +231,18 @@ the download tasks. The local path for the archive does match the downloaded arc

    Configuration

    -
    -

    Config Playbook

    +
    +

    Config Playbooks

    -

    playbooks/keycloak_realm.yml creates or updates provided realm, user federation(s), client(s), client role(s) and client user(s).

    + +

    Example configuration command

    diff --git a/main/_downloads/7a1ffec81340e26b924b5291048874ac/keycloak_client_scope.yml b/main/_downloads/7a1ffec81340e26b924b5291048874ac/keycloak_client_scope.yml new file mode 100644 index 0000000..aa5ed3d --- /dev/null +++ b/main/_downloads/7a1ffec81340e26b924b5291048874ac/keycloak_client_scope.yml @@ -0,0 +1,48 @@ +--- +- name: Playbook for Keycloak Client Scope Configuration + hosts: all + vars: + keycloak_admin_user: admin + keycloak_admin_password: "remembertochangeme" + keycloak_url: "http://localhost:8080" + keycloak_realm: TestRealm + tasks: + - name: Create client scope with protocol mappers + middleware_automation.keycloak.keycloak_client_scope: + auth_keycloak_url: "{{ keycloak_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + name: TestClientScope + description: "Client scope created via Ansible" + protocol: openid-connect + protocol_mappers: + - name: email + protocolMapper: oidc-usermodel-attribute-mapper + config: + user.attribute: email + claim.name: email + jsonType.label: String + id.token.claim: "true" + access.token.claim: "true" + userinfo.token.claim: "true" + - name: firstName + protocolMapper: oidc-usermodel-attribute-mapper + config: + user.attribute: firstName + claim.name: given_name + jsonType.label: String + id.token.claim: "true" + access.token.claim: "true" + userinfo.token.claim: "true" + - name: username + protocolMapper: oidc-usermodel-attribute-mapper + config: + user.attribute: username + claim.name: preferred_username + jsonType.label: String + id.token.claim: "true" + access.token.claim: "true" + userinfo.token.claim: "true" + state: present diff --git a/main/_downloads/bf30f2df147c4911eb753f8cef6007e1/keycloak_authentication_flow.yml b/main/_downloads/bf30f2df147c4911eb753f8cef6007e1/keycloak_authentication_flow.yml new file mode 100644 index 0000000..38878b5 --- /dev/null +++ b/main/_downloads/bf30f2df147c4911eb753f8cef6007e1/keycloak_authentication_flow.yml @@ -0,0 +1,27 @@ +--- +- name: Playbook for Keycloak Authentication Flow Configuration + hosts: all + vars: + keycloak_admin_user: admin + keycloak_admin_password: "remembertochangeme" + keycloak_url: "http://localhost:8080" + keycloak_realm: TestRealm + tasks: + - name: Create authentication flow with executions + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: "{{ keycloak_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + alias: my-browser-flow + description: "Custom browser authentication flow" + provider_id: basic-flow + executions: + - provider_id: auth-cookie + requirement: ALTERNATIVE + - provider_id: auth-password + requirement: REQUIRED + - provider_id: auth-otp-form + requirement: ALTERNATIVE + state: present diff --git a/main/_sources/CHANGELOG.rst.txt b/main/_sources/CHANGELOG.rst.txt index f305f2a..e27f049 100644 --- a/main/_sources/CHANGELOG.rst.txt +++ b/main/_sources/CHANGELOG.rst.txt @@ -15,6 +15,11 @@ Major Changes - AMW-467 Download keycloak binary from password protected HTTP location `#321 `_ - v26.4.x compability `#317 `_ +Minor Changes +------------- + +- AMW-518 Validating arguments against arg spec 'main' fails unexpectedly. `#324 `_ + Bugfixes -------- diff --git a/main/_sources/README.md.txt b/main/_sources/README.md.txt index 11c55f9..5afa582 100644 --- a/main/_sources/README.md.txt +++ b/main/_sources/README.md.txt @@ -55,6 +55,15 @@ A requirement file is provided to install: +### Included modules + +* `keycloak_realm`: module for managing Keycloak realms (create/update/delete). +* `keycloak_client`: module for managing Keycloak clients (create/update/delete). +* `keycloak_role`: module for managing Keycloak roles — realm roles and client roles (create/update/delete). +* `keycloak_user_federation`: module for managing user federations such as LDAP/AD (create/update/delete). +* `keycloak_client_scope`: module for managing client scopes and protocol mappers (create/update/delete). +* `keycloak_authentication_flow`: module for managing authentication flows and execution steps (create/delete, copy existing flows). + ## Usage @@ -109,10 +118,13 @@ Note: when deploying clustered configurations, all hosts belonging to the cluste ## Configuration -### Config Playbook +### Config Playbooks -[`playbooks/keycloak_realm.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_realm.yml) creates or updates provided realm, user federation(s), client(s), client role(s) and client user(s). +* [`playbooks/keycloak_realm.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_realm.yml) creates or updates provided realm, user federation(s), client(s), client role(s) and client user(s). +* [`playbooks/keycloak_realm_client.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_realm_client.yml) creates a realm with clients, roles and users using the `keycloak_realm` role. +* [`playbooks/keycloak_client_scope.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_client_scope.yml) creates a client scope with protocol mappers using the `keycloak_client_scope` module. +* [`playbooks/keycloak_authentication_flow.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_authentication_flow.yml) creates a custom authentication flow with execution steps using the `keycloak_authentication_flow` module. ### Example configuration command diff --git a/main/_sources/plugins/index.rst.txt b/main/_sources/plugins/index.rst.txt index 6b49178..2583b6d 100644 --- a/main/_sources/plugins/index.rst.txt +++ b/main/_sources/plugins/index.rst.txt @@ -5,7 +5,9 @@ Plugin Index :caption: modules plugins :maxdepth: 0 + keycloak_authentication_flow keycloak_client + keycloak_client_scope keycloak_realm keycloak_role keycloak_user_federation diff --git a/main/_sources/plugins/keycloak_authentication_flow.rst.txt b/main/_sources/plugins/keycloak_authentication_flow.rst.txt new file mode 100644 index 0000000..728939e --- /dev/null +++ b/main/_sources/plugins/keycloak_authentication_flow.rst.txt @@ -0,0 +1,621 @@ +.. Document meta + +:orphan: + +.. |antsibull-internal-nbsp| unicode:: 0xA0 + :trim: + +.. meta:: + :antsibull-docs: 2.24.0 + +.. Anchors + +.. _ansible_collections.middleware_automation.keycloak.keycloak_authentication_flow_module: + +.. Anchors: short name for ansible.builtin + +.. Title + +keycloak_authentication_flow -- Allows administration of Keycloak authentication flows via Keycloak API ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. Collection note + +.. note:: + This module is part of the `middleware_automation.keycloak collection `_. + + It is not included in ``ansible-core``. + To check whether it is installed, run :code:`ansible-galaxy collection list`. + + To install it, use: :code:`ansible\-galaxy collection install middleware\_automation.keycloak`. + + To use it in a playbook, specify: :code:`middleware_automation.keycloak.keycloak_authentication_flow`. + +.. version_added + + +.. contents:: + :local: + :depth: 1 + +.. Deprecated + + +Synopsis +-------- + +.. Description + +- This module allows you to add, remove or modify Keycloak authentication flows via the Keycloak REST API. It requires access to the REST API via OpenID Connect; the user connecting and the client being used must have the requisite access rights. In a default Keycloak installation, admin\-cli and an admin user would work, as would a separate client definition with the scope tailored to your needs and a user having the expected roles. +- This module supports creating new top\-level authentication flows, copying existing flows, and adding execution steps to a flow. + + +.. Aliases + + +.. Requirements + + + + + + +.. Options + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Parameter

    Comments

    +
    +

    alias

    + +

    + string + / required +

    +
    +

    Alias (name) of the authentication flow.

    +
    +
    +

    auth_client_id

    + +

    + string +

    +
    +

    OpenID Connect client_id to authenticate to the API with.

    +

    Default: "admin-cli"

    +
    +
    +

    auth_client_secret

    + +

    + string +

    +
    +

    Client Secret to use in conjunction with auth_client_id (if required).

    +
    +
    +
    +

    auth_keycloak_url

    + +

    aliases: url

    +

    + string + / required +

    +
    +

    URL to the Keycloak instance.

    +
    +
    +
    +

    auth_password

    + +

    aliases: password

    +

    + string +

    +
    +

    Password to authenticate for API access with.

    +
    +
    +

    auth_realm

    + +

    + string +

    +
    +

    Keycloak realm name to authenticate to for API access.

    +
    +
    +
    +

    auth_username

    + +

    aliases: username

    +

    + string +

    +
    +

    Username to authenticate for API access with.

    +
    +
    +

    connection_timeout

    + +

    + integer +

    +

    added in middleware_automation.keycloak 4.5.0

    +
    +

    Controls the HTTP connections timeout period (in seconds) to Keycloak API.

    +

    Default: 10

    +
    +
    +
    +

    copy_from

    + +

    aliases: copyFrom

    +

    + string +

    +
    +

    If set, the new flow is created as a copy of the flow with this alias.

    +

    Cannot be used together with executions.

    +
    +
    +

    description

    + +

    + string +

    +
    +

    Description of the authentication flow.

    +

    Default: ""

    +
    +
    +

    executions

    + +

    + list + / elements=dictionary +

    +
    +

    A list of executions (authenticator steps) to add to the flow.

    +

    Each execution is a dict with keys provider_id (or providerId) and requirement.

    +

    Executions are only added when the flow is first created.

    +

    Default: []

    +
    +
    +
    +

    provider_id

    + +

    aliases: providerId

    +

    + string + / required +

    +
    +

    The authenticator provider ID (e.g. auth-cookie, auth-password, auth-otp-form).

    +
    +
    +

    requirement

    + +

    + string + / required +

    +
    +

    The requirement level for this execution.

    +

    Choices:

    +
      +
    • "REQUIRED"

    • +
    • "ALTERNATIVE"

    • +
    • "DISABLED"

    • +
    • "CONDITIONAL"

    • +
    + +
    +
    +

    http_agent

    + +

    + string +

    +

    added in middleware_automation.keycloak 5.4.0

    +
    +

    Configures the HTTP User-Agent header.

    +

    Default: "Ansible"

    +
    +
    +
    +

    provider_id

    + +

    aliases: providerId

    +

    + string +

    +
    +

    The provider ID for the flow.

    +

    Default: "basic-flow"

    +
    +
    +

    realm

    + +

    + string +

    +
    +

    The Keycloak realm under which this authentication flow resides.

    +

    Default: "master"

    +
    +
    +

    state

    + +

    + string +

    +
    +

    State of the authentication flow.

    +

    On present, the flow will be created if it does not yet exist.

    +

    On absent, the flow will be removed if it exists.

    +

    Choices:

    +
      +
    • "present" ← (default)

    • +
    • "absent"

    • +
    + +
    +
    +

    token

    + +

    + string +

    +

    added in middleware_automation.keycloak 3.0.0

    +
    +

    Authentication token for Keycloak API.

    +
    +
    +

    validate_certs

    + +

    + boolean +

    +
    +

    Verify TLS certificates (do not disable this in production).

    +

    Choices:

    +
      +
    • false

    • +
    • true ← (default)

    • +
    + +
    + + + +.. Attributes + + +Attributes +---------- + +.. tabularcolumns:: \X{2}{10}\X{3}{10}\X{5}{10} + +.. list-table:: + :width: 100% + :widths: auto + :header-rows: 1 + :class: longtable ansible-option-table + + * - Attribute + - Support + - Description + + * - .. raw:: html + +
    +
    + + .. _ansible_collections.middleware_automation.keycloak.keycloak_authentication_flow_module__attribute-check_mode: + + .. rst-class:: ansible-option-title + + **check_mode** + + .. raw:: html + + + + .. raw:: html + +
    + + - .. raw:: html + +
    + + :ansible-attribute-support-label:`Support: \ `\ :ansible-attribute-support-full:`full` + + + .. raw:: html + +
    + + - .. raw:: html + +
    + + Can run in :literal:`check\_mode` and return changed status prediction without modifying target. + + + .. raw:: html + +
    + + + * - .. raw:: html + +
    +
    + + .. _ansible_collections.middleware_automation.keycloak.keycloak_authentication_flow_module__attribute-diff_mode: + + .. rst-class:: ansible-option-title + + **diff_mode** + + .. raw:: html + + + + .. raw:: html + +
    + + - .. raw:: html + +
    + + :ansible-attribute-support-label:`Support: \ `\ :ansible-attribute-support-full:`full` + + + .. raw:: html + +
    + + - .. raw:: html + +
    + + Will return details on what has changed (or possibly needs changing in :literal:`check\_mode`\ ), when in diff mode. + + + .. raw:: html + +
    + + + +.. Notes + + +.. Seealso + + +.. Examples + +Examples +-------- + +.. code-block:: yaml+jinja + + - name: Create an authentication flow with executions + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: password + realm: TestRealm + alias: my-browser-flow + description: "Custom browser flow" + provider_id: basic-flow + executions: + - provider_id: auth-cookie + requirement: ALTERNATIVE + - provider_id: auth-password + requirement: REQUIRED + - provider_id: auth-otp-form + requirement: ALTERNATIVE + state: present + delegate_to: localhost + + - name: Create an authentication flow by copying an existing one + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: password + realm: TestRealm + alias: my-copy-of-browser + copy_from: browser + state: present + delegate_to: localhost + + - name: Create a flow using token authentication + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: http://localhost:8080 + token: MY_TOKEN + realm: TestRealm + alias: my-flow + state: present + delegate_to: localhost + + - name: Delete an authentication flow + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: password + realm: TestRealm + alias: my-browser-flow + state: absent + delegate_to: localhost + + + +.. Facts + + +.. Return values + +Return Values +------------- +Common return values are documented :ref:`here `, the following are the fields unique to this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + +

    Key

    Description

    +
    +

    end_state

    + +

    + dictionary +

    +
    +

    Representation of the authentication flow after module execution.

    +

    Returned: on success

    +

    Sample: {"alias": "my-browser-flow", "builtIn": false, "id": "uuid-here", "providerId": "basic-flow", "topLevel": true}

    +
    +
    +

    msg

    + +

    + string +

    +
    +

    Message as to what action was taken.

    +

    Returned: always

    +

    Sample: "Authentication flow my-browser-flow has been created"

    +
    + + + +.. Status (Presently only deprecated) + + +.. Authors + +Authors +~~~~~~~ + +- Paulo Menon (@paulomenon) + + +.. Extra links + + +.. Parsing errors diff --git a/main/_sources/plugins/keycloak_client_scope.rst.txt b/main/_sources/plugins/keycloak_client_scope.rst.txt new file mode 100644 index 0000000..d04c7f2 --- /dev/null +++ b/main/_sources/plugins/keycloak_client_scope.rst.txt @@ -0,0 +1,632 @@ +.. Document meta + +:orphan: + +.. |antsibull-internal-nbsp| unicode:: 0xA0 + :trim: + +.. meta:: + :antsibull-docs: 2.24.0 + +.. Anchors + +.. _ansible_collections.middleware_automation.keycloak.keycloak_client_scope_module: + +.. Anchors: short name for ansible.builtin + +.. Title + +keycloak_client_scope -- Allows administration of Keycloak client scopes via Keycloak API ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. Collection note + +.. note:: + This module is part of the `middleware_automation.keycloak collection `_. + + It is not included in ``ansible-core``. + To check whether it is installed, run :code:`ansible-galaxy collection list`. + + To install it, use: :code:`ansible\-galaxy collection install middleware\_automation.keycloak`. + + To use it in a playbook, specify: :code:`middleware_automation.keycloak.keycloak_client_scope`. + +.. version_added + + +.. contents:: + :local: + :depth: 1 + +.. Deprecated + + +Synopsis +-------- + +.. Description + +- This module allows you to add, remove or modify Keycloak client scopes via the Keycloak REST API. It requires access to the REST API via OpenID Connect; the user connecting and the client being used must have the requisite access rights. In a default Keycloak installation, admin\-cli and an admin user would work, as would a separate client definition with the scope tailored to your needs and a user having the expected roles. +- This module also supports managing protocol mappers within a client scope. + + +.. Aliases + + +.. Requirements + + + + + + +.. Options + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Parameter

    Comments

    +
    +

    attributes

    + +

    + dictionary +

    +
    +

    A dict of key/value pairs to set as attributes for the client scope.

    +
    +
    +

    auth_client_id

    + +

    + string +

    +
    +

    OpenID Connect client_id to authenticate to the API with.

    +

    Default: "admin-cli"

    +
    +
    +

    auth_client_secret

    + +

    + string +

    +
    +

    Client Secret to use in conjunction with auth_client_id (if required).

    +
    +
    +
    +

    auth_keycloak_url

    + +

    aliases: url

    +

    + string + / required +

    +
    +

    URL to the Keycloak instance.

    +
    +
    +
    +

    auth_password

    + +

    aliases: password

    +

    + string +

    +
    +

    Password to authenticate for API access with.

    +
    +
    +

    auth_realm

    + +

    + string +

    +
    +

    Keycloak realm name to authenticate to for API access.

    +
    +
    +
    +

    auth_username

    + +

    aliases: username

    +

    + string +

    +
    +

    Username to authenticate for API access with.

    +
    +
    +

    connection_timeout

    + +

    + integer +

    +

    added in middleware_automation.keycloak 4.5.0

    +
    +

    Controls the HTTP connections timeout period (in seconds) to Keycloak API.

    +

    Default: 10

    +
    +
    +

    description

    + +

    + string +

    +
    +

    Description of the client scope.

    +

    Default: ""

    +
    +
    +

    http_agent

    + +

    + string +

    +

    added in middleware_automation.keycloak 5.4.0

    +
    +

    Configures the HTTP User-Agent header.

    +

    Default: "Ansible"

    +
    +
    +

    name

    + +

    + string + / required +

    +
    +

    Name of the client scope.

    +
    +
    +

    protocol

    + +

    + string +

    +
    +

    The protocol associated with the client scope.

    +

    Choices:

    +
      +
    • "openid-connect" ← (default)

    • +
    • "saml"

    • +
    + +
    +
    +

    protocol_mappers

    + +

    + list + / elements=dictionary +

    +
    +

    A list of protocol mappers to associate with the client scope.

    +

    Each mapper is a dict with the keys name, protocol, protocolMapper, and config.

    +

    Default: []

    +
    +
    +

    config

    + +

    + dictionary + / required +

    +
    +

    Configuration for the protocol mapper.

    +
    +
    +

    name

    + +

    + string + / required +

    +
    +

    Name of the protocol mapper.

    +
    +
    +

    protocol

    + +

    + string +

    +
    +

    Protocol for the mapper.

    +

    Default: "openid-connect"

    +
    +
    +
    +

    protocolMapper

    + +

    aliases: protocol_mapper_type

    +

    + string + / required +

    +
    +

    The mapper type (e.g. oidc-usermodel-attribute-mapper, oidc-audience-mapper).

    +
    +
    +

    realm

    + +

    + string +

    +
    +

    The Keycloak realm under which this client scope resides.

    +

    Default: "master"

    +
    +
    +

    state

    + +

    + string +

    +
    +

    State of the client scope.

    +

    On present, the client scope will be created if it does not yet exist, or updated with the parameters you provide.

    +

    On absent, the client scope will be removed if it exists.

    +

    Choices:

    +
      +
    • "present" ← (default)

    • +
    • "absent"

    • +
    + +
    +
    +

    token

    + +

    + string +

    +

    added in middleware_automation.keycloak 3.0.0

    +
    +

    Authentication token for Keycloak API.

    +
    +
    +

    validate_certs

    + +

    + boolean +

    +
    +

    Verify TLS certificates (do not disable this in production).

    +

    Choices:

    +
      +
    • false

    • +
    • true ← (default)

    • +
    + +
    + + + +.. Attributes + + +Attributes +---------- + +.. tabularcolumns:: \X{2}{10}\X{3}{10}\X{5}{10} + +.. list-table:: + :width: 100% + :widths: auto + :header-rows: 1 + :class: longtable ansible-option-table + + * - Attribute + - Support + - Description + + * - .. raw:: html + +
    +
    + + .. _ansible_collections.middleware_automation.keycloak.keycloak_client_scope_module__attribute-check_mode: + + .. rst-class:: ansible-option-title + + **check_mode** + + .. raw:: html + + + + .. raw:: html + +
    + + - .. raw:: html + +
    + + :ansible-attribute-support-label:`Support: \ `\ :ansible-attribute-support-full:`full` + + + .. raw:: html + +
    + + - .. raw:: html + +
    + + Can run in :literal:`check\_mode` and return changed status prediction without modifying target. + + + .. raw:: html + +
    + + + * - .. raw:: html + +
    +
    + + .. _ansible_collections.middleware_automation.keycloak.keycloak_client_scope_module__attribute-diff_mode: + + .. rst-class:: ansible-option-title + + **diff_mode** + + .. raw:: html + + + + .. raw:: html + +
    + + - .. raw:: html + +
    + + :ansible-attribute-support-label:`Support: \ `\ :ansible-attribute-support-full:`full` + + + .. raw:: html + +
    + + - .. raw:: html + +
    + + Will return details on what has changed (or possibly needs changing in :literal:`check\_mode`\ ), when in diff mode. + + + .. raw:: html + +
    + + + +.. Notes + + +.. Seealso + + +.. Examples + +Examples +-------- + +.. code-block:: yaml+jinja + + - name: Create a client scope with protocol mappers + middleware_automation.keycloak.keycloak_client_scope: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: password + realm: TestRealm + name: my-client-scope + description: "A custom client scope" + protocol: openid-connect + protocol_mappers: + - name: email + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + config: + user.attribute: email + claim.name: email + jsonType.label: String + id.token.claim: "true" + access.token.claim: "true" + userinfo.token.claim: "true" + state: present + delegate_to: localhost + + - name: Create a client scope using token authentication + middleware_automation.keycloak.keycloak_client_scope: + auth_keycloak_url: http://localhost:8080 + token: MY_TOKEN + realm: TestRealm + name: my-scope + state: present + delegate_to: localhost + + - name: Delete a client scope + middleware_automation.keycloak.keycloak_client_scope: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: password + realm: TestRealm + name: my-client-scope + state: absent + delegate_to: localhost + + + +.. Facts + + +.. Return values + +Return Values +------------- +Common return values are documented :ref:`here `, the following are the fields unique to this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + +

    Key

    Description

    +
    +

    end_state

    + +

    + dictionary +

    +
    +

    Representation of the client scope after module execution.

    +

    Returned: on success

    +

    Sample: {"description": "A custom scope", "id": "uuid-here", "name": "my-scope", "protocol": "openid-connect"}

    +
    +
    +

    msg

    + +

    + string +

    +
    +

    Message as to what action was taken.

    +

    Returned: always

    +

    Sample: "Client scope my-scope has been created"

    +
    + + + +.. Status (Presently only deprecated) + + +.. Authors + +Authors +~~~~~~~ + +- Paulo Menon (@paulomenon) + + +.. Extra links + + +.. Parsing errors diff --git a/main/_sources/roles/keycloak_realm.md.txt b/main/_sources/roles/keycloak_realm.md.txt index e01c72f..d5056c4 100644 --- a/main/_sources/roles/keycloak_realm.md.txt +++ b/main/_sources/roles/keycloak_realm.md.txt @@ -12,7 +12,7 @@ Role Defaults |:---------|:------------|:--------| |`keycloak_admin_user`| Administration console user account | `admin` | |`keycloak_host`| hostname | `localhost` | -|`keycloak_context`| Context path for rest calls | `/auth` | +|`keycloak_context`| Context path for rest calls (set to `/auth` for legacy WildFly-based Keycloak) | `` | |`keycloak_http_port`| HTTP port | `8080` | |`keycloak_https_port`| TLS HTTP port | `8443` | |`keycloak_auth_realm`| Name of the main authentication realm | `master` | @@ -107,6 +107,20 @@ Refer to [docs](https://docs.ansible.com/ansible/latest/collections/community/ge For a comprehensive example, refer to the [playbook](../../playbooks/keycloak_realm.yml). +Related Modules +--------------- + +For features not covered by this role, the collection provides dedicated modules: + +| Module | What It Manages | +|:-------|:----------------| +| `keycloak_client_scope` | Client scopes and protocol mappers — see [example playbook](../../playbooks/keycloak_client_scope.yml) | +| `keycloak_authentication_flow` | Authentication flows and execution steps — see [example playbook](../../playbooks/keycloak_authentication_flow.yml) | +| `keycloak_client` | Clients (also used internally by this role) | +| `keycloak_role` | Realm and client roles | +| `keycloak_user_federation` | User federations such as LDAP (also used internally by this role) | + + Example Playbook ---------------- @@ -127,6 +141,47 @@ The following is an example playbook that makes use of the role to create a real keycloak_clients: [...] ``` +The following example uses the `keycloak_client_scope` module to create a client scope with protocol mappers: + +```yaml +- name: Create client scope + middleware_automation.keycloak.keycloak_client_scope: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: changeme + realm: TestRealm + name: my-scope + protocol_mappers: + - name: email + protocolMapper: oidc-usermodel-attribute-mapper + config: + user.attribute: email + claim.name: email + id.token.claim: "true" + access.token.claim: "true" + state: present +``` + +The following example uses the `keycloak_authentication_flow` module to create a custom authentication flow: + +```yaml +- name: Create authentication flow + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: changeme + realm: TestRealm + alias: my-browser-flow + executions: + - provider_id: auth-cookie + requirement: ALTERNATIVE + - provider_id: auth-password + requirement: REQUIRED + state: present +``` + License ------- diff --git a/main/_static/base-stemmer.js b/main/_static/base-stemmer.js new file mode 100644 index 0000000..e6fa0c4 --- /dev/null +++ b/main/_static/base-stemmer.js @@ -0,0 +1,476 @@ +// @ts-check + +/**@constructor*/ +BaseStemmer = function() { + /** @protected */ + this.current = ''; + this.cursor = 0; + this.limit = 0; + this.limit_backward = 0; + this.bra = 0; + this.ket = 0; + + /** + * @param {string} value + */ + this.setCurrent = function(value) { + this.current = value; + this.cursor = 0; + this.limit = this.current.length; + this.limit_backward = 0; + this.bra = this.cursor; + this.ket = this.limit; + }; + + /** + * @return {string} + */ + this.getCurrent = function() { + return this.current; + }; + + /** + * @param {BaseStemmer} other + */ + this.copy_from = function(other) { + /** @protected */ + this.current = other.current; + this.cursor = other.cursor; + this.limit = other.limit; + this.limit_backward = other.limit_backward; + this.bra = other.bra; + this.ket = other.ket; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.in_grouping = function(s, min, max) { + /** @protected */ + if (this.cursor >= this.limit) return false; + var ch = this.current.charCodeAt(this.cursor); + if (ch > max || ch < min) return false; + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) return false; + this.cursor++; + return true; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.go_in_grouping = function(s, min, max) { + /** @protected */ + while (this.cursor < this.limit) { + var ch = this.current.charCodeAt(this.cursor); + if (ch > max || ch < min) + return true; + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) + return true; + this.cursor++; + } + return false; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.in_grouping_b = function(s, min, max) { + /** @protected */ + if (this.cursor <= this.limit_backward) return false; + var ch = this.current.charCodeAt(this.cursor - 1); + if (ch > max || ch < min) return false; + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) return false; + this.cursor--; + return true; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.go_in_grouping_b = function(s, min, max) { + /** @protected */ + while (this.cursor > this.limit_backward) { + var ch = this.current.charCodeAt(this.cursor - 1); + if (ch > max || ch < min) return true; + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) return true; + this.cursor--; + } + return false; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.out_grouping = function(s, min, max) { + /** @protected */ + if (this.cursor >= this.limit) return false; + var ch = this.current.charCodeAt(this.cursor); + if (ch > max || ch < min) { + this.cursor++; + return true; + } + ch -= min; + if ((s[ch >>> 3] & (0X1 << (ch & 0x7))) == 0) { + this.cursor++; + return true; + } + return false; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.go_out_grouping = function(s, min, max) { + /** @protected */ + while (this.cursor < this.limit) { + var ch = this.current.charCodeAt(this.cursor); + if (ch <= max && ch >= min) { + ch -= min; + if ((s[ch >>> 3] & (0X1 << (ch & 0x7))) != 0) { + return true; + } + } + this.cursor++; + } + return false; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.out_grouping_b = function(s, min, max) { + /** @protected */ + if (this.cursor <= this.limit_backward) return false; + var ch = this.current.charCodeAt(this.cursor - 1); + if (ch > max || ch < min) { + this.cursor--; + return true; + } + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) { + this.cursor--; + return true; + } + return false; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.go_out_grouping_b = function(s, min, max) { + /** @protected */ + while (this.cursor > this.limit_backward) { + var ch = this.current.charCodeAt(this.cursor - 1); + if (ch <= max && ch >= min) { + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) != 0) { + return true; + } + } + this.cursor--; + } + return false; + }; + + /** + * @param {string} s + * @return {boolean} + */ + this.eq_s = function(s) + { + /** @protected */ + if (this.limit - this.cursor < s.length) return false; + if (this.current.slice(this.cursor, this.cursor + s.length) != s) + { + return false; + } + this.cursor += s.length; + return true; + }; + + /** + * @param {string} s + * @return {boolean} + */ + this.eq_s_b = function(s) + { + /** @protected */ + if (this.cursor - this.limit_backward < s.length) return false; + if (this.current.slice(this.cursor - s.length, this.cursor) != s) + { + return false; + } + this.cursor -= s.length; + return true; + }; + + /** + * @param {Among[]} v + * @return {number} + */ + this.find_among = function(v) + { + /** @protected */ + var i = 0; + var j = v.length; + + var c = this.cursor; + var l = this.limit; + + var common_i = 0; + var common_j = 0; + + var first_key_inspected = false; + + while (true) + { + var k = i + ((j - i) >>> 1); + var diff = 0; + var common = common_i < common_j ? common_i : common_j; // smaller + // w[0]: string, w[1]: substring_i, w[2]: result, w[3]: function (optional) + var w = v[k]; + var i2; + for (i2 = common; i2 < w[0].length; i2++) + { + if (c + common == l) + { + diff = -1; + break; + } + diff = this.current.charCodeAt(c + common) - w[0].charCodeAt(i2); + if (diff != 0) break; + common++; + } + if (diff < 0) + { + j = k; + common_j = common; + } + else + { + i = k; + common_i = common; + } + if (j - i <= 1) + { + if (i > 0) break; // v->s has been inspected + if (j == i) break; // only one item in v + + // - but now we need to go round once more to get + // v->s inspected. This looks messy, but is actually + // the optimal approach. + + if (first_key_inspected) break; + first_key_inspected = true; + } + } + do { + var w = v[i]; + if (common_i >= w[0].length) + { + this.cursor = c + w[0].length; + if (w.length < 4) return w[2]; + var res = w[3](this); + this.cursor = c + w[0].length; + if (res) return w[2]; + } + i = w[1]; + } while (i >= 0); + return 0; + }; + + // find_among_b is for backwards processing. Same comments apply + /** + * @param {Among[]} v + * @return {number} + */ + this.find_among_b = function(v) + { + /** @protected */ + var i = 0; + var j = v.length + + var c = this.cursor; + var lb = this.limit_backward; + + var common_i = 0; + var common_j = 0; + + var first_key_inspected = false; + + while (true) + { + var k = i + ((j - i) >> 1); + var diff = 0; + var common = common_i < common_j ? common_i : common_j; + var w = v[k]; + var i2; + for (i2 = w[0].length - 1 - common; i2 >= 0; i2--) + { + if (c - common == lb) + { + diff = -1; + break; + } + diff = this.current.charCodeAt(c - 1 - common) - w[0].charCodeAt(i2); + if (diff != 0) break; + common++; + } + if (diff < 0) + { + j = k; + common_j = common; + } + else + { + i = k; + common_i = common; + } + if (j - i <= 1) + { + if (i > 0) break; + if (j == i) break; + if (first_key_inspected) break; + first_key_inspected = true; + } + } + do { + var w = v[i]; + if (common_i >= w[0].length) + { + this.cursor = c - w[0].length; + if (w.length < 4) return w[2]; + var res = w[3](this); + this.cursor = c - w[0].length; + if (res) return w[2]; + } + i = w[1]; + } while (i >= 0); + return 0; + }; + + /* to replace chars between c_bra and c_ket in this.current by the + * chars in s. + */ + /** + * @param {number} c_bra + * @param {number} c_ket + * @param {string} s + * @return {number} + */ + this.replace_s = function(c_bra, c_ket, s) + { + /** @protected */ + var adjustment = s.length - (c_ket - c_bra); + this.current = this.current.slice(0, c_bra) + s + this.current.slice(c_ket); + this.limit += adjustment; + if (this.cursor >= c_ket) this.cursor += adjustment; + else if (this.cursor > c_bra) this.cursor = c_bra; + return adjustment; + }; + + /** + * @return {boolean} + */ + this.slice_check = function() + { + /** @protected */ + if (this.bra < 0 || + this.bra > this.ket || + this.ket > this.limit || + this.limit > this.current.length) + { + return false; + } + return true; + }; + + /** + * @param {number} c_bra + * @return {boolean} + */ + this.slice_from = function(s) + { + /** @protected */ + var result = false; + if (this.slice_check()) + { + this.replace_s(this.bra, this.ket, s); + result = true; + } + return result; + }; + + /** + * @return {boolean} + */ + this.slice_del = function() + { + /** @protected */ + return this.slice_from(""); + }; + + /** + * @param {number} c_bra + * @param {number} c_ket + * @param {string} s + */ + this.insert = function(c_bra, c_ket, s) + { + /** @protected */ + var adjustment = this.replace_s(c_bra, c_ket, s); + if (c_bra <= this.bra) this.bra += adjustment; + if (c_bra <= this.ket) this.ket += adjustment; + }; + + /** + * @return {string} + */ + this.slice_to = function() + { + /** @protected */ + var result = ''; + if (this.slice_check()) + { + result = this.current.slice(this.bra, this.ket); + } + return result; + }; + + /** + * @return {string} + */ + this.assign_to = function() + { + /** @protected */ + return this.current.slice(0, this.limit); + }; +}; diff --git a/main/_static/doctools.js b/main/_static/doctools.js index 0398ebb..807cdb1 100644 --- a/main/_static/doctools.js +++ b/main/_static/doctools.js @@ -59,7 +59,7 @@ const Documentation = { Object.assign(Documentation.TRANSLATIONS, catalog.messages); Documentation.PLURAL_EXPR = new Function( "n", - `return (${catalog.plural_expr})` + `return (${catalog.plural_expr})`, ); Documentation.LOCALE = catalog.locale; }, @@ -89,7 +89,7 @@ const Documentation = { const togglerElements = document.querySelectorAll("img.toggler"); togglerElements.forEach((el) => - el.addEventListener("click", (event) => toggler(event.currentTarget)) + el.addEventListener("click", (event) => toggler(event.currentTarget)), ); togglerElements.forEach((el) => (el.style.display = "")); if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); @@ -98,14 +98,15 @@ const Documentation = { initOnKeyListeners: () => { // only install a listener if it is really needed if ( - !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && - !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS + && !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS ) return; document.addEventListener("keydown", (event) => { // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) + return; // bail with special keys if (event.altKey || event.ctrlKey || event.metaKey) return; diff --git a/main/_static/english-stemmer.js b/main/_static/english-stemmer.js new file mode 100644 index 0000000..056760e --- /dev/null +++ b/main/_static/english-stemmer.js @@ -0,0 +1,1066 @@ +// Generated from english.sbl by Snowball 3.0.1 - https://snowballstem.org/ + +/**@constructor*/ +var EnglishStemmer = function() { + var base = new BaseStemmer(); + + /** @const */ var a_0 = [ + ["arsen", -1, -1], + ["commun", -1, -1], + ["emerg", -1, -1], + ["gener", -1, -1], + ["later", -1, -1], + ["organ", -1, -1], + ["past", -1, -1], + ["univers", -1, -1] + ]; + + /** @const */ var a_1 = [ + ["'", -1, 1], + ["'s'", 0, 1], + ["'s", -1, 1] + ]; + + /** @const */ var a_2 = [ + ["ied", -1, 2], + ["s", -1, 3], + ["ies", 1, 2], + ["sses", 1, 1], + ["ss", 1, -1], + ["us", 1, -1] + ]; + + /** @const */ var a_3 = [ + ["succ", -1, 1], + ["proc", -1, 1], + ["exc", -1, 1] + ]; + + /** @const */ var a_4 = [ + ["even", -1, 2], + ["cann", -1, 2], + ["inn", -1, 2], + ["earr", -1, 2], + ["herr", -1, 2], + ["out", -1, 2], + ["y", -1, 1] + ]; + + /** @const */ var a_5 = [ + ["", -1, -1], + ["ed", 0, 2], + ["eed", 1, 1], + ["ing", 0, 3], + ["edly", 0, 2], + ["eedly", 4, 1], + ["ingly", 0, 2] + ]; + + /** @const */ var a_6 = [ + ["", -1, 3], + ["bb", 0, 2], + ["dd", 0, 2], + ["ff", 0, 2], + ["gg", 0, 2], + ["bl", 0, 1], + ["mm", 0, 2], + ["nn", 0, 2], + ["pp", 0, 2], + ["rr", 0, 2], + ["at", 0, 1], + ["tt", 0, 2], + ["iz", 0, 1] + ]; + + /** @const */ var a_7 = [ + ["anci", -1, 3], + ["enci", -1, 2], + ["ogi", -1, 14], + ["li", -1, 16], + ["bli", 3, 12], + ["abli", 4, 4], + ["alli", 3, 8], + ["fulli", 3, 9], + ["lessli", 3, 15], + ["ousli", 3, 10], + ["entli", 3, 5], + ["aliti", -1, 8], + ["biliti", -1, 12], + ["iviti", -1, 11], + ["tional", -1, 1], + ["ational", 14, 7], + ["alism", -1, 8], + ["ation", -1, 7], + ["ization", 17, 6], + ["izer", -1, 6], + ["ator", -1, 7], + ["iveness", -1, 11], + ["fulness", -1, 9], + ["ousness", -1, 10], + ["ogist", -1, 13] + ]; + + /** @const */ var a_8 = [ + ["icate", -1, 4], + ["ative", -1, 6], + ["alize", -1, 3], + ["iciti", -1, 4], + ["ical", -1, 4], + ["tional", -1, 1], + ["ational", 5, 2], + ["ful", -1, 5], + ["ness", -1, 5] + ]; + + /** @const */ var a_9 = [ + ["ic", -1, 1], + ["ance", -1, 1], + ["ence", -1, 1], + ["able", -1, 1], + ["ible", -1, 1], + ["ate", -1, 1], + ["ive", -1, 1], + ["ize", -1, 1], + ["iti", -1, 1], + ["al", -1, 1], + ["ism", -1, 1], + ["ion", -1, 2], + ["er", -1, 1], + ["ous", -1, 1], + ["ant", -1, 1], + ["ent", -1, 1], + ["ment", 15, 1], + ["ement", 16, 1] + ]; + + /** @const */ var a_10 = [ + ["e", -1, 1], + ["l", -1, 2] + ]; + + /** @const */ var a_11 = [ + ["andes", -1, -1], + ["atlas", -1, -1], + ["bias", -1, -1], + ["cosmos", -1, -1], + ["early", -1, 5], + ["gently", -1, 3], + ["howe", -1, -1], + ["idly", -1, 2], + ["news", -1, -1], + ["only", -1, 6], + ["singly", -1, 7], + ["skies", -1, 1], + ["sky", -1, -1], + ["ugly", -1, 4] + ]; + + /** @const */ var /** Array */ g_aeo = [17, 64]; + + /** @const */ var /** Array */ g_v = [17, 65, 16, 1]; + + /** @const */ var /** Array */ g_v_WXY = [1, 17, 65, 208, 1]; + + /** @const */ var /** Array */ g_valid_LI = [55, 141, 2]; + + var /** boolean */ B_Y_found = false; + var /** number */ I_p2 = 0; + var /** number */ I_p1 = 0; + + + /** @return {boolean} */ + function r_prelude() { + B_Y_found = false; + /** @const */ var /** number */ v_1 = base.cursor; + lab0: { + base.bra = base.cursor; + if (!(base.eq_s("'"))) + { + break lab0; + } + base.ket = base.cursor; + if (!base.slice_del()) + { + return false; + } + } + base.cursor = v_1; + /** @const */ var /** number */ v_2 = base.cursor; + lab1: { + base.bra = base.cursor; + if (!(base.eq_s("y"))) + { + break lab1; + } + base.ket = base.cursor; + if (!base.slice_from("Y")) + { + return false; + } + B_Y_found = true; + } + base.cursor = v_2; + /** @const */ var /** number */ v_3 = base.cursor; + lab2: { + while(true) + { + /** @const */ var /** number */ v_4 = base.cursor; + lab3: { + golab4: while(true) + { + /** @const */ var /** number */ v_5 = base.cursor; + lab5: { + if (!(base.in_grouping(g_v, 97, 121))) + { + break lab5; + } + base.bra = base.cursor; + if (!(base.eq_s("y"))) + { + break lab5; + } + base.ket = base.cursor; + base.cursor = v_5; + break golab4; + } + base.cursor = v_5; + if (base.cursor >= base.limit) + { + break lab3; + } + base.cursor++; + } + if (!base.slice_from("Y")) + { + return false; + } + B_Y_found = true; + continue; + } + base.cursor = v_4; + break; + } + } + base.cursor = v_3; + return true; + }; + + /** @return {boolean} */ + function r_mark_regions() { + I_p1 = base.limit; + I_p2 = base.limit; + /** @const */ var /** number */ v_1 = base.cursor; + lab0: { + lab1: { + /** @const */ var /** number */ v_2 = base.cursor; + lab2: { + if (base.find_among(a_0) == 0) + { + break lab2; + } + break lab1; + } + base.cursor = v_2; + if (!base.go_out_grouping(g_v, 97, 121)) + { + break lab0; + } + base.cursor++; + if (!base.go_in_grouping(g_v, 97, 121)) + { + break lab0; + } + base.cursor++; + } + I_p1 = base.cursor; + if (!base.go_out_grouping(g_v, 97, 121)) + { + break lab0; + } + base.cursor++; + if (!base.go_in_grouping(g_v, 97, 121)) + { + break lab0; + } + base.cursor++; + I_p2 = base.cursor; + } + base.cursor = v_1; + return true; + }; + + /** @return {boolean} */ + function r_shortv() { + lab0: { + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab1: { + if (!(base.out_grouping_b(g_v_WXY, 89, 121))) + { + break lab1; + } + if (!(base.in_grouping_b(g_v, 97, 121))) + { + break lab1; + } + if (!(base.out_grouping_b(g_v, 97, 121))) + { + break lab1; + } + break lab0; + } + base.cursor = base.limit - v_1; + lab2: { + if (!(base.out_grouping_b(g_v, 97, 121))) + { + break lab2; + } + if (!(base.in_grouping_b(g_v, 97, 121))) + { + break lab2; + } + if (base.cursor > base.limit_backward) + { + break lab2; + } + break lab0; + } + base.cursor = base.limit - v_1; + if (!(base.eq_s_b("past"))) + { + return false; + } + } + return true; + }; + + /** @return {boolean} */ + function r_R1() { + return I_p1 <= base.cursor; + }; + + /** @return {boolean} */ + function r_R2() { + return I_p2 <= base.cursor; + }; + + /** @return {boolean} */ + function r_Step_1a() { + var /** number */ among_var; + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab0: { + base.ket = base.cursor; + if (base.find_among_b(a_1) == 0) + { + base.cursor = base.limit - v_1; + break lab0; + } + base.bra = base.cursor; + if (!base.slice_del()) + { + return false; + } + } + base.ket = base.cursor; + among_var = base.find_among_b(a_2); + if (among_var == 0) + { + return false; + } + base.bra = base.cursor; + switch (among_var) { + case 1: + if (!base.slice_from("ss")) + { + return false; + } + break; + case 2: + lab1: { + /** @const */ var /** number */ v_2 = base.limit - base.cursor; + lab2: { + { + /** @const */ var /** number */ c1 = base.cursor - 2; + if (c1 < base.limit_backward) + { + break lab2; + } + base.cursor = c1; + } + if (!base.slice_from("i")) + { + return false; + } + break lab1; + } + base.cursor = base.limit - v_2; + if (!base.slice_from("ie")) + { + return false; + } + } + break; + case 3: + if (base.cursor <= base.limit_backward) + { + return false; + } + base.cursor--; + if (!base.go_out_grouping_b(g_v, 97, 121)) + { + return false; + } + base.cursor--; + if (!base.slice_del()) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_Step_1b() { + var /** number */ among_var; + base.ket = base.cursor; + among_var = base.find_among_b(a_5); + base.bra = base.cursor; + lab0: { + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab1: { + switch (among_var) { + case 1: + /** @const */ var /** number */ v_2 = base.limit - base.cursor; + lab2: { + lab3: { + /** @const */ var /** number */ v_3 = base.limit - base.cursor; + lab4: { + if (base.find_among_b(a_3) == 0) + { + break lab4; + } + if (base.cursor > base.limit_backward) + { + break lab4; + } + break lab3; + } + base.cursor = base.limit - v_3; + if (!r_R1()) + { + break lab2; + } + if (!base.slice_from("ee")) + { + return false; + } + } + } + base.cursor = base.limit - v_2; + break; + case 2: + break lab1; + case 3: + among_var = base.find_among_b(a_4); + if (among_var == 0) + { + break lab1; + } + switch (among_var) { + case 1: + /** @const */ var /** number */ v_4 = base.limit - base.cursor; + if (!(base.out_grouping_b(g_v, 97, 121))) + { + break lab1; + } + if (base.cursor > base.limit_backward) + { + break lab1; + } + base.cursor = base.limit - v_4; + base.bra = base.cursor; + if (!base.slice_from("ie")) + { + return false; + } + break; + case 2: + if (base.cursor > base.limit_backward) + { + break lab1; + } + break; + } + break; + } + break lab0; + } + base.cursor = base.limit - v_1; + /** @const */ var /** number */ v_5 = base.limit - base.cursor; + if (!base.go_out_grouping_b(g_v, 97, 121)) + { + return false; + } + base.cursor--; + base.cursor = base.limit - v_5; + if (!base.slice_del()) + { + return false; + } + base.ket = base.cursor; + base.bra = base.cursor; + /** @const */ var /** number */ v_6 = base.limit - base.cursor; + among_var = base.find_among_b(a_6); + switch (among_var) { + case 1: + if (!base.slice_from("e")) + { + return false; + } + return false; + case 2: + { + /** @const */ var /** number */ v_7 = base.limit - base.cursor; + lab5: { + if (!(base.in_grouping_b(g_aeo, 97, 111))) + { + break lab5; + } + if (base.cursor > base.limit_backward) + { + break lab5; + } + return false; + } + base.cursor = base.limit - v_7; + } + break; + case 3: + if (base.cursor != I_p1) + { + return false; + } + /** @const */ var /** number */ v_8 = base.limit - base.cursor; + if (!r_shortv()) + { + return false; + } + base.cursor = base.limit - v_8; + if (!base.slice_from("e")) + { + return false; + } + return false; + } + base.cursor = base.limit - v_6; + base.ket = base.cursor; + if (base.cursor <= base.limit_backward) + { + return false; + } + base.cursor--; + base.bra = base.cursor; + if (!base.slice_del()) + { + return false; + } + } + return true; + }; + + /** @return {boolean} */ + function r_Step_1c() { + base.ket = base.cursor; + lab0: { + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab1: { + if (!(base.eq_s_b("y"))) + { + break lab1; + } + break lab0; + } + base.cursor = base.limit - v_1; + if (!(base.eq_s_b("Y"))) + { + return false; + } + } + base.bra = base.cursor; + if (!(base.out_grouping_b(g_v, 97, 121))) + { + return false; + } + lab2: { + if (base.cursor > base.limit_backward) + { + break lab2; + } + return false; + } + if (!base.slice_from("i")) + { + return false; + } + return true; + }; + + /** @return {boolean} */ + function r_Step_2() { + var /** number */ among_var; + base.ket = base.cursor; + among_var = base.find_among_b(a_7); + if (among_var == 0) + { + return false; + } + base.bra = base.cursor; + if (!r_R1()) + { + return false; + } + switch (among_var) { + case 1: + if (!base.slice_from("tion")) + { + return false; + } + break; + case 2: + if (!base.slice_from("ence")) + { + return false; + } + break; + case 3: + if (!base.slice_from("ance")) + { + return false; + } + break; + case 4: + if (!base.slice_from("able")) + { + return false; + } + break; + case 5: + if (!base.slice_from("ent")) + { + return false; + } + break; + case 6: + if (!base.slice_from("ize")) + { + return false; + } + break; + case 7: + if (!base.slice_from("ate")) + { + return false; + } + break; + case 8: + if (!base.slice_from("al")) + { + return false; + } + break; + case 9: + if (!base.slice_from("ful")) + { + return false; + } + break; + case 10: + if (!base.slice_from("ous")) + { + return false; + } + break; + case 11: + if (!base.slice_from("ive")) + { + return false; + } + break; + case 12: + if (!base.slice_from("ble")) + { + return false; + } + break; + case 13: + if (!base.slice_from("og")) + { + return false; + } + break; + case 14: + if (!(base.eq_s_b("l"))) + { + return false; + } + if (!base.slice_from("og")) + { + return false; + } + break; + case 15: + if (!base.slice_from("less")) + { + return false; + } + break; + case 16: + if (!(base.in_grouping_b(g_valid_LI, 99, 116))) + { + return false; + } + if (!base.slice_del()) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_Step_3() { + var /** number */ among_var; + base.ket = base.cursor; + among_var = base.find_among_b(a_8); + if (among_var == 0) + { + return false; + } + base.bra = base.cursor; + if (!r_R1()) + { + return false; + } + switch (among_var) { + case 1: + if (!base.slice_from("tion")) + { + return false; + } + break; + case 2: + if (!base.slice_from("ate")) + { + return false; + } + break; + case 3: + if (!base.slice_from("al")) + { + return false; + } + break; + case 4: + if (!base.slice_from("ic")) + { + return false; + } + break; + case 5: + if (!base.slice_del()) + { + return false; + } + break; + case 6: + if (!r_R2()) + { + return false; + } + if (!base.slice_del()) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_Step_4() { + var /** number */ among_var; + base.ket = base.cursor; + among_var = base.find_among_b(a_9); + if (among_var == 0) + { + return false; + } + base.bra = base.cursor; + if (!r_R2()) + { + return false; + } + switch (among_var) { + case 1: + if (!base.slice_del()) + { + return false; + } + break; + case 2: + lab0: { + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab1: { + if (!(base.eq_s_b("s"))) + { + break lab1; + } + break lab0; + } + base.cursor = base.limit - v_1; + if (!(base.eq_s_b("t"))) + { + return false; + } + } + if (!base.slice_del()) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_Step_5() { + var /** number */ among_var; + base.ket = base.cursor; + among_var = base.find_among_b(a_10); + if (among_var == 0) + { + return false; + } + base.bra = base.cursor; + switch (among_var) { + case 1: + lab0: { + lab1: { + if (!r_R2()) + { + break lab1; + } + break lab0; + } + if (!r_R1()) + { + return false; + } + { + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab2: { + if (!r_shortv()) + { + break lab2; + } + return false; + } + base.cursor = base.limit - v_1; + } + } + if (!base.slice_del()) + { + return false; + } + break; + case 2: + if (!r_R2()) + { + return false; + } + if (!(base.eq_s_b("l"))) + { + return false; + } + if (!base.slice_del()) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_exception1() { + var /** number */ among_var; + base.bra = base.cursor; + among_var = base.find_among(a_11); + if (among_var == 0) + { + return false; + } + base.ket = base.cursor; + if (base.cursor < base.limit) + { + return false; + } + switch (among_var) { + case 1: + if (!base.slice_from("sky")) + { + return false; + } + break; + case 2: + if (!base.slice_from("idl")) + { + return false; + } + break; + case 3: + if (!base.slice_from("gentl")) + { + return false; + } + break; + case 4: + if (!base.slice_from("ugli")) + { + return false; + } + break; + case 5: + if (!base.slice_from("earli")) + { + return false; + } + break; + case 6: + if (!base.slice_from("onli")) + { + return false; + } + break; + case 7: + if (!base.slice_from("singl")) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_postlude() { + if (!B_Y_found) + { + return false; + } + while(true) + { + /** @const */ var /** number */ v_1 = base.cursor; + lab0: { + golab1: while(true) + { + /** @const */ var /** number */ v_2 = base.cursor; + lab2: { + base.bra = base.cursor; + if (!(base.eq_s("Y"))) + { + break lab2; + } + base.ket = base.cursor; + base.cursor = v_2; + break golab1; + } + base.cursor = v_2; + if (base.cursor >= base.limit) + { + break lab0; + } + base.cursor++; + } + if (!base.slice_from("y")) + { + return false; + } + continue; + } + base.cursor = v_1; + break; + } + return true; + }; + + this.stem = /** @return {boolean} */ function() { + lab0: { + /** @const */ var /** number */ v_1 = base.cursor; + lab1: { + if (!r_exception1()) + { + break lab1; + } + break lab0; + } + base.cursor = v_1; + lab2: { + { + /** @const */ var /** number */ v_2 = base.cursor; + lab3: { + { + /** @const */ var /** number */ c1 = base.cursor + 3; + if (c1 > base.limit) + { + break lab3; + } + base.cursor = c1; + } + break lab2; + } + base.cursor = v_2; + } + break lab0; + } + base.cursor = v_1; + r_prelude(); + r_mark_regions(); + base.limit_backward = base.cursor; base.cursor = base.limit; + /** @const */ var /** number */ v_3 = base.limit - base.cursor; + r_Step_1a(); + base.cursor = base.limit - v_3; + /** @const */ var /** number */ v_4 = base.limit - base.cursor; + r_Step_1b(); + base.cursor = base.limit - v_4; + /** @const */ var /** number */ v_5 = base.limit - base.cursor; + r_Step_1c(); + base.cursor = base.limit - v_5; + /** @const */ var /** number */ v_6 = base.limit - base.cursor; + r_Step_2(); + base.cursor = base.limit - v_6; + /** @const */ var /** number */ v_7 = base.limit - base.cursor; + r_Step_3(); + base.cursor = base.limit - v_7; + /** @const */ var /** number */ v_8 = base.limit - base.cursor; + r_Step_4(); + base.cursor = base.limit - v_8; + /** @const */ var /** number */ v_9 = base.limit - base.cursor; + r_Step_5(); + base.cursor = base.limit - v_9; + base.cursor = base.limit_backward; + /** @const */ var /** number */ v_10 = base.cursor; + r_postlude(); + base.cursor = v_10; + } + return true; + }; + + /**@return{string}*/ + this['stemWord'] = function(/**string*/word) { + base.setCurrent(word); + this.stem(); + return base.getCurrent(); + }; +}; diff --git a/main/_static/language_data.js b/main/_static/language_data.js index c7fe6c6..5776786 100644 --- a/main/_static/language_data.js +++ b/main/_static/language_data.js @@ -1,192 +1,13 @@ /* * This script contains the language-specific data used by searchtools.js, - * namely the list of stopwords, stemmer, scorer and splitter. + * namely the set of stopwords, stemmer, scorer and splitter. */ -var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; +const stopwords = new Set(["a", "about", "above", "after", "again", "against", "all", "am", "an", "and", "any", "are", "aren't", "as", "at", "be", "because", "been", "before", "being", "below", "between", "both", "but", "by", "can't", "cannot", "could", "couldn't", "did", "didn't", "do", "does", "doesn't", "doing", "don't", "down", "during", "each", "few", "for", "from", "further", "had", "hadn't", "has", "hasn't", "have", "haven't", "having", "he", "he'd", "he'll", "he's", "her", "here", "here's", "hers", "herself", "him", "himself", "his", "how", "how's", "i", "i'd", "i'll", "i'm", "i've", "if", "in", "into", "is", "isn't", "it", "it's", "its", "itself", "let's", "me", "more", "most", "mustn't", "my", "myself", "no", "nor", "not", "of", "off", "on", "once", "only", "or", "other", "ought", "our", "ours", "ourselves", "out", "over", "own", "same", "shan't", "she", "she'd", "she'll", "she's", "should", "shouldn't", "so", "some", "such", "than", "that", "that's", "the", "their", "theirs", "them", "themselves", "then", "there", "there's", "these", "they", "they'd", "they'll", "they're", "they've", "this", "those", "through", "to", "too", "under", "until", "up", "very", "was", "wasn't", "we", "we'd", "we'll", "we're", "we've", "were", "weren't", "what", "what's", "when", "when's", "where", "where's", "which", "while", "who", "who's", "whom", "why", "why's", "with", "won't", "would", "wouldn't", "you", "you'd", "you'll", "you're", "you've", "your", "yours", "yourself", "yourselves"]); +window.stopwords = stopwords; // Export to global scope -/* Non-minified version is copied as a separate JS file, if available */ - -/** - * Porter Stemmer - */ -var Stemmer = function() { - - var step2list = { - ational: 'ate', - tional: 'tion', - enci: 'ence', - anci: 'ance', - izer: 'ize', - bli: 'ble', - alli: 'al', - entli: 'ent', - eli: 'e', - ousli: 'ous', - ization: 'ize', - ation: 'ate', - ator: 'ate', - alism: 'al', - iveness: 'ive', - fulness: 'ful', - ousness: 'ous', - aliti: 'al', - iviti: 'ive', - biliti: 'ble', - logi: 'log' - }; - - var step3list = { - icate: 'ic', - ative: '', - alize: 'al', - iciti: 'ic', - ical: 'ic', - ful: '', - ness: '' - }; - - var c = "[^aeiou]"; // consonant - var v = "[aeiouy]"; // vowel - var C = c + "[^aeiouy]*"; // consonant sequence - var V = v + "[aeiou]*"; // vowel sequence - - var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 - var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 - var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 - var s_v = "^(" + C + ")?" + v; // vowel in stem - - this.stemWord = function (w) { - var stem; - var suffix; - var firstch; - var origword = w; - - if (w.length < 3) - return w; - - var re; - var re2; - var re3; - var re4; - - firstch = w.substr(0,1); - if (firstch == "y") - w = firstch.toUpperCase() + w.substr(1); - - // Step 1a - re = /^(.+?)(ss|i)es$/; - re2 = /^(.+?)([^s])s$/; - - if (re.test(w)) - w = w.replace(re,"$1$2"); - else if (re2.test(w)) - w = w.replace(re2,"$1$2"); - - // Step 1b - re = /^(.+?)eed$/; - re2 = /^(.+?)(ed|ing)$/; - if (re.test(w)) { - var fp = re.exec(w); - re = new RegExp(mgr0); - if (re.test(fp[1])) { - re = /.$/; - w = w.replace(re,""); - } - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - re2 = new RegExp(s_v); - if (re2.test(stem)) { - w = stem; - re2 = /(at|bl|iz)$/; - re3 = new RegExp("([^aeiouylsz])\\1$"); - re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re2.test(w)) - w = w + "e"; - else if (re3.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - else if (re4.test(w)) - w = w + "e"; - } - } - - // Step 1c - re = /^(.+?)y$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(s_v); - if (re.test(stem)) - w = stem + "i"; - } - - // Step 2 - re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step2list[suffix]; - } - - // Step 3 - re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step3list[suffix]; - } - - // Step 4 - re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; - re2 = /^(.+?)(s|t)(ion)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - if (re.test(stem)) - w = stem; - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1] + fp[2]; - re2 = new RegExp(mgr1); - if (re2.test(stem)) - w = stem; - } - - // Step 5 - re = /^(.+?)e$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - re2 = new RegExp(meq1); - re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) - w = stem; - } - re = /ll$/; - re2 = new RegExp(mgr1); - if (re.test(w) && re2.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - - // and turn initial Y back to y - if (firstch == "y") - w = firstch.toLowerCase() + w.substr(1); - return w; - } -} - +/* Non-minified versions are copied as separate JavaScript files, if available */ +BaseStemmer=function(){this.current="",this.cursor=0,this.limit=0,this.limit_backward=0,this.bra=0,this.ket=0,this.setCurrent=function(t){this.current=t,this.cursor=0,this.limit=this.current.length,this.limit_backward=0,this.bra=this.cursor,this.ket=this.limit},this.getCurrent=function(){return this.current},this.copy_from=function(t){this.current=t.current,this.cursor=t.cursor,this.limit=t.limit,this.limit_backward=t.limit_backward,this.bra=t.bra,this.ket=t.ket},this.in_grouping=function(t,r,i){return!(this.cursor>=this.limit||i<(i=this.current.charCodeAt(this.cursor))||i>>3]&1<<(7&i))||(this.cursor++,0))},this.go_in_grouping=function(t,r,i){for(;this.cursor>>3]&1<<(7&s)))return!0;this.cursor++}return!1},this.in_grouping_b=function(t,r,i){return!(this.cursor<=this.limit_backward||i<(i=this.current.charCodeAt(this.cursor-1))||i>>3]&1<<(7&i))||(this.cursor--,0))},this.go_in_grouping_b=function(t,r,i){for(;this.cursor>this.limit_backward;){var s=this.current.charCodeAt(this.cursor-1);if(i>>3]&1<<(7&s)))return!0;this.cursor--}return!1},this.out_grouping=function(t,r,i){return!(this.cursor>=this.limit)&&(i<(i=this.current.charCodeAt(this.cursor))||i>>3]&1<<(7&i)))&&(this.cursor++,!0)},this.go_out_grouping=function(t,r,i){for(;this.cursor>>3]&1<<(7&s)))return!0;this.cursor++}return!1},this.out_grouping_b=function(t,r,i){return!(this.cursor<=this.limit_backward)&&(i<(i=this.current.charCodeAt(this.cursor-1))||i>>3]&1<<(7&i)))&&(this.cursor--,!0)},this.go_out_grouping_b=function(t,r,i){for(;this.cursor>this.limit_backward;){var s=this.current.charCodeAt(this.cursor-1);if(s<=i&&r<=s&&0!=(t[(s-=r)>>>3]&1<<(7&s)))return!0;this.cursor--}return!1},this.eq_s=function(t){return!(this.limit-this.cursor>>1),o=0,a=e=(l=t[r])[0].length){if(this.cursor=s+l[0].length,l.length<4)return l[2];var g=l[3](this);if(this.cursor=s+l[0].length,g)return l[2]}}while(0<=(r=l[1]));return 0},this.find_among_b=function(t){for(var r=0,i=t.length,s=this.cursor,h=this.limit_backward,e=0,n=0,c=!1;;){for(var u,o=r+(i-r>>1),a=0,l=e=(u=t[r])[0].length){if(this.cursor=s-u[0].length,u.length<4)return u[2];var g=u[3](this);if(this.cursor=s-u[0].length,g)return u[2]}}while(0<=(r=u[1]));return 0},this.replace_s=function(t,r,i){var s=i.length-(r-t);return this.current=this.current.slice(0,t)+i+this.current.slice(r),this.limit+=s,this.cursor>=r?this.cursor+=s:this.cursor>t&&(this.cursor=t),s},this.slice_check=function(){return!(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>this.current.length)},this.slice_from=function(t){var r=!1;return this.slice_check()&&(this.replace_s(this.bra,this.ket,t),r=!0),r},this.slice_del=function(){return this.slice_from("")},this.insert=function(t,r,i){r=this.replace_s(t,r,i);t<=this.bra&&(this.bra+=r),t<=this.ket&&(this.ket+=r)},this.slice_to=function(){var t="";return t=this.slice_check()?this.current.slice(this.bra,this.ket):t},this.assign_to=function(){return this.current.slice(0,this.limit)}}; +var EnglishStemmer=function(){var a=new BaseStemmer,c=[["arsen",-1,-1],["commun",-1,-1],["emerg",-1,-1],["gener",-1,-1],["later",-1,-1],["organ",-1,-1],["past",-1,-1],["univers",-1,-1]],o=[["'",-1,1],["'s'",0,1],["'s",-1,1]],u=[["ied",-1,2],["s",-1,3],["ies",1,2],["sses",1,1],["ss",1,-1],["us",1,-1]],t=[["succ",-1,1],["proc",-1,1],["exc",-1,1]],l=[["even",-1,2],["cann",-1,2],["inn",-1,2],["earr",-1,2],["herr",-1,2],["out",-1,2],["y",-1,1]],n=[["",-1,-1],["ed",0,2],["eed",1,1],["ing",0,3],["edly",0,2],["eedly",4,1],["ingly",0,2]],f=[["",-1,3],["bb",0,2],["dd",0,2],["ff",0,2],["gg",0,2],["bl",0,1],["mm",0,2],["nn",0,2],["pp",0,2],["rr",0,2],["at",0,1],["tt",0,2],["iz",0,1]],_=[["anci",-1,3],["enci",-1,2],["ogi",-1,14],["li",-1,16],["bli",3,12],["abli",4,4],["alli",3,8],["fulli",3,9],["lessli",3,15],["ousli",3,10],["entli",3,5],["aliti",-1,8],["biliti",-1,12],["iviti",-1,11],["tional",-1,1],["ational",14,7],["alism",-1,8],["ation",-1,7],["ization",17,6],["izer",-1,6],["ator",-1,7],["iveness",-1,11],["fulness",-1,9],["ousness",-1,10],["ogist",-1,13]],m=[["icate",-1,4],["ative",-1,6],["alize",-1,3],["iciti",-1,4],["ical",-1,4],["tional",-1,1],["ational",5,2],["ful",-1,5],["ness",-1,5]],b=[["ic",-1,1],["ance",-1,1],["ence",-1,1],["able",-1,1],["ible",-1,1],["ate",-1,1],["ive",-1,1],["ize",-1,1],["iti",-1,1],["al",-1,1],["ism",-1,1],["ion",-1,2],["er",-1,1],["ous",-1,1],["ant",-1,1],["ent",-1,1],["ment",15,1],["ement",16,1]],k=[["e",-1,1],["l",-1,2]],g=[["andes",-1,-1],["atlas",-1,-1],["bias",-1,-1],["cosmos",-1,-1],["early",-1,5],["gently",-1,3],["howe",-1,-1],["idly",-1,2],["news",-1,-1],["only",-1,6],["singly",-1,7],["skies",-1,1],["sky",-1,-1],["ugly",-1,4]],d=[17,64],v=[17,65,16,1],i=[1,17,65,208,1],w=[55,141,2],p=!1,y=0,h=0;function q(){var r=a.limit-a.cursor;return!!(a.out_grouping_b(i,89,121)&&a.in_grouping_b(v,97,121)&&a.out_grouping_b(v,97,121)||(a.cursor=a.limit-r,a.out_grouping_b(v,97,121)&&a.in_grouping_b(v,97,121)&&!(a.cursor>a.limit_backward))||(a.cursor=a.limit-r,a.eq_s_b("past")))}function z(){return h<=a.cursor}function Y(){return y<=a.cursor}this.stem=function(){var r=a.cursor;if(!(()=>{var r;if(a.bra=a.cursor,0!=(r=a.find_among(g))&&(a.ket=a.cursor,!(a.cursora.limit)a.cursor=i;else{a.cursor=e,a.cursor=r,(()=>{p=!1;var r=a.cursor;if(a.bra=a.cursor,!a.eq_s("'")||(a.ket=a.cursor,a.slice_del())){a.cursor=r;r=a.cursor;if(a.bra=a.cursor,a.eq_s("y")){if(a.ket=a.cursor,!a.slice_from("Y"))return;p=!0}a.cursor=r;for(r=a.cursor;;){var i=a.cursor;r:{for(;;){var e=a.cursor;if(a.in_grouping(v,97,121)&&(a.bra=a.cursor,a.eq_s("y"))){a.ket=a.cursor,a.cursor=e;break}if(a.cursor=e,a.cursor>=a.limit)break r;a.cursor++}if(!a.slice_from("Y"))return;p=!0;continue}a.cursor=i;break}a.cursor=r}})(),h=a.limit,y=a.limit;i=a.cursor;r:{var s=a.cursor;if(0==a.find_among(c)){if(a.cursor=s,!a.go_out_grouping(v,97,121))break r;if(a.cursor++,!a.go_in_grouping(v,97,121))break r;a.cursor++}h=a.cursor,a.go_out_grouping(v,97,121)&&(a.cursor++,a.go_in_grouping(v,97,121))&&(a.cursor++,y=a.cursor)}a.cursor=i,a.limit_backward=a.cursor,a.cursor=a.limit;var e=a.limit-a.cursor,r=((()=>{var r=a.limit-a.cursor;if(a.ket=a.cursor,0==a.find_among_b(o))a.cursor=a.limit-r;else if(a.bra=a.cursor,!a.slice_del())return;if(a.ket=a.cursor,0!=(r=a.find_among_b(u)))switch(a.bra=a.cursor,r){case 1:if(a.slice_from("ss"))break;return;case 2:r:{var i=a.limit-a.cursor,e=a.cursor-2;if(!(e{a.ket=a.cursor,o=a.find_among_b(n),a.bra=a.cursor;r:{var r=a.limit-a.cursor;i:{switch(o){case 1:var i=a.limit-a.cursor;e:{var e=a.limit-a.cursor;if(0==a.find_among_b(t)||a.cursor>a.limit_backward){if(a.cursor=a.limit-e,!z())break e;if(!a.slice_from("ee"))return}}a.cursor=a.limit-i;break;case 2:break i;case 3:if(0==(o=a.find_among_b(l)))break i;switch(o){case 1:var s=a.limit-a.cursor;if(!a.out_grouping_b(v,97,121))break i;if(a.cursor>a.limit_backward)break i;if(a.cursor=a.limit-s,a.bra=a.cursor,a.slice_from("ie"))break;return;case 2:if(a.cursor>a.limit_backward)break i}}break r}a.cursor=a.limit-r;var c=a.limit-a.cursor;if(!a.go_out_grouping_b(v,97,121))return;if(a.cursor--,a.cursor=a.limit-c,!a.slice_del())return;a.ket=a.cursor,a.bra=a.cursor;var o,c=a.limit-a.cursor;switch(o=a.find_among_b(f)){case 1:return a.slice_from("e");case 2:var u=a.limit-a.cursor;if(a.in_grouping_b(d,97,111)&&!(a.cursor>a.limit_backward))return;a.cursor=a.limit-u;break;case 3:return a.cursor!=h||(u=a.limit-a.cursor,q()&&(a.cursor=a.limit-u,a.slice_from("e")))}if(a.cursor=a.limit-c,a.ket=a.cursor,a.cursor<=a.limit_backward)return;if(a.cursor--,a.bra=a.cursor,!a.slice_del())return}})(),a.cursor=a.limit-r,a.limit-a.cursor),r=(a.ket=a.cursor,e=a.limit-a.cursor,(a.eq_s_b("y")||(a.cursor=a.limit-e,a.eq_s_b("Y")))&&(a.bra=a.cursor,a.out_grouping_b(v,97,121))&&a.cursor>a.limit_backward&&a.slice_from("i"),a.cursor=a.limit-i,a.limit-a.cursor),e=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(_))&&(a.bra=a.cursor,z()))switch(r){case 1:if(a.slice_from("tion"))break;return;case 2:if(a.slice_from("ence"))break;return;case 3:if(a.slice_from("ance"))break;return;case 4:if(a.slice_from("able"))break;return;case 5:if(a.slice_from("ent"))break;return;case 6:if(a.slice_from("ize"))break;return;case 7:if(a.slice_from("ate"))break;return;case 8:if(a.slice_from("al"))break;return;case 9:if(a.slice_from("ful"))break;return;case 10:if(a.slice_from("ous"))break;return;case 11:if(a.slice_from("ive"))break;return;case 12:if(a.slice_from("ble"))break;return;case 13:if(a.slice_from("og"))break;return;case 14:if(!a.eq_s_b("l"))return;if(a.slice_from("og"))break;return;case 15:if(a.slice_from("less"))break;return;case 16:if(!a.in_grouping_b(w,99,116))return;if(a.slice_del())break}})(),a.cursor=a.limit-r,a.limit-a.cursor),i=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(m))&&(a.bra=a.cursor,z()))switch(r){case 1:if(a.slice_from("tion"))break;return;case 2:if(a.slice_from("ate"))break;return;case 3:if(a.slice_from("al"))break;return;case 4:if(a.slice_from("ic"))break;return;case 5:if(a.slice_del())break;return;case 6:if(!Y())return;if(a.slice_del())break}})(),a.cursor=a.limit-e,a.limit-a.cursor),r=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(b))&&(a.bra=a.cursor,Y()))switch(r){case 1:if(a.slice_del())break;return;case 2:var i=a.limit-a.cursor;if(!a.eq_s_b("s")&&(a.cursor=a.limit-i,!a.eq_s_b("t")))return;if(a.slice_del())break}})(),a.cursor=a.limit-i,a.limit-a.cursor),e=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(k)))switch(a.bra=a.cursor,r){case 1:if(!Y()){if(!z())return;var i=a.limit-a.cursor;if(q())return;a.cursor=a.limit-i}if(a.slice_del())break;return;case 2:if(!Y())return;if(!a.eq_s_b("l"))return;if(a.slice_del())break}})(),a.cursor=a.limit-r,a.cursor=a.limit_backward,a.cursor);(()=>{if(p)for(;;){var r=a.cursor;r:{for(;;){var i=a.cursor;if(a.bra=a.cursor,a.eq_s("Y")){a.ket=a.cursor,a.cursor=i;break}if(a.cursor=i,a.cursor>=a.limit)break r;a.cursor++}if(a.slice_from("y"))continue;return}a.cursor=r;break}})(),a.cursor=e}}return!0},this.stemWord=function(r){return a.setCurrent(r),this.stem(),a.getCurrent()}}; +window.Stemmer = EnglishStemmer; diff --git a/main/_static/searchtools.js b/main/_static/searchtools.js index 91f4be5..e29b1c7 100644 --- a/main/_static/searchtools.js +++ b/main/_static/searchtools.js @@ -41,11 +41,12 @@ if (typeof Scorer === "undefined") { } // Global search result kind enum, used by themes to style search results. +// prettier-ignore class SearchResultKind { - static get index() { return "index"; } - static get object() { return "object"; } - static get text() { return "text"; } - static get title() { return "title"; } + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } } const _removeChildren = (element) => { @@ -58,6 +59,15 @@ const _removeChildren = (element) => { const _escapeRegExp = (string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string +const _escapeHTML = (text) => { + return text + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +}; + const _displayItem = (item, searchTerms, highlightTerms) => { const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; @@ -90,25 +100,30 @@ const _displayItem = (item, searchTerms, highlightTerms) => { let linkEl = listItem.appendChild(document.createElement("a")); linkEl.href = linkUrl + anchor; linkEl.dataset.score = score; - linkEl.innerHTML = title; + linkEl.innerHTML = _escapeHTML(title); if (descr) { listItem.appendChild(document.createElement("span")).innerHTML = - " (" + descr + ")"; + ` (${_escapeHTML(descr)})`; // highlight search terms in the description - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); - } - else if (showSearchSummary) + if (SPHINX_HIGHLIGHT_ENABLED) + // SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js + highlightTerms.forEach((term) => + _highlightText(listItem, term, "highlighted"), + ); + } else if (showSearchSummary) fetch(requestUrl) .then((responseData) => responseData.text()) .then((data) => { if (data) listItem.appendChild( - Search.makeSearchSummary(data, searchTerms, anchor) + Search.makeSearchSummary(data, searchTerms, anchor), ); // highlight search terms in the summary - if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js - highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + if (SPHINX_HIGHLIGHT_ENABLED) + // SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js + highlightTerms.forEach((term) => + _highlightText(listItem, term, "highlighted"), + ); }); Search.output.appendChild(listItem); }; @@ -117,14 +132,14 @@ const _finishSearch = (resultCount) => { Search.title.innerText = _("Search Results"); if (!resultCount) Search.status.innerText = Documentation.gettext( - "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.", ); else Search.status.innerText = Documentation.ngettext( "Search finished, found one page matching the search query.", "Search finished, found ${resultCount} pages matching the search query.", resultCount, - ).replace('${resultCount}', resultCount); + ).replace("${resultCount}", resultCount); }; const _displayNextItem = ( results, @@ -138,7 +153,7 @@ const _displayNextItem = ( _displayItem(results.pop(), searchTerms, highlightTerms); setTimeout( () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), - 5 + 5, ); } // search finished, update title and status message @@ -170,9 +185,10 @@ const _orderResultsByScoreThenName = (a, b) => { * This is the same as ``\W+`` in Python, preserving the surrogate pair area. */ if (typeof splitQuery === "undefined") { - var splitQuery = (query) => query + var splitQuery = (query) => + query .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) - .filter(term => term) // remove remaining empty strings + .filter((term) => term); // remove remaining empty strings } /** @@ -184,16 +200,23 @@ const Search = { _pulse_status: -1, htmlToText: (htmlString, anchor) => { - const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + const htmlElement = new DOMParser().parseFromString( + htmlString, + "text/html", + ); for (const removalQuery of [".headerlink", "script", "style"]) { - htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + htmlElement.querySelectorAll(removalQuery).forEach((el) => { + el.remove(); + }); } if (anchor) { - const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + const anchorContent = htmlElement.querySelector( + `[role="main"] ${anchor}`, + ); if (anchorContent) return anchorContent.textContent; console.warn( - `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`, ); } @@ -202,7 +225,7 @@ const Search = { if (docContent) return docContent.textContent; console.warn( - "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template.", ); return ""; }, @@ -287,12 +310,8 @@ const Search = { const queryTermLower = queryTerm.toLowerCase(); // maybe skip this "word" - // stopwords array is from language_data.js - if ( - stopwords.indexOf(queryTermLower) !== -1 || - queryTerm.match(/^\d+$/) - ) - return; + // stopwords set is from language_data.js + if (stopwords.has(queryTermLower) || queryTerm.match(/^\d+$/)) return; // stem the word let word = stemmer.stemWord(queryTermLower); @@ -304,8 +323,12 @@ const Search = { } }); - if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js - localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + if (SPHINX_HIGHLIGHT_ENABLED) { + // SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js + localStorage.setItem( + "sphinx_highlight_terms", + [...highlightTerms].join(" "), + ); } // console.debug("SEARCH: searching for:"); @@ -318,7 +341,13 @@ const Search = { /** * execute search (requires search index to be loaded) */ - _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + _performSearch: ( + query, + searchTerms, + excludedTerms, + highlightTerms, + objectTerms, + ) => { const filenames = Search._index.filenames; const docNames = Search._index.docnames; const titles = Search._index.titles; @@ -334,10 +363,15 @@ const Search = { const queryLower = query.toLowerCase().trim(); for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + if ( + title.toLowerCase().trim().includes(queryLower) + && queryLower.length >= title.length / 2 + ) { for (const [file, id] of foundTitles) { - const score = Math.round(Scorer.title * queryLower.length / title.length); - const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + const score = Math.round( + (Scorer.title * queryLower.length) / title.length, + ); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles normalResults.push([ docNames[file], titles[file] !== title ? `${titles[file]} > ${title}` : title, @@ -353,9 +387,9 @@ const Search = { // search for explicit entries in index directives for (const [entry, foundEntries] of Object.entries(indexEntries)) { - if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + if (entry.includes(queryLower) && queryLower.length >= entry.length / 2) { for (const [file, id, isMain] of foundEntries) { - const score = Math.round(100 * queryLower.length / entry.length); + const score = Math.round((100 * queryLower.length) / entry.length); const result = [ docNames[file], titles[file], @@ -376,11 +410,13 @@ const Search = { // lookup as object objectTerms.forEach((term) => - normalResults.push(...Search.performObjectSearch(term, objectTerms)) + normalResults.push(...Search.performObjectSearch(term, objectTerms)), ); // lookup as search terms in fulltext - normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + normalResults.push( + ...Search.performTermsSearch(searchTerms, excludedTerms), + ); // let the scorer override scores with a custom scoring function if (Scorer.score) { @@ -401,7 +437,11 @@ const Search = { // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept let seen = new Set(); results = results.reverse().reduce((acc, result) => { - let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + let resultStr = result + .slice(0, 4) + .concat([result[5]]) + .map((v) => String(v)) + .join(","); if (!seen.has(resultStr)) { acc.push(result); seen.add(resultStr); @@ -413,8 +453,20 @@ const Search = { }, query: (query) => { - const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); - const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + const [ + searchQuery, + searchTerms, + excludedTerms, + highlightTerms, + objectTerms, + ] = Search._parseQuery(query); + const results = Search._performSearch( + searchQuery, + searchTerms, + excludedTerms, + highlightTerms, + objectTerms, + ); // for debugging //Search.lastresults = results.slice(); // a copy @@ -437,7 +489,7 @@ const Search = { const results = []; const objectSearchCallback = (prefix, match) => { - const name = match[4] + const name = match[4]; const fullname = (prefix ? prefix + "." : "") + name; const fullnameLower = fullname.toLowerCase(); if (fullnameLower.indexOf(object) < 0) return; @@ -489,9 +541,7 @@ const Search = { ]); }; Object.keys(objects).forEach((prefix) => - objects[prefix].forEach((array) => - objectSearchCallback(prefix, array) - ) + objects[prefix].forEach((array) => objectSearchCallback(prefix, array)), ); return results; }, @@ -516,8 +566,14 @@ const Search = { // find documents, if any, containing the query word in their text/title term indices // use Object.hasOwnProperty to avoid mismatching against prototype properties const arr = [ - { files: terms.hasOwnProperty(word) ? terms[word] : undefined, score: Scorer.term }, - { files: titleTerms.hasOwnProperty(word) ? titleTerms[word] : undefined, score: Scorer.title }, + { + files: terms.hasOwnProperty(word) ? terms[word] : undefined, + score: Scorer.term, + }, + { + files: titleTerms.hasOwnProperty(word) ? titleTerms[word] : undefined, + score: Scorer.title, + }, ]; // add support for partial matches if (word.length > 2) { @@ -558,7 +614,8 @@ const Search = { // create the mapping files.forEach((file) => { if (!fileMap.has(file)) fileMap.set(file, [word]); - else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + else if (fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); }); }); @@ -569,11 +626,11 @@ const Search = { // as search terms with length < 3 are discarded const filteredTermCount = [...searchTerms].filter( - (term) => term.length > 2 + (term) => term.length > 2, ).length; if ( - wordList.length !== searchTerms.size && - wordList.length !== filteredTermCount + wordList.length !== searchTerms.size + && wordList.length !== filteredTermCount ) continue; @@ -581,10 +638,10 @@ const Search = { if ( [...excludedTerms].some( (term) => - terms[term] === file || - titleTerms[term] === file || - (terms[term] || []).includes(file) || - (titleTerms[term] || []).includes(file) + terms[term] === file + || titleTerms[term] === file + || (terms[term] || []).includes(file) + || (titleTerms[term] || []).includes(file), ) ) break; @@ -626,7 +683,8 @@ const Search = { let summary = document.createElement("p"); summary.classList.add("context"); - summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + summary.textContent = + top + text.substr(startWithContext, 240).trim() + tail; return summary; }, diff --git a/main/_static/sphinx_highlight.js b/main/_static/sphinx_highlight.js index 8a96c69..a74e103 100644 --- a/main/_static/sphinx_highlight.js +++ b/main/_static/sphinx_highlight.js @@ -1,7 +1,7 @@ /* Highlighting utilities for Sphinx HTML documentation. */ "use strict"; -const SPHINX_HIGHLIGHT_ENABLED = true +const SPHINX_HIGHLIGHT_ENABLED = true; /** * highlight a given string on a node by wrapping it in @@ -13,9 +13,9 @@ const _highlight = (node, addItems, text, className) => { const parent = node.parentNode; const pos = val.toLowerCase().indexOf(text); if ( - pos >= 0 && - !parent.classList.contains(className) && - !parent.classList.contains("nohighlight") + pos >= 0 + && !parent.classList.contains(className) + && !parent.classList.contains("nohighlight") ) { let span; @@ -30,13 +30,7 @@ const _highlight = (node, addItems, text, className) => { span.appendChild(document.createTextNode(val.substr(pos, text.length))); const rest = document.createTextNode(val.substr(pos + text.length)); - parent.insertBefore( - span, - parent.insertBefore( - rest, - node.nextSibling - ) - ); + parent.insertBefore(span, parent.insertBefore(rest, node.nextSibling)); node.nodeValue = val.substr(0, pos); /* There may be more occurrences of search term in this node. So call this * function recursively on the remaining fragment. @@ -46,7 +40,7 @@ const _highlight = (node, addItems, text, className) => { if (isInSVG) { const rect = document.createElementNS( "http://www.w3.org/2000/svg", - "rect" + "rect", ); const bbox = parent.getBBox(); rect.x.baseVal.value = bbox.x; @@ -65,7 +59,7 @@ const _highlightText = (thisNode, text, className) => { let addItems = []; _highlight(thisNode, addItems, text, className); addItems.forEach((obj) => - obj.parent.insertAdjacentElement("beforebegin", obj.target) + obj.parent.insertAdjacentElement("beforebegin", obj.target), ); }; @@ -73,25 +67,31 @@ const _highlightText = (thisNode, text, className) => { * Small JavaScript module for the documentation. */ const SphinxHighlight = { - /** * highlight the search words provided in localstorage in the text */ highlightSearchWords: () => { - if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight // get and clear terms from localstorage const url = new URL(window.location); const highlight = - localStorage.getItem("sphinx_highlight_terms") - || url.searchParams.get("highlight") - || ""; - localStorage.removeItem("sphinx_highlight_terms") - url.searchParams.delete("highlight"); - window.history.replaceState({}, "", url); + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms"); + // Update history only if '?highlight' is present; otherwise it + // clears text fragments (not set in window.location by the browser) + if (url.searchParams.has("highlight")) { + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + } // get individual terms from highlight string - const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + const terms = highlight + .toLowerCase() + .split(/\s+/) + .filter((x) => x); if (terms.length === 0) return; // nothing to do // There should never be more than one element matching "div.body" @@ -107,11 +107,11 @@ const SphinxHighlight = { document .createRange() .createContextualFragment( - '" - ) + '", + ), ); }, @@ -125,7 +125,7 @@ const SphinxHighlight = { document .querySelectorAll("span.highlighted") .forEach((el) => el.classList.remove("highlighted")); - localStorage.removeItem("sphinx_highlight_terms") + localStorage.removeItem("sphinx_highlight_terms"); }, initEscapeListener: () => { @@ -134,10 +134,15 @@ const SphinxHighlight = { document.addEventListener("keydown", (event) => { // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) + return; // bail with special keys - if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; - if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) + return; + if ( + DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + && event.key === "Escape" + ) { SphinxHighlight.hideSearchWords(); event.preventDefault(); } diff --git a/main/developing.html b/main/developing.html index 4245e86..5196e32 100644 --- a/main/developing.html +++ b/main/developing.html @@ -16,8 +16,8 @@ - - + + diff --git a/main/genindex.html b/main/genindex.html index 94d82b8..06dbdd6 100644 --- a/main/genindex.html +++ b/main/genindex.html @@ -15,8 +15,8 @@ - - + + diff --git a/main/index.html b/main/index.html index e93b59c..49d04dc 100644 --- a/main/index.html +++ b/main/index.html @@ -16,8 +16,8 @@ - - + + @@ -107,7 +107,9 @@
  • Plugin Index
      +
    • keycloak_authentication_flow – Allows administration of Keycloak authentication flows via Keycloak API
    • keycloak_client – Allows administration of Keycloak clients via Keycloak API
    • +
    • keycloak_client_scope – Allows administration of Keycloak client scopes via Keycloak API
    • keycloak_realm – Allows administration of Keycloak realm via Keycloak API
    • keycloak_role – Allows administration of Keycloak roles via Keycloak API
    • keycloak_user_federation – Allows administration of Keycloak user federations via Keycloak API
    • diff --git a/main/objects.inv b/main/objects.inv index 048577bb3acdf63cc44831b9a690afaa897c861e..be76981dc466e7a2c1b44d39681f1866cd4b0e31 100644 GIT binary patch delta 758 zcmV z3__Aio)AfSI@-W4Vw`Ny92gJ*=$T6p!F)Qtdz#kSOvO-*Q-47h4A6u#29e@H_ym7I z3?_^!j2QJpstdKahE&o>H-t;Z-@=5)c_Be5WMlzMY231WMN{#y52zh{*sH-cHQ1`b zRt+w>>_w9lD^k2yg9ox1hD6GYE^=8&UPH7|^>=0-w2mqL2ci`Em`B8D zaWA**o8IdU8h;)?32*4XDAa?v8X~$!t-7XlXs4btDI&gwbCPKg-u!7cuPLoNx+1Fs z`W!H~$IPFX-rIQhmE;TvOWY!?Td((}Y5at?F8-pl4q$1Bd>-@Z8q8sBdW#}wj+?3K z^kAo8@svZW{IVGO_A^KbyG=T0a6pf~5||6f!X?CznSax=Qy5#I>pdz$Yzh5DwALqIpzZqjRgao=~4^W z_uSjXcYpFZ8TB&$`|}r#S#c#0i4&SqAa(XI)Izx_GQAE;IO`u&^_Pg67)KdJ ztII%IJ5{fuu3ZfsWpdN0P{^XXB-=gV#AY$u$!~Q*-`F!U{Svpe6;?%qt8G$0AQQ!> z>VHvkVYLhNC=WNCTG`OdxxK4itKK9ZpsN3##t2)m@K;W~GlnvnBn)0i z0TC%B&xoWvpY7lfGfsAB1q_G)^vETMV6_Nu9v1y-I-*O3xqqN*258P1gSe`L@P+yT z(U>UiFk*Hd(p^L`qmaue<4LKyR-_nN1FLB5S((yYyti^|p6{BTxYrY#p4jxnE!LK-f1(OoL~u=HBB=@8Z2kwO6^}857C>0$XURJC^@{2; z;~26}{whCCAb)8JH=fezYsP71j%G!!fPPL?nIp(iRYvlmVSb$r$JR563Ed*SJvgDp zu^d7~%zraLlB4_fz}-7MD?pj4(@HHM zP*fc)_XpfA=$a4O-MV=@<|7h=1r1!*Aq!}ozg#6;M>J&P{`~rlLdN)uKqSd%PKDI> z1@Vp1u9F3Nro;rpCh^%;n@f6g*; z?KO0=l6)V}3WW^HMcFv@f`yj{xmD!{PA=;2P-}yBL5=Heoo`SG^;_jXRdgXe1obG7 zw?Z$h%dEKRTCa5G`!8Q?nD!{%N=>&weE6$Ab=F{b~t?< d)9FuoeMwRX)aSmm#@&Q;?pOUM_zQa+KGUSNIQswq diff --git a/main/plugins/index.html b/main/plugins/index.html index 0d978fe..b0fe281 100644 --- a/main/plugins/index.html +++ b/main/plugins/index.html @@ -16,12 +16,12 @@ - - + + - + @@ -48,7 +48,9 @@
      • Ansible Collection - middleware_automation.keycloak
      • Plugin Index
          +
        • keycloak_authentication_flow – Allows administration of Keycloak authentication flows via Keycloak API
        • keycloak_client – Allows administration of Keycloak clients via Keycloak API
        • +
        • keycloak_client_scope – Allows administration of Keycloak client scopes via Keycloak API
        • keycloak_realm – Allows administration of Keycloak realm via Keycloak API
        • keycloak_role – Allows administration of Keycloak roles via Keycloak API
        • keycloak_user_federation – Allows administration of Keycloak user federations via Keycloak API
        • @@ -105,6 +107,17 @@