diff --git a/bin/extract-examples.bash b/bin/extract-examples.bash new file mode 100644 index 0000000..78beeed --- /dev/null +++ b/bin/extract-examples.bash @@ -0,0 +1,157 @@ +#!/usr/bin/env bash + +set -o errexit # Exit script when a command exits with non-zero status. +set -o errtrace # Exit on error inside any functions or sub-shells. +set -o nounset # Exit script on use of an undefined variable. +set -o pipefail # Return exit status of the last command in the pipe that exited with a non-zero exit code + +# ============================================================================== +#/ Extract HTML and JS code examples from Markdown files +# ------------------------------------------------------------------------------ +#/ +#/ Usage: $0 [-h] +#/ +#/ Where: +#/ The path to the Markdown files to extract examples from +#/ +#/ Options: +#/ -h|--help Print this help dialogue and exit +#/ +#/ This script will recursively extract all the code examples from the Markdown +#/ files found in the given path. The extracted examples will be saved in a +#/ directory called 'examples' in the same directory as the Markdown file(s). +#/ +#/ The script will look for code blocks that start with "```javascript" or +#/ "```html". It will extract the content of the code block and save it in a +#/ file with the same name as the Markdown file but with a number at the end and +#/ a .js or .html extension (depending on the code block). +#/ +#/ For example, if the Markdown file is called 'example.md', the extracted +#/ examples will be saved in 'examples/example.1.js', 'examples/example.2.js', +#/ +#/ Usage example: +#/ +#/ $0 ./docs + +: "${OUTPUT_ERROR:=/dev/stderr}" +: "${OUTPUT_NORMAL:=/dev/stdout}" + +: readonly "${COLOR_RED:=$(tput setaf 1)}" +: readonly "${COLOR_WHITE:=$(tput setaf 7)}" +: readonly "${RESET_TEXT:=$(tput sgr0)}" # turn off all attributes +: readonly "${TEXT_BOLD:=$(tput bold)}" # turn on bold (extra bright) mode +: readonly "${TEXT_DIM:=$(tput dim)}" # turn on half-bright mode +: readonly -i "${EXIT_NOT_ENOUGH_PARAMETERS:=65}" + +error(){ + if [ ${#} == 1 ];then + logMessage "ERROR " "${COLOR_RED}" "${@}" "Call with ${TEXT_BOLD}--help${RESET_TEXT}${TEXT_DIM} for more information." >&2 + else + logMessage "ERROR " "${COLOR_RED}" "${@}" >&2 + fi +} + +logMessage(){ + local sType="${1?Three parameters required: }" + local sColor="${2?Three parameters required: }" + local sMessage="${3?Three parameters required: }" + + message "${COLOR_WHITE}[${sColor}${sType}${COLOR_WHITE}]${RESET_TEXT}" "${sMessage}" + + # Each additional parameter will be treated as extra information to display with the error + if [[ "$#" -gt 3 ]]; then + shift 3 + for sMessage in "$@"; do + message " " "${TEXT_DIM}${sMessage}${RESET_TEXT}" + done + fi +} + +message(){ + local sPrefix="${1?Two parameters required: }" + local sMessage="${2?Two parameters required: }" + + echo -e "${sPrefix} ${sMessage}" +} + +statusMessage() { + message " -----> " "${@}" +} + +topicMessage() { + message " =====> " "${@}" +} + +usage() { + local sFile sScript sUsage + + readonly sFile="${0}" + + sScript="$(basename "${sFile}")" + sUsage="$(grep '^#/' < "${sFile}" | cut -c4-)" + + readonly sScript sUsage + + echo -e "${sUsage//\$0/${sScript}}\n" >> /dev/stdout +} + +extract_examples() { + local -i iCounter + local sBaseName sDirname sFile sPath sExamplePath + + + if [[ ${#@} -lt 1 ]]; then + error "One parameter required: " + exit "${EXIT_NOT_ENOUGH_PARAMETERS}" + fi + + + if [[ "$1" == '-h' || "$1" == '--help' ]];then + usage + return 0 + fi + + local sPath="${1?One parameter required: }" + + sPath="$(realpath "${sPath}")" + sExamplePath="${sPath}/examples" + + topicMessage "Extracting examples from ${sPath} to ${sExamplePath}" + + while IFS= read -r -d '' sFile; do + iCounter=0 + sDirname="$(dirname "${sFile}")" + sDirname="${sDirname#"${sPath}"}" + sBaseName="$(basename "${sFile}" '.md')" + + # shellcheck disable=SC2016 + grep -ozP '```(javascript|html)(.|\n)+?```' < "${sFile}" | while IFS= read -r sLine; do + local sFilename sExtension + + if grep -qP '```(javascript|html)' <<< "${sLine}"; then + iCounter=$((iCounter + 1)) + statusMessage "Found example ${iCounter} in ${sDirname}/${sBaseName}.md" + sExtension="$(grep -oP '```(javascript|html)' <<< "${sLine}" | cut -c 4-)" + if [ "${sExtension}" == "javascript" ]; then + sExtension="js" + fi + + sFilename="${sExamplePath}${sDirname}/${sBaseName}.${iCounter}.${sExtension}" + + if [ ! -d "$(dirname "${sFilename}")" ]; then + mkdir -p "$(dirname "${sFilename}")" + fi + + echo '' > "${sFilename}" + else + echo "${sLine}" >> "${sFilename}" + fi + done + done < <(find "${sPath}" -type f -name '*.md' -print0) +} + +if [ "$0" != "${BASH_SOURCE[0]}" ]; then + export -f extract_examples +else + extract_examples "${@}" +fi diff --git a/docs/examples/asserts.1.js b/docs/examples/asserts.1.js new file mode 100644 index 0000000..b7a7f1e --- /dev/null +++ b/docs/examples/asserts.1.js @@ -0,0 +1,17 @@ + +let error + +if (error = assert.fails(url, { + searchParams: { + response_type: 'code', + client_id: 'mockClientId', + state: assert.optional(/.+/) + } +})) { + return metro.response({ + url: req.url, + status: 400, + statusText: 'Bad Request', + body: '400 Bad Request' + }) +} diff --git a/docs/examples/asserts.2.js b/docs/examples/asserts.2.js new file mode 100644 index 0000000..1b78ebc --- /dev/null +++ b/docs/examples/asserts.2.js @@ -0,0 +1,6 @@ + +assert.check(oauth2, { + client_id: /.+/, + authRedirectURL: /.+/, + scope: /.*/ +}) diff --git a/docs/examples/asserts.3.js b/docs/examples/asserts.3.js new file mode 100644 index 0000000..e20141a --- /dev/null +++ b/docs/examples/asserts.3.js @@ -0,0 +1,2 @@ + +metro.assert.enable() diff --git a/docs/examples/debugging.1.js b/docs/examples/debugging.1.js new file mode 100644 index 0000000..bc4c472 --- /dev/null +++ b/docs/examples/debugging.1.js @@ -0,0 +1,10 @@ + +metro.trace.add('debug', { + request: (req) => { + if (req.searchParams.has('foo')) { + debugger; + + } + + } +}) diff --git a/docs/examples/debugging.2.js b/docs/examples/debugging.2.js new file mode 100644 index 0000000..1bd098e --- /dev/null +++ b/docs/examples/debugging.2.js @@ -0,0 +1,2 @@ + +metro.trace.add('group', metro.trace.group()) diff --git a/docs/examples/example-helper.css b/docs/examples/example-helper.css new file mode 100644 index 0000000..0acf6c2 --- /dev/null +++ b/docs/examples/example-helper.css @@ -0,0 +1,180 @@ +title { + display: inline; +} + +header, main { + padding-bottom: 0; + padding-top: 0; +} + +main blockquote { + font-size: 1.35em; + margin-top: 0; + padding: 0; +} + +main h2 { + width: 100%; + text-align: center; +} + +main li { + display: block; + overflow: auto; + white-space: pre; +} + +main pre code { + width: fit-content; +} + +main script::after { + content: ''; +} + +main script::before { + content: ' + +
+ +

MetroJS - Examples

+

This page contains all examples from the MetroJs documentation.

+

Select an example from the menu to see the code and the result.

+
+ +
+
+

Code

+
+
+
+

Result

+ +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/docs/examples/introduction.1.js b/docs/examples/introduction.1.js new file mode 100644 index 0000000..d02aead --- /dev/null +++ b/docs/examples/introduction.1.js @@ -0,0 +1,20 @@ + +import * as metro from '@muze-nl/metro' +import jsonmw from '@muze-nl/metro/src/mw/json.mjs' + +const token = 'my-token' + +const client = metro.client({ + url: 'https://api.github.com/', + headers: { + 'Authorization':'Bearer '+token + } +}).with(jsonmw()) + +let response = await client.get('/repos/poef/metrojs/commits') + +if (response.ok) { + for ( const commit of response.body ) { + console.log(commit.commit.message) + } +} diff --git a/docs/examples/middleware.1.js b/docs/examples/middleware.1.js new file mode 100644 index 0000000..60af7cf --- /dev/null +++ b/docs/examples/middleware.1.js @@ -0,0 +1,3 @@ + +import jsonmw from '@muze-nl/metro/src/mw/jsonmw' +const client = metro.client( jsonmw() ) diff --git a/docs/examples/middleware.2.js b/docs/examples/middleware.2.js new file mode 100644 index 0000000..389dd72 --- /dev/null +++ b/docs/examples/middleware.2.js @@ -0,0 +1,9 @@ + +async function myMiddleware(req,next) { + req = req.with('?foo=bar') + let res = await next(req) + if (res.ok) { + res = res.with({headers:{'X-Foo':'bar'}}) + } + return res +} diff --git a/docs/examples/middleware/echomock.1.js b/docs/examples/middleware/echomock.1.js new file mode 100644 index 0000000..29a3d6b --- /dev/null +++ b/docs/examples/middleware/echomock.1.js @@ -0,0 +1,5 @@ + +import * as metro from '@muze-nl/metro' +import echomw from '@muze-nl/metro/src/mw/echo.mock.mjs' + +const client = metro.client().with( echomw() ) diff --git a/docs/examples/middleware/errormock.1.js b/docs/examples/middleware/errormock.1.js new file mode 100644 index 0000000..10c31d3 --- /dev/null +++ b/docs/examples/middleware/errormock.1.js @@ -0,0 +1,10 @@ + +import * as metro from '@muze-nl/metro' +import errormw from '@muze-nl/metro/src/mw/error.mock.mjs' + +const client = metro.client().with( errormw() ) +try { + client.get('https://example.com/404/') +} catch(err) { + console.error(err) +} diff --git a/docs/examples/middleware/json.1.js b/docs/examples/middleware/json.1.js new file mode 100644 index 0000000..c9790b7 --- /dev/null +++ b/docs/examples/middleware/json.1.js @@ -0,0 +1,7 @@ + +import * as metro from '@muze-nl/metro' +import jsonmw from '@muze-nl/metro/src/mw/json.mjs' + +const client = metro.client().with( jsonmw({ + space: "\t" +}) ) diff --git a/docs/examples/middleware/json.2.js b/docs/examples/middleware/json.2.js new file mode 100644 index 0000000..615cc0d --- /dev/null +++ b/docs/examples/middleware/json.2.js @@ -0,0 +1,8 @@ + +let response = await client.post(url, { + some: 'data' +}) +let result +if (response.ok) { + result = response.body.something +} diff --git a/docs/examples/middleware/oauth2.1.js b/docs/examples/middleware/oauth2.1.js new file mode 100644 index 0000000..798d119 --- /dev/null +++ b/docs/examples/middleware/oauth2.1.js @@ -0,0 +1,7 @@ + +import oauth2mw from '@muze-nl/metro/src/mw/oauth2.mjs' +const client = metro.client('https://oauth2api.example.com') +.with( oauth2mw({ + client_id: myClientId, + client_secret: myClientSecret +}) ) diff --git a/docs/examples/middleware/thrower.1.js b/docs/examples/middleware/thrower.1.js new file mode 100644 index 0000000..152fc54 --- /dev/null +++ b/docs/examples/middleware/thrower.1.js @@ -0,0 +1,12 @@ + +import * as metro from '@muze-nl/metro' +import throwermw from '@muze-nl/metro/src/mw/thrower.mjs' + +const client = metro.client().with( throwermw() ) + +try { + let response = await client.get('https://example.com/404/') + result = response.text() +} catch(err) { + console.error(err) +} diff --git a/docs/examples/middleware/thrower.2.js b/docs/examples/middleware/thrower.2.js new file mode 100644 index 0000000..88011de --- /dev/null +++ b/docs/examples/middleware/thrower.2.js @@ -0,0 +1,8 @@ + +const client = metro.client().with( throwermw({ + 404: (req) => { + return client.get(req.with({ + url: 'https://example.com/' + })) + } +})) diff --git a/docs/examples/quickstart.1.html b/docs/examples/quickstart.1.html new file mode 100644 index 0000000..eb468fc --- /dev/null +++ b/docs/examples/quickstart.1.html @@ -0,0 +1,9 @@ + + + diff --git a/docs/examples/quickstart.2.html b/docs/examples/quickstart.2.html new file mode 100644 index 0000000..20ebef7 --- /dev/null +++ b/docs/examples/quickstart.2.html @@ -0,0 +1,5 @@ + + + diff --git a/docs/examples/quickstart.3.js b/docs/examples/quickstart.3.js new file mode 100644 index 0000000..ff895bf --- /dev/null +++ b/docs/examples/quickstart.3.js @@ -0,0 +1,7 @@ + +const client = metro.client() +let response = await client.post(url, { + body: metro.formdata({ + name: value + }) +}) diff --git a/docs/examples/reference/assert/fails.1.js b/docs/examples/reference/assert/fails.1.js new file mode 100644 index 0000000..5713c72 --- /dev/null +++ b/docs/examples/reference/assert/fails.1.js @@ -0,0 +1,16 @@ + +function myValidatorFunction(property, data) { + if (data.error) { + return data.error + } + return false // no problems +} + +let errors = assert.fails(response, { + status: 200, + headers: { + 'Content-Type':'application/json', + 'Etag': assert.optional(/([a-z0-9_\-])+/i) + }, + body: myValidatorFunction +}) diff --git a/docs/examples/reference/assert/oneOf.1.js b/docs/examples/reference/assert/oneOf.1.js new file mode 100644 index 0000000..f9407d7 --- /dev/null +++ b/docs/examples/reference/assert/oneOf.1.js @@ -0,0 +1,4 @@ + +let errors = assert.fails(url.searchParams, { + grant_type: assert.oneOf('refresh_token','authorization_code') +}) diff --git a/docs/examples/reference/assert/optional.1.js b/docs/examples/reference/assert/optional.1.js new file mode 100644 index 0000000..5c2d622 --- /dev/null +++ b/docs/examples/reference/assert/optional.1.js @@ -0,0 +1,4 @@ + +let errors = assert.fails(url.searchParams, { + state: assert.optional(/.+/) +}) diff --git a/docs/examples/reference/client/README.1.js b/docs/examples/reference/client/README.1.js new file mode 100644 index 0000000..410ccad --- /dev/null +++ b/docs/examples/reference/client/README.1.js @@ -0,0 +1,4 @@ + +const newClient = oldClient.with({ + url: '/otherpath/' +}) diff --git a/docs/examples/reference/client/README.2.js b/docs/examples/reference/client/README.2.js new file mode 100644 index 0000000..fd53194 --- /dev/null +++ b/docs/examples/reference/client/README.2.js @@ -0,0 +1,8 @@ + +const client = metro.client(async (req,next) => { + return next(req.with({ + headers: { + 'Authorization':'Bearer '+token + } + })) +}) diff --git a/docs/examples/reference/client/README.3.js b/docs/examples/reference/client/README.3.js new file mode 100644 index 0000000..d394778 --- /dev/null +++ b/docs/examples/reference/client/README.3.js @@ -0,0 +1,4 @@ + +const client = metro.client({ + verbs: ['get','post'] +}) diff --git a/docs/examples/reference/client/get.1.js b/docs/examples/reference/client/get.1.js new file mode 100644 index 0000000..2b9783a --- /dev/null +++ b/docs/examples/reference/client/get.1.js @@ -0,0 +1,2 @@ + +let response = await client.get('/mypath/') diff --git a/docs/examples/reference/client/post.1.js b/docs/examples/reference/client/post.1.js new file mode 100644 index 0000000..94ca7a1 --- /dev/null +++ b/docs/examples/reference/client/post.1.js @@ -0,0 +1,6 @@ + +let response = await client.post({ + body: { + key: value + } +}) diff --git a/docs/examples/reference/formdata/README.1.js b/docs/examples/reference/formdata/README.1.js new file mode 100644 index 0000000..5693638 --- /dev/null +++ b/docs/examples/reference/formdata/README.1.js @@ -0,0 +1,7 @@ + +let body = metro.formdata({ + name: 'Luke', + movies: [ + 'A New Hope', 'The Empire Strikes Back', 'Return of the Jedi' + ] +}) diff --git a/docs/examples/reference/request/README.1.js b/docs/examples/reference/request/README.1.js new file mode 100644 index 0000000..9aeb582 --- /dev/null +++ b/docs/examples/reference/request/README.1.js @@ -0,0 +1,2 @@ + +let req = metro.request('https://example.com/') diff --git a/docs/examples/reference/response/README.1.js b/docs/examples/reference/response/README.1.js new file mode 100644 index 0000000..1d0d883 --- /dev/null +++ b/docs/examples/reference/response/README.1.js @@ -0,0 +1,10 @@ + +const res = metro.response({ + status: 200, + statusText: 'OK', + body: { + data: 'some data' + } +}) + +const data = res.body.data diff --git a/docs/examples/reference/trace/README.1.js b/docs/examples/reference/trace/README.1.js new file mode 100644 index 0000000..bc4c472 --- /dev/null +++ b/docs/examples/reference/trace/README.1.js @@ -0,0 +1,10 @@ + +metro.trace.add('debug', { + request: (req) => { + if (req.searchParams.has('foo')) { + debugger; + + } + + } +}) diff --git a/docs/examples/reference/trace/add.1.js b/docs/examples/reference/trace/add.1.js new file mode 100644 index 0000000..562c6ac --- /dev/null +++ b/docs/examples/reference/trace/add.1.js @@ -0,0 +1,5 @@ + +{ + request: (req) => {}, + response: (res) => {} +} diff --git a/docs/examples/reference/trace/group.1.js b/docs/examples/reference/trace/group.1.js new file mode 100644 index 0000000..61d047a --- /dev/null +++ b/docs/examples/reference/trace/group.1.js @@ -0,0 +1,2 @@ + +metro.trace.add('group',metro.trace.group()) diff --git a/docs/examples/reference/url/README.1.js b/docs/examples/reference/url/README.1.js new file mode 100644 index 0000000..c019f48 --- /dev/null +++ b/docs/examples/reference/url/README.1.js @@ -0,0 +1,4 @@ + +let url = metro.url('https://example.com/?foo=bar', { + port: 8080 +}) diff --git a/docs/examples/tutorials.1.js b/docs/examples/tutorials.1.js new file mode 100644 index 0000000..9d5a683 --- /dev/null +++ b/docs/examples/tutorials.1.js @@ -0,0 +1,2 @@ + +import * as metro from '@muze-nl/metro' diff --git a/docs/examples/tutorials.10.js b/docs/examples/tutorials.10.js new file mode 100644 index 0000000..07b02c2 --- /dev/null +++ b/docs/examples/tutorials.10.js @@ -0,0 +1,15 @@ + +import jsonmw from '@muze-nl/metro/src/mw/json.mjs' + +const client = metro.client('https://example.com').with( jsonmw() ) + +async function postData(data) { + const response = await client.post('/resource', { + body: data + }) + if (response.ok) { + return response.body + } else { + throw new NetworkError(response.status+': '+response.statusText) + } +} diff --git a/docs/examples/tutorials.11.js b/docs/examples/tutorials.11.js new file mode 100644 index 0000000..c47cdd4 --- /dev/null +++ b/docs/examples/tutorials.11.js @@ -0,0 +1,15 @@ + +const client = metro.client('https://example.com') + +async function corsPostData(data) { + const response = await client.post('/resource', { + body: metro.formdata(data), + mode: 'cors', + credentials: 'same-origin' + }) + if (response.ok) { + return response.text() + } else { + throw new NetworkError(response.status+': '+response.statusText) + } +} diff --git a/docs/examples/tutorials.12.js b/docs/examples/tutorials.12.js new file mode 100644 index 0000000..3e8aca3 --- /dev/null +++ b/docs/examples/tutorials.12.js @@ -0,0 +1,11 @@ + +const client = metro.client('https://example.com') + +async function getPrivateData(data) { + const response = await client.get('/private') + if (response.ok) { + return response.text() + } else { + throw new NetworkError(response.status+': '+response.statusText) + } +} diff --git a/docs/examples/tutorials.13.js b/docs/examples/tutorials.13.js new file mode 100644 index 0000000..6921523 --- /dev/null +++ b/docs/examples/tutorials.13.js @@ -0,0 +1,8 @@ + +const user = 'Foo' +const pass = 'Bar' +const client = metro.client('https://example.com', { + headers: { + Authorization: 'Basic '+btoa(user+':'+pass) + } +} diff --git a/docs/examples/tutorials.14.js b/docs/examples/tutorials.14.js new file mode 100644 index 0000000..ac82260 --- /dev/null +++ b/docs/examples/tutorials.14.js @@ -0,0 +1,7 @@ + +const token = 'Foo' +const client = metro.client('https://example.com', { + headers: { + Authorization: 'Bearer '+token + } +} diff --git a/docs/examples/tutorials.2.js b/docs/examples/tutorials.2.js new file mode 100644 index 0000000..8378b71 --- /dev/null +++ b/docs/examples/tutorials.2.js @@ -0,0 +1,2 @@ + +import * as metro from 'https://cdn.jsdelivr.net/npm/@muze-nl/metro/src/metro.mjs' diff --git a/docs/examples/tutorials.3.html b/docs/examples/tutorials.3.html new file mode 100644 index 0000000..61dd87e --- /dev/null +++ b/docs/examples/tutorials.3.html @@ -0,0 +1,2 @@ + + diff --git a/docs/examples/tutorials.4.js b/docs/examples/tutorials.4.js new file mode 100644 index 0000000..16a1688 --- /dev/null +++ b/docs/examples/tutorials.4.js @@ -0,0 +1,11 @@ + +const client = metro.client('https://example.com') +async function getData() { + const response = await client.get('/resource') + + if (response.ok) { + return response.text() + } else { + throw new NetworkError(response.status+': '+response.statusText) + } +} diff --git a/docs/examples/tutorials.5.js b/docs/examples/tutorials.5.js new file mode 100644 index 0000000..0a84397 --- /dev/null +++ b/docs/examples/tutorials.5.js @@ -0,0 +1,11 @@ + +const client = metro.client('https://example.com') + +async function postData(data) { + const response = await client.post('/resource', metro.formdata(data)) + if (response.ok) { + return response.text() + } else { + throw new NetworkError(response.status+': '+response.statusText) + } +} diff --git a/docs/examples/tutorials.6.js b/docs/examples/tutorials.6.js new file mode 100644 index 0000000..0004ee7 --- /dev/null +++ b/docs/examples/tutorials.6.js @@ -0,0 +1,13 @@ + +const client = metro.client('https://example.com') + +async function postData(data) { + const response = await client.post('/resource', { + body: metro.formdata(data) + }) + if (response.ok) { + return response.text() + } else { + throw new NetworkError(response.status+': '+response.statusText) + } +} diff --git a/docs/examples/tutorials.7.js b/docs/examples/tutorials.7.js new file mode 100644 index 0000000..11ad872 --- /dev/null +++ b/docs/examples/tutorials.7.js @@ -0,0 +1,6 @@ + +const client = metro.client('https://example.com', { + headers: { + Accept: 'application/json' + } +}) diff --git a/docs/examples/tutorials.8.js b/docs/examples/tutorials.8.js new file mode 100644 index 0000000..bf3230a --- /dev/null +++ b/docs/examples/tutorials.8.js @@ -0,0 +1,13 @@ + +async function postData(data) { + const response = await client.post('/resource', metro.formdata(data), { + headers: { + Accept: 'application/json' + } + }) + if (response.ok) { + return response.json() + } else { + throw new NetworkError(response.status+': '+response.statusText) + } +} diff --git a/docs/examples/tutorials.9.js b/docs/examples/tutorials.9.js new file mode 100644 index 0000000..27f1262 --- /dev/null +++ b/docs/examples/tutorials.9.js @@ -0,0 +1,17 @@ + +const client = metro.client('https://example.com') + +async function postData(data) { + const response = await client.post('/resource', { + body: data, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + } + }) + if (response.ok) { + return response.json() + } else { + throw new NetworkError(response.status+': '+response.statusText) + } +}