|
| 1 | +# How to add a multi-language code examples to redis.io |
| 2 | + |
| 3 | +## Configure Hugo |
| 4 | + |
| 5 | +The website redis.io is built from Markdown files using [Hugo](https://gohugo.io). Multi-language code example support is configured in Hugo by adding information to its configuration file, `config.toml`. |
| 6 | +There are two sections that need to updated when new languages are added. |
| 7 | + |
| 8 | +1. In the `[params]` section: |
| 9 | + |
| 10 | + ```toml |
| 11 | + clientsExamples = ["Python", "Node.js", "Java-Sync", "Java-Async", "Java-Reactive", "Go", "C#", "RedisVL", "PHP"] |
| 12 | + ``` |
| 13 | + |
| 14 | + The order of the `clientsExamples` list matters: it's the order in which the language tabs are presented for each code example. |
| 15 | +1. In the `[params.clientsConfig]` section: |
| 16 | + |
| 17 | + ```toml |
| 18 | + [params.clientsConfig] |
| 19 | + "Python"={quickstartSlug="redis-py"} |
| 20 | + "Node.js"={quickstartSlug="nodejs"} |
| 21 | + "Java-sync"={quickstartSlug="jedis"} |
| 22 | + "Java-async"={quickstartSlug="lettuce"} |
| 23 | + "Java-reactive"={quickstartSlug="lettuce"} |
| 24 | + "Go"={quickstartSlug="go"} |
| 25 | + "C#"={quickstartSlug="dotnet"} |
| 26 | + "RedisVL"={quickstartSlug="redis-vl"} |
| 27 | + "PHP"={quickstartSlug="php"} |
| 28 | + ``` |
| 29 | + |
| 30 | +This configuration, along with the configuration steps below, is used to control the behavior of the Hugo shortcode that was developed to show tabbed code examples. |
| 31 | +A shortcode is a simple snippet inside a content file that Hugo will render using a predefined template. This template can contain HTML and JavaScript. |
| 32 | + |
| 33 | +### How to add a new programming language |
| 34 | + |
| 35 | +#### Add the components file |
| 36 | + |
| 37 | +The folder `data/components` contains one component configuration file for each supported language. These files contain information about the GitHub repos that house the code examples. |
| 38 | + |
| 39 | +Here is the configuration file for Python, `redis_py.json`: |
| 40 | + |
| 41 | +```json |
| 42 | +{ |
| 43 | + "id": "redis_py", |
| 44 | + "type": "client", |
| 45 | + "name": "redis-py", |
| 46 | + "language": "Python", |
| 47 | + "label": "Python", |
| 48 | + "repository": { |
| 49 | + "git_uri": "https://github.com/redis/redis-py" |
| 50 | + }, |
| 51 | + "examples": { |
| 52 | + "git_uri": "https://github.com/redis/redis-py", |
| 53 | + "path": "doctests", |
| 54 | + "pattern": "*.py" |
| 55 | + } |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | +The `language` property needs to match the value that was added to the `config.toml` file in the previous step. The `label` property, while generally the same as `language`, may be set to a string that is different from `language`. For RedisVL, `language` is set to `Python` and `label` is set to `RedisVL`. The `examples` property points to a GitHub repository, a path under which examples should be searched, and a file name pattern. The current logic will scan for examples that fulfill the filename pattern within the given path. |
| 60 | + |
| 61 | +#### Register the component file |
| 62 | + |
| 63 | +Register your component file by adding it to the `clients` array in the `index.json` file, which resides in the the same folder as the per-language JSON files. The entry should match the file name prefix and ID of the component. |
| 64 | + |
| 65 | +Here is an example: |
| 66 | +```json |
| 67 | +"clients": [ |
| 68 | + "nredisstack", |
| 69 | + "go_redis", |
| 70 | + "node_redis", |
| 71 | + "php", |
| 72 | + "redis_py", |
| 73 | + "jedis", |
| 74 | + "lettuce_async", |
| 75 | + "lettuce_reactive", |
| 76 | + "redis_vl" |
| 77 | +] |
| 78 | +``` |
| 79 | + |
| 80 | +Code examples are pulled from the GitHub repo for each supported language at docs site build time. |
| 81 | + |
| 82 | +### Verify that your language is supported by the source code file parser |
| 83 | + |
| 84 | +Component handling is implemented in `build/components/component.py`. The example file parser that is used by it is implemented inside `build/components/example.py`. Add any language-specific information you need to have the build code support your language's examples. |
| 85 | + |
| 86 | +```python |
| 87 | +TEST_MARKER = { |
| 88 | + 'java': '@Test', |
| 89 | + 'java-sync': '@Test', |
| 90 | + 'java-async': '@Test', |
| 91 | + 'java-reactive': '@Test', |
| 92 | + 'c#': r'\[Fact]|\[SkipIfRedis\(.*\)]' |
| 93 | +} |
| 94 | +PREFIXES = { |
| 95 | + 'python': '#', |
| 96 | + 'node.js': '//', |
| 97 | + 'java': '//', |
| 98 | + 'java-sync': '//', |
| 99 | + 'java-async': '//', |
| 100 | + 'java-reactive': '//', |
| 101 | + 'go': '//', |
| 102 | + 'c#': '//', |
| 103 | + 'redisvl': '#', |
| 104 | + 'php': '//' |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +The `TEST_MARKER` dictionary maps programming languages to test framework annotations, which allows the parser to filter such source code lines out. The `PREFIXES` dictionary maps each language to its comment prefix. Python, for example, uses a hashtag (`#`) to start a comment. |
| 109 | + |
| 110 | +## Understand special comments in the example source code files |
| 111 | + |
| 112 | +Each code example uses special comments, such as `HIDE_START` and `REMOVE_START`, to control how the examples are displayed. The following list gives an explanation: |
| 113 | + |
| 114 | +- `EXAMPLE: id`: Defines the identifier of the source code example file, where `id` is any common string (for example, `cmds_string`). IDs should only contain ASCII alphanumeric characters, underline characters (`_`), or hyphen characters (`-`). Do not use multibyte characters. |
| 115 | +- `BINDER_ID id`: Defines the [BinderHub](https://binderhub.readthedocs.io/en/latest/) commit hash for the example. This is used to generate a link to a BinderHub instance that will run the example. |
| 116 | +- `HIDE_START`: Starts a code block that should be *hidden* when showing the example. This code block will only become visible if **unhide** (the eye button) is clicked. |
| 117 | +- `HIDE_END`: Marks the end a hidden code block. |
| 118 | +- `REMOVE_START`: Starts a code block that should be entirely removed when the example is processed by the build code. This is useful for removing lines of code that do not contribute to the example but are needed to embed the code into a proper test case or framework. Good examples of such code blocks are imports of external libraries or test assertions. |
| 119 | +- `REMOVE_END`: Marks the end of a code block that should be removed from the example. |
| 120 | +- `STEP_START step-name`: Starts a code block that represents a specific step in a set of examples. |
| 121 | +- `STEP_END`: Marks the end of a code block that represents a specific step in a set of examples. |
| 122 | + |
| 123 | +## Add examples to the client library or to the local_examples directory |
| 124 | + |
| 125 | +Examples are added to either a client repo, or, temporarily, to the `local_examples` directory in the `redis.io/docs` repo. |
| 126 | + |
| 127 | +### Add examples to the client libraries |
| 128 | + |
| 129 | +Add a source code file to an appropriate client repo. Consult the /data/components/<client-component>.json file for the location. |
| 130 | + |
| 131 | +| Programming Language | GitHub Repo | Default directory | |
| 132 | +|----------------------|-----------------------------------------------------|---------------------------------------------------| |
| 133 | +| C# | [NRedisStack](https://github.com/redis/NRedisStack) | `tests/Doc` | |
| 134 | +| Go | [go-redis](https://github.com/redis/go-redis) | `doctests` | |
| 135 | +| Java | [jedis](https://github.com/redis/jedis) | `src/test/java/io/redis/examples` | |
| 136 | +| | [Lettuce](https://github.com/redis/lettuce) | `src/test/java/io/redis/examples/async` or | |
| 137 | +| | | `src/test/java/io/redis/examples/reactive` | |
| 138 | +| Node.js | [node-redis](https://github.com/redis/node-redis) | `doctests` | |
| 139 | +| PHP | [Predis](https://github.com/predis/predis) | Examples, for now, are stored in `local_examples` | |
| 140 | +| Python | [redis-py](https://github.com/redis/redis-py) | `doctests` | |
| 141 | +| | [RedisVL](https://github.com/redis/redis-vl-python) | `doctests` | |
| 142 | + |
| 143 | +### Add examples to the local_examples directory |
| 144 | + |
| 145 | +At times, it can take quite a while to get new or updated examples through the review process. To make the examples available immediately on the docs site, you can place examples temporarily in the `local_examples/client-specific` directory. The manner in which files are added isn't terribly important, as the build code will recursively walk the entire directory, so it will find examples in any directory under `local_examples`. |
| 146 | + |
| 147 | +``` |
| 148 | +local_examples |
| 149 | +├── client-specific |
| 150 | +│ ├── go |
| 151 | +│ │ ... |
| 152 | +│ ├── jedis |
| 153 | +│ │ ... |
| 154 | +│ ├── lettuce-async |
| 155 | +│ │ ... |
| 156 | +│ ├── lettuce-reactive |
| 157 | +│ │ ... |
| 158 | +│ ├── nodejs |
| 159 | +│ │ ... |
| 160 | +│ └── redis-py |
| 161 | +│ ... |
| 162 | +``` |
| 163 | + |
| 164 | +## Add your example to the content page |
| 165 | + |
| 166 | +In order to add a multi-language code example to a content page, use the `clients-example` shortcode: |
| 167 | + |
| 168 | +``` |
| 169 | +{{< clients-example id ... />}} |
| 170 | +``` |
| 171 | + |
| 172 | +The ID is the same one you used with `EXAMPLE: id` in the first line of your code example. |
| 173 | + |
| 174 | +### Named versus positional parameters |
| 175 | + |
| 176 | +The `clients-example` shortcode supports both positional and named parameters. The lion's share of current examples use positional parameters, but, going forward, names parameters should be used. |
| 177 | + |
| 178 | +Named parameters: |
| 179 | + |
| 180 | +- set: Name of the example set (required) |
| 181 | +- step: Example step name (required) |
| 182 | +- lang_filter: Language filter (optional, default: "") |
| 183 | +- max_lines: Maximum number of lines shown by default (optional, default: 100) |
| 184 | +- dft_tab_name: Custom first tab name (optional, default: ">_ Redis CLI") |
| 185 | +- dft_tab_link_title: Custom first tab footer link title (optional) |
| 186 | +- dft_tab_url: Custom first tab footer link URL (optional) |
| 187 | +- show_footer: Show footer (optional, default: true) |
| 188 | + |
| 189 | +Positional parameters (for backward compatibility): |
| 190 | + |
| 191 | +- 0: example set name |
| 192 | +- 1: step name |
| 193 | +- 2: language filter |
| 194 | +- 3: max lines |
| 195 | +- 4: custom first tab name |
| 196 | +- 5: custom first tab footer link title |
| 197 | +- 6: custom first tab footer link URL |
| 198 | + |
| 199 | +### Examples |
| 200 | + |
| 201 | +When converting existing content with redis-cli examples to the new format, you can wrap the existing redis-cli example: |
| 202 | + |
| 203 | +``` |
| 204 | +{{< clients-example set="set_and_get" step="" >}} |
| 205 | +> set mykey somevalue |
| 206 | +OK |
| 207 | +> get mykey |
| 208 | +"somevalue" |
| 209 | +{{< /clients-example >}} |
| 210 | +``` |
| 211 | + |
| 212 | +If the redis-cli example is too long you can hide some lines by specifying the limit as the fourth argument: |
| 213 | + |
| 214 | +``` |
| 215 | +{{< clients-example set="set_and_get" step="" lang_filter="" max_lines="2" >}} |
| 216 | +> set mykey somevalue |
| 217 | +OK |
| 218 | +> get mykey <-- this line will be hidden |
| 219 | +"somevalue" <-- this line will be hidden |
| 220 | +{{< /clients-example >}} |
| 221 | +``` |
| 222 | + |
| 223 | +To refer to a particular step placed in between `STEP_START stepname` and `STEP_END` comments in the code example, you should use the second argument to define the name of the step: |
| 224 | + |
| 225 | +``` |
| 226 | +{{< clients-example set="id" step="stepname" />}} |
| 227 | +``` |
| 228 | + |
| 229 | +If you need to embed an example for a specific programming language, the third argument should be defined: |
| 230 | + |
| 231 | +``` |
| 232 | +{{< clients-example set="id" step="stepname" lang_filter="lang" />}} |
| 233 | +``` |
| 234 | + |
| 235 | +The following example shows the `connect` step of a Python example: |
| 236 | + |
| 237 | +``` |
| 238 | +{{< clients-example set="set_and_get" step="connect" lang_filter="Python" />}} |
| 239 | +``` |
| 240 | +The programming language name should match the value in the Hugo configuration file. |
0 commit comments