diff --git a/codeblocks/.gitignore b/codeblocks/.gitignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/codeblocks/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/codeblocks/index.js b/codeblocks/index.js new file mode 100644 index 000000000..8040e2761 --- /dev/null +++ b/codeblocks/index.js @@ -0,0 +1,83 @@ +const visit = require('unist-util-visit'); +const { join, dirname } = require('path'); +const CACHEDIR = join(__dirname, '../.cache'); +const { mkdir } = require('fs'); +const fetch = require('node-fetch') + +const plugin = (options) => { + const transformer = async (ast, vfile) => { + //DEBUG: + //if (vfile.path.includes('test.md')) console.log(ast) + + const cblocks = [] + const assets = [] + + // Collect all (relevant) codeblocks + visit(ast, 'code', async (node) => { + // In this extension we repurpose the language hint for a source URL + // Code blocks that need their URL cached & replaced with the correct imports: + if (node.lang?.startsWith('http://') || node.lang?.startsWith('https://')) { + cblocks.push(node) + } + }); + + //DEBUG: + //if (cblocks.length) console.log(cblocks) + + // We run the changes outside of the visitor because the visitor does not support callbacks + for (const node of cblocks) { + const url = node.lang; + const lang = await detectLang(url); + const file = await cachedFile(url); + node.lang = lang + assets.push(file.path) + const assetId = assets.length-1 + + // TODO: decide whether to use asset imports & external files or embedded content + node.value = `/* ${url} {__externalCodeSnippet${assetId}} */\n` + file.contents; + } + + //DEBUG: + //if (cblocks.length) console.log(cblocks, assets) + }; + return transformer; +}; + +module.exports = plugin; + + +async function detectLang(url) { + //TODO: + return 'rust' +} + +async function cachedFile(url) { + // Convert the URL into a cache path + const path = cachePath(url); + if (!path) { + return `/* ERROR: could not cache file: ${url} */` + } + // TODO: maybe we want to have a whitelist for allowed paths? + // E.g. limit to https://raw.githubusercontent.com/suborbital/** + + // + + // TODO: handle request errors + const contents = await fetch(url).then(r => r.text()); + + // + + // Return both path and contents + return { path, contents } +} + +function cachePath(url) { + const p = (/https?:\/\/(.*)/.exec(url) ?? [])[1]; + if (!p) { + console.error('Cache path invalid!'); + return void 0; + } + + return join(CACHEDIR, p); +} \ No newline at end of file diff --git a/codeblocks/package-lock.json b/codeblocks/package-lock.json new file mode 100644 index 000000000..4dcb0246a --- /dev/null +++ b/codeblocks/package-lock.json @@ -0,0 +1,154 @@ +{ + "name": "codeblocks", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "codeblocks", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "node-fetch": "^2.6.7", + "unist-util-visit": "^2.0.1" + } + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + }, + "dependencies": { + "@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==" + }, + "unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + } + }, + "unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + } + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/codeblocks/package.json b/codeblocks/package.json new file mode 100644 index 000000000..aada694fe --- /dev/null +++ b/codeblocks/package.json @@ -0,0 +1,15 @@ +{ + "name": "codeblocks", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "node-fetch": "^2.6.7", + "unist-util-visit": "^2.0.1" + } +} diff --git a/website/code/rwasm-testdata/fetch/.runnable.yaml b/website/code/rwasm-testdata/fetch/.runnable.yaml new file mode 100755 index 000000000..a2e5fd95b --- /dev/null +++ b/website/code/rwasm-testdata/fetch/.runnable.yaml @@ -0,0 +1,4 @@ +name: fetch +namespace: default +lang: rust +apiVersion: 0.2.5 diff --git a/website/code/rwasm-testdata/fetch/Cargo.lock b/website/code/rwasm-testdata/fetch/Cargo.lock new file mode 100755 index 000000000..ac6b1333e --- /dev/null +++ b/website/code/rwasm-testdata/fetch/Cargo.lock @@ -0,0 +1,60 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "fetch" +version = "0.1.0" +dependencies = [ + "suborbital", +] + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "suborbital" +version = "0.14.0" +dependencies = [ + "suborbital-macro", +] + +[[package]] +name = "suborbital-macro" +version = "0.14.0" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" diff --git a/website/code/rwasm-testdata/fetch/Cargo.toml b/website/code/rwasm-testdata/fetch/Cargo.toml new file mode 100755 index 000000000..f9bc20f30 --- /dev/null +++ b/website/code/rwasm-testdata/fetch/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "fetch" +version = "0.1.0" +authors = ["Suborbital Runnable "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +suborbital = { path = "../../../api/rust/core" } \ No newline at end of file diff --git a/website/code/rwasm-testdata/fetch/fetch.wasm b/website/code/rwasm-testdata/fetch/fetch.wasm new file mode 100755 index 000000000..d1533fe2b Binary files /dev/null and b/website/code/rwasm-testdata/fetch/fetch.wasm differ diff --git a/website/code/rwasm-testdata/fetch/src/lib.rs b/website/code/rwasm-testdata/fetch/src/lib.rs new file mode 100755 index 000000000..45393870a --- /dev/null +++ b/website/code/rwasm-testdata/fetch/src/lib.rs @@ -0,0 +1,42 @@ +use suborbital::runnable::*; +use suborbital::http; +use suborbital::util; +use suborbital::log; +use std::collections::BTreeMap; + +struct Fetch{} + +impl Runnable for Fetch { + fn run(&self, input: Vec) -> Result, RunErr> { + let url = util::to_string(input); + + let _ = match http::get(url.as_str(), None) { + Ok(res) => res, + Err(e) => return Err(RunErr::new(1, e.message.as_str())) + }; + + // test sending a POST request with headers and a body + let mut headers = BTreeMap::new(); + headers.insert("Content-Type", "application/json"); + headers.insert("X-ATMO-TEST", "testvalgoeshere"); + + let body = String::from("{\"message\": \"testing the echo!\"}").as_bytes().to_vec(); + + match http::post("https://postman-echo.com/post", Some(body), Some(headers)) { + Ok(res) => { + log::info(util::to_string(res.clone()).as_str()); + Ok(res) + }, + Err(e) => Err(RunErr::new(1, e.message.as_str())) + } + } +} + + +// initialize the runner, do not edit below // +static RUNNABLE: &Fetch = &Fetch{}; + +#[no_mangle] +pub extern fn _start() { + use_runnable(RUNNABLE); +} diff --git a/website/docs/atmo/runnable-api/http-client.md b/website/docs/atmo/runnable-api/http-client.md index aaf244195..03559efa6 100755 --- a/website/docs/atmo/runnable-api/http-client.md +++ b/website/docs/atmo/runnable-api/http-client.md @@ -185,3 +185,11 @@ public func HttpDelete(url: String) -> String ``` + + +## Example Runnable + +import CodeBlock from '@theme/CodeBlock'; +import exampleCodeRustFetch from '@code/rwasm-testdata/fetch/src/lib.rs'; + +{exampleCodeRustFetch} diff --git a/website/docs/test.md b/website/docs/test.md new file mode 100644 index 000000000..d29ec05be --- /dev/null +++ b/website/docs/test.md @@ -0,0 +1,8 @@ +# Test code snippet loading + +:::tip +The code below is being loaded directly [from GitHub](https://raw.githubusercontent.com/suborbital/reactr/main/rwasm/testdata/fetch/src/lib.rs) during buildtime. +::: + +```https://raw.githubusercontent.com/suborbital/reactr/main/rwasm/testdata/fetch/src/lib.rs +``` diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 1b55aa327..5afeb9acc 100755 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -143,7 +143,10 @@ module.exports = { routeBasePath: '/', sidebarPath: require.resolve('./sidebars.js'), editUrl: 'https://github.com/suborbital/docs/edit/main/website', - showLastUpdateTime: true + showLastUpdateTime: true, + beforeDefaultRemarkPlugins: [ + require('../codeblocks/') + ], }, theme: { customCss: [ @@ -153,5 +156,45 @@ module.exports = { } } ] - ] + ], + plugins: [ + // Allow loading raw code snippets as Webpack Source Assets when using the @code alias + // https://webpack.js.org/guides/asset-modules/#source-assets + // https://docusaurus.io/docs/markdown-features/react#importing-components + // https://docusaurus.io/docs/api/docusaurus-config#plugins + // https://docusaurus.io/docs/api/plugin-methods/lifecycle-apis#configureWebpack + () => ({ + name: 'suborbital-docs-rawfiles', + + // TODO: can also load this content dynamically? if we do we need to cache it to improve the perf of consecutive builds + async loadContent() {}, + async contentLoaded({content, actions}) {}, + + // TODO: add `code` to list of watched folders? + getPathsToWatch() {}, + + configureWebpack(config) { + // Set a custom alias for code snippets in /website/code/ + let siteAlias = config.resolve.alias['@site'] + let codeAlias = siteAlias + '/code' + + console.log('Using code snippets from ', codeAlias) + return { + resolve: { + alias: { + '@code': codeAlias + } + }, + module: { + rules: [ + { + test: (input) => { let t=/\/website\/code\//.test(input); if (t) console.log('Asset load: ',input); return t; }, + type: 'asset/source', + } + ] + } + }; + } + }) + ], } diff --git a/website/package-lock.json b/website/package-lock.json index f4d540788..86119614a 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "suborbital-docs", "version": "0.0.0", + "hasInstallScript": true, "dependencies": { "@docusaurus/core": "2.0.0-beta.15", "@mdx-js/react": "^1.6.21", diff --git a/website/package.json b/website/package.json index 1f1e09424..43e4fd97c 100755 --- a/website/package.json +++ b/website/package.json @@ -3,6 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { + "postinstall": "npm ci --prefix ../codeblocks/", "docusaurus": "docusaurus", "start": "docusaurus start", "build": "docusaurus build",