diff --git a/docs/README.md b/docs/README.md index 625823e2be1..d07cc7f3981 100644 --- a/docs/README.md +++ b/docs/README.md @@ -56,10 +56,12 @@ Most changes are reflected live without having to restart the server. $ yarn build ``` -This command generates static content into the `build` directory and can be served using any static contents hosting service. +This command generates static content into the `build` directory and can be served using any static contents hosting service. When run on Netlify, it will also build the typescript projects needed for extracting type information via typedoc. -## #include_code macro +## Macros + +### `#include_code` You can embed code snippets into a `.md`/`.mdx` file from code which lives elsewhere in the repo. - In your markdown file: @@ -119,7 +121,26 @@ You can embed code snippets into a `.md`/`.mdx` file from code which lives elsew - `#include_code hello path/from/repo/root/to/file.ts typescript noTitle,noLineNumbers,noSourceLink` - Ironically, we can't show you a rendering of these examples, because this README.md file doesn't support the `#include_code` macro! +> See [here](./src/components/GithubCode/index.js) for another way to include code, although this approach is flakier, so the above `#include_code` macro is preferred. + +### `#include_aztec_version` + +This macros will be replaced inline with the current aztec packages tag, which is `aztec-packages-v0.7.10` at the time of these writing. This value is sourced from `.release-please-manifest.json` on the project root. -### Another way to include code. +Alternatively, you can also use the `AztecPackagesVersion()` js function, which you need to import explicitly: + +``` +import { AztecPackagesVersion } from "@site/src/components/Version"; +<>{AztecPackagesVersion()} +``` -See [here](./src/components/GithubCode/index.js), although this approach is flakier, so the above `#include_code` macro is preferred. \ No newline at end of file + ### `#include_noir_version` + +This macros will be replaced inline with the required nargo version, which is `0.11.1-aztec.0` at the time of these writing. This value is sourced from `yarn-project/noir-compiler/src/noir-version.json`. + +Alternatively, you can also use the `NoirVersion()` js function, which you need to import explicitly: + +``` +import { NoirVersion } from "@site/src/components/Version"; +<>{NoirVersion()} +``` diff --git a/docs/docs/dev_docs/contracts/syntax/main.md b/docs/docs/dev_docs/contracts/syntax/main.md index c89e97a1a2d..124e271e7ee 100644 --- a/docs/docs/dev_docs/contracts/syntax/main.md +++ b/docs/docs/dev_docs/contracts/syntax/main.md @@ -18,22 +18,21 @@ Aztec.nr contains abstractions which remove the need to understand the low-level To import Aztec.nr into your Aztec contract project, simply include it as a dependency. For example: -import { AztecPackagesVersion } from "@site/src/components/Version"; - -{`[package] +```toml +[package] name = "token_contract" authors = [""] compiler_version = "0.1" type = "contract" - + [dependencies] # Framework import -aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="${AztecPackagesVersion()}", directory="yarn-project/aztec-nr/aztec" } - +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/aztec" } + # Utility dependencies -value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="${AztecPackagesVersion()}", directory="yarn-project/aztec-nr/value-note"} -safe_math = { git="https://github.com/AztecProtocol/aztec-packages/", tag="${AztecPackagesVersion()}", directory="yarn-project/aztec-nr/safe-math"} -`} +value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/value-note"} +safe_math = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/safe-math"} +``` :::info Note: currently the dependency name ***MUST*** be `aztec`. The framework expects this namespace to be available when compiling into contracts. This limitation may be removed in the future. diff --git a/docs/docs/dev_docs/dapps/tutorials/contract_deployment.md b/docs/docs/dev_docs/dapps/tutorials/contract_deployment.md index d677b403f77..36972b19fc7 100644 --- a/docs/docs/dev_docs/dapps/tutorials/contract_deployment.md +++ b/docs/docs/dev_docs/dapps/tutorials/contract_deployment.md @@ -17,13 +17,12 @@ nargo new --contract token Then, open the `contracts/token/Nargo.toml` configuration file, and add the `aztec.nr` and `value_note` libraries as dependencies: -import { AztecPackagesVersion } from "@site/src/components/Version"; - -{`[dependencies] -aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="${AztecPackagesVersion()}", directory="yarn-project/aztec-nr/aztec" } -value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="${AztecPackagesVersion()}", directory="yarn-project/aztec-nr/value-note"} -safe_math = { git="https://github.com/AztecProtocol/aztec-packages/", tag="${AztecPackagesVersion()}", directory="yarn-project/aztec-nr/safe-math"} -`} +```toml +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/aztec" } +value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/value-note"} +safe_math = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/safe-math"} +``` Last, copy-paste the code from the `Token` contract into `contracts/token/main.nr`: diff --git a/docs/docs/dev_docs/getting_started/noir_contracts.md b/docs/docs/dev_docs/getting_started/noir_contracts.md index 65adc75609f..d05a4cafbb4 100644 --- a/docs/docs/dev_docs/getting_started/noir_contracts.md +++ b/docs/docs/dev_docs/getting_started/noir_contracts.md @@ -59,17 +59,16 @@ Before writing the contracts, we must add the aztec.nr library. This adds smart 3. Add aztec.nr library as a dependency to your noir project. Open Nargo.toml that is in the `contracts/example_contract` folder, and add the dependency section as follows: -import { AztecPackagesVersion } from "@site/src/components/Version"; - -{`[package] +```toml +[package] name = "example_contract" authors = [""] compiler_version = "0.1" type = "contract" [dependencies] -aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="master", directory="yarn-project/aztec-nr/aztec" } -`} +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/aztec" } +``` :::note You may need to update your dependencies depending on the contract that you are writing. For example, the token contract [imports more](../getting_started/token_contract_tutorial#project-setup). diff --git a/docs/docs/dev_docs/getting_started/token_contract_tutorial.md b/docs/docs/dev_docs/getting_started/token_contract_tutorial.md index d53e63c161d..64a61a5faae 100644 --- a/docs/docs/dev_docs/getting_started/token_contract_tutorial.md +++ b/docs/docs/dev_docs/getting_started/token_contract_tutorial.md @@ -80,19 +80,18 @@ Your project should look like this: Add the following dependencies to your Nargo.toml file, below the package information: -import { AztecPackagesVersion } from "@site/src/components/Version"; - -{`[package] +```toml +[package] name = "token_contract" authors = [""] compiler_version = "0.1" type = "contract" - + [dependencies] -aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="${AztecPackagesVersion()}", directory="yarn-project/aztec-nr/aztec" } -value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="${AztecPackagesVersion()}", directory="yarn-project/aztec-nr/value-note"} -safe_math = { git="https://github.com/AztecProtocol/aztec-packages/", tag="${AztecPackagesVersion()}", directory="yarn-project/aztec-nr/safe-math"} -`} +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/aztec" } +value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/value-note"} +safe_math = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/safe-math"} +``` ## Contract Interface diff --git a/docs/src/preprocess/include_code.js b/docs/src/preprocess/include_code.js new file mode 100644 index 00000000000..1e87b9aa351 --- /dev/null +++ b/docs/src/preprocess/include_code.js @@ -0,0 +1,324 @@ +const fs = require("fs"); +const path = require("path"); +const childProcess = require("child_process"); + +const getLineNumberFromIndex = (fileContent, index) => { + return fileContent.substring(0, index).split("\n").length; +}; + +/** + * Search for lines of the form + */ +function processHighlighting(codeSnippet, identifier) { + const lines = codeSnippet.split("\n"); + /** + * For an identifier = bar: + * + * Matches of the form: `highlight-next-line:foo:bar:baz` will be replaced with "highlight-next-line". + * Matches of the form: `highlight-next-line:foo:baz` will be replaced with "". + */ + const regex1 = /highlight-next-line:([a-zA-Z0-9-._:]+)/; + const replacement1 = "highlight-next-line"; + const regex2 = /highlight-start:([a-zA-Z0-9-._:]+)/; + const replacement2 = "highlight-start"; + const regex3 = /highlight-end:([a-zA-Z0-9-._:]+)/; + const replacement3 = "highlight-end"; + const regex4 = /this-will-error:([a-zA-Z0-9-._:]+)/; + const replacement4 = "this-will-error"; + + let result = ""; + let mutated = false; + + const processLine = (line, regex, replacement) => { + const match = line.match(regex); + if (match) { + mutated = true; + + const identifiers = match[1].split(":"); + if (identifiers.includes(identifier)) { + line = line.replace(match[0], replacement); + } else { + // Remove matched text completely + line = line.replace(match[0], ""); + } + } else { + // No match: it's an ordinary line of code. + } + return line.trim() == "//" || line.trim() == "#" ? "" : line; + }; + + for (let line of lines) { + mutated = false; + line = processLine(line, regex1, replacement1); + line = processLine(line, regex2, replacement2); + line = processLine(line, regex3, replacement3); + line = processLine(line, regex4, replacement4); + result += line === "" && mutated ? "" : line + "\n"; + } + + return result.trim(); +} + +let lastReleasedVersion; + +/** Returns the last released tag */ +function getLatestTag() { + if (!lastReleasedVersion) { + const manifest = path.resolve( + __dirname, + "../../../.release-please-manifest.json" + ); + lastReleasedVersion = JSON.parse(fs.readFileSync(manifest).toString())["."]; + } + return lastReleasedVersion + ? `aztec-packages-v${lastReleasedVersion}` + : undefined; +} + +/** Returns whether to use the latest release or the current version of stuff. */ +function useLastRelease() { + return process.env.NETLIFY || process.env.INCLUDE_RELEASED_CODE; +} + +/** + * Returns the contents of a file. If the build is running for publishing, it will load the contents + * of the file in the last released version. + */ +function readFile(filePath, tag) { + if (tag && tag !== "master") { + try { + const tag = getLatestTag(); + const root = path.resolve(__dirname, "../../../"); + const relPath = path.relative(root, filePath); + return childProcess.execSync(`git show ${tag}:${relPath}`).toString(); + } catch (err) { + console.error( + `Error reading file ${relPath} from latest version. Falling back to current content.` + ); + } + } + return fs.readFileSync(filePath, "utf-8"); +} + +/** Extracts a code snippet, trying with the last release if applicable, and falling back to current content. */ +function extractCodeSnippet(filePath, identifier) { + if (useLastRelease()) { + try { + return doExtractCodeSnippet(filePath, identifier, false); + } catch (err) { + console.error( + `Error extracting code snippet ${identifier} for ${filePath}: ${err}. Falling back to current content.` + ); + } + } + + return doExtractCodeSnippet(filePath, identifier, true); +} + +/** + * Parse a code file, looking for identifiers of the form: + * `docs:start:${identifier}` and `docs:end:{identifier}`. + * Extract that section of code. + * + * It's complicated if code snippet identifiers overlap (i.e. the 'start' of one code snippet is in the + * middle of another code snippet). The extra logic in this function searches for all identifiers, and + * removes any which fall within the bounds of the code snippet for this particular `identifier` param. + * @returns the code snippet, and start and end line numbers which can later be used for creating a link to github source code. + */ +function doExtractCodeSnippet(filePath, identifier, useCurrent) { + const tag = useCurrent ? "master" : getLatestTag(); + let fileContent = readFile(filePath, tag); + let lineRemovalCount = 0; + let linesToRemove = []; + + const startRegex = /(?:\/\/|#)\s+docs:start:([a-zA-Z0-9-._:]+)/g; // `g` will iterate through the regex.exec loop + const endRegex = /(?:\/\/|#)\s+docs:end:([a-zA-Z0-9-._:]+)/g; + + /** + * Search for one of the regex statements in the code file. If it's found, return the line as a string and the line number. + */ + const lookForMatch = (regex) => { + let match; + let matchFound = false; + let matchedLineNum = null; + let actualMatch = null; + let lines = fileContent.split("\n"); + while ((match = regex.exec(fileContent))) { + if (match !== null) { + const identifiers = match[1].split(":"); + let tempMatch = identifiers.includes(identifier) ? match : null; + + if (tempMatch === null) { + // If it's not a match, we'll make a note that we should remove the matched text, because it's from some other identifier and should not appear in the snippet for this identifier. + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + if (line.trim() == match[0].trim()) { + linesToRemove.push(i + 1); // lines are indexed from 1 + ++lineRemovalCount; + } + } + } else { + if (matchFound === true) { + throw new Error( + `Duplicate for regex ${regex} and identifier ${identifier}` + ); + } + matchFound = true; + matchedLineNum = getLineNumberFromIndex(fileContent, tempMatch.index); + actualMatch = tempMatch; + } + } + } + + return [actualMatch, matchedLineNum]; + }; + + let [startMatch, startLineNum] = lookForMatch(startRegex); + let [endMatch, endLineNum] = lookForMatch(endRegex); + + // Double-check that the extracted line actually contains the required start and end identifier. + if (startMatch !== null) { + const startIdentifiers = startMatch[1].split(":"); + startMatch = startIdentifiers.includes(identifier) ? startMatch : null; + } + if (endMatch !== null) { + const endIdentifiers = endMatch[1].split(":"); + endMatch = endIdentifiers.includes(identifier) ? endMatch : null; + } + + if (startMatch === null || endMatch === null) { + if (startMatch === null && endMatch === null) { + throw new Error( + `Identifier "${identifier}" not found in file "${filePath}"` + ); + } else if (startMatch === null) { + throw new Error( + `Start line "docs:start:${identifier}" not found in file "${filePath}"` + ); + } else { + throw new Error( + `End line "docs:end:${identifier}" not found in file "${filePath}"` + ); + } + } + + let lines = fileContent.split("\n"); + + // We only want to remove lines which actually fall within the bounds of our code snippet, so narrow down the list of lines that we actually want to remove. + linesToRemove = linesToRemove.filter((lineNum) => { + const removal_in_bounds = lineNum >= startLineNum && lineNum <= endLineNum; + return removal_in_bounds; + }); + + // Remove lines which contain `docs:` comments for unrelated identifiers: + lines = lines.filter((l, i) => { + return !linesToRemove.includes(i + 1); // lines are indexed from 1 + }); + + // Remove lines from the snippet which fall outside the `docs:start` and `docs:end` values. + lines = lines.filter((l, i) => { + return i + 1 > startLineNum && i + 1 < endLineNum - linesToRemove.length; // lines are indexed from 1 + }); + + // We have our code snippet! + let codeSnippet = lines.join("\n"); + + // The code snippet might contain some docusaurus highlighting comments for other identifiers. We should remove those. + codeSnippet = processHighlighting(codeSnippet, identifier); + + return [codeSnippet, startLineNum, endLineNum, tag]; +} + +/** + * Explaining this regex: + * + * E.g. `#include_code snippet_identifier /circuits/my_code.cpp cpp` + * + * #include_code\s+(\S+)\s+(\S+)\s+(\S+) + * - This is the main regex to match the above format. + * - \s+: one or more whitespace characters (space or tab) after `include_code` command. + * - (\S+): one or more non-whitespaced characters. Captures this as the first argument, which is a human-readable identifier for the code block. + * - etc. + * + * Lookaheads are needed to allow us to ignore commented-out lines: + * + * ^(?!