Published
Edited
Apr 29, 2021
Importers
1 star
Insert cell
Insert cell
_ = require("lodash@4") // most recent per 2021-02-11
Insert cell
// of the embedded major version
mostRecentMinorPatch = _.VERSION // in MAJOR.MINOR.PATCH format
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
methodInfos = parse(annotatedSourceCode, commentBlocks)
Insert cell
Insert cell
// see: https://www.npmjs.com/package/comment-parser
commentBlocks = {
// requires specific version running into 502 (Bad Gateway Error) otherwise
const parser = await require('https://bundle.run/comment-parser@1.1.4');
const options = {
startLine: 1, // one-based line numbers
spacing: Spacing.PRESERVE // preserving line breaks (within @example and description)
};
return parser.parse(annotatedSourceCode, options);
}
Insert cell
Spacing = ({
COMPACT: "compact", // default
PRESERVE: "preserve"
})
Insert cell
parse = async (sourceCode, commentBlocks) => {
const repo = getSource(sourceCode);
const annotationBlocks = _.filter(commentBlocks, isMethodAnnotationBlock);
return _.map(annotationBlocks, commentBlock => createMethodInfo(commentBlock, repo));
}
Insert cell
getSource = (sourceCode) => {
const lines = _.split(sourceCode, "\n");
const getOneBasedLineNumber = (zeroBasedLineIndex) => _.add(zeroBasedLineIndex, 1);
const createNumberedSource = (line, index) => {
return {
number: getOneBasedLineNumber(index),
source: line
};
};
const source = _.map(lines, createNumberedSource);
const getZeroBasedLineIndex = (oneBasedLineNumber) => _.subtract(oneBasedLineNumber, 1);
const getSource = (oneBasedLineNumber) => {
const index = getZeroBasedLineIndex(oneBasedLineNumber);
return source[index];
}
return {getSource, source};
}
Insert cell
createMethodInfo = (commentBlock, repo) => {
const lineNumber = getFirstLineNumberAfter(commentBlock);
const source = repo.getSource(lineNumber);
return {
name: getMethodName(source.source),
comments: commentBlock,
source: [source]
};
}
Insert cell
getFirstLineNumberAfter = (commentBlock) => {
const lastLineNumber = _.last(commentBlock.source).number;
return _.add(lastLineNumber, 1);
}
Insert cell
isMethodAnnotationBlock = (commentBlock) => {
const memberOf = findTag(commentBlock, JsDocTag.MEMBEROF);
if (_.isUndefined(memberOf)) {
return _.stubFalse();
}

return _.eq(memberOf.name, "_") &&
includesTag(commentBlock, JsDocTag.SINCE);
}
Insert cell
JsDocTag = ({
MEMBEROF: "memberof",
PARAM: "param",
RETURNS: "returns",
SINCE: "since"
})
Insert cell
findTag = (commentBlock, tagName) => {
const predicate = (tag) => _.eq(_.toLower(tag.tag), tagName);
return _.find(commentBlock.tags, predicate);
}
Insert cell
includesTag = (commentBlock, tagName) => {
const tag = findTag(commentBlock, tagName);
return _.isObject(tag);
}
Insert cell
getMethodName = (line) => {
const regex = /(?:function (\w+)\()|(?:var (\w+) = )/;
const result = regex.exec(line);
return result[1] ?? result[2];
}
Insert cell
getSourceUrl = (methodInfo) => {
const lineNumber = _.first(methodInfo.source).number;
return `https://github.com/lodash/lodash/blob/${_.VERSION}/lodash.js#L${lineNumber}`
}
Insert cell
standalonePackageExists = {
const semver = await require("https://bundle.run/semver@7.3.5") // semver parser used by npm
const standalonePackageExists = async (methodName) => {
if (semver.gte(_.VERSION, "5.0.0")) {
return _.stubFalse(); // per method packages will be removed in v5
}

const packageName = getStandalonePackageName(methodName);
const url = `https://api.npmjs.org/downloads/point/last-day/${packageName}`;
const response = await fetch(url).then(response => response.json());
return _.isUndefined(response.error);
};
return standalonePackageExists;
}
Insert cell
getStandalonePackageName = (methodName) => `lodash.${_.toLower(methodName)}`
Insert cell
// see: https://lodash.com/per-method-packages
getStandalonePackageUrl = (methodName) => {
const packageName = getStandalonePackageName(methodName);
return `https://www.npmjs.com/package/${packageName}`;
}
Insert cell
Insert cell
composeParagraphed = async (methodName, ...tags) => compose(methodName, {spacing: Spacing.PRESERVE, tags})
Insert cell
compose = createComposer(methodInfos)
Insert cell
createComposer = (methodInfos) => {
const keyValuePairs = _.map(methodInfos, d => [d.name, d]);
const mapping = new Map(keyValuePairs);
const methodNames = [...mapping.keys()];
const defaults = {
methodNames,
spacing: Spacing.COMPACT,
tags: []
};
const compose = async (methodName, options = {}) => {
const methodInfo = mapping.get(methodName);
let mergedOptions = _.cloneDeep(defaults);
if (_.isString(options)) {
mergedOptions.tags.push(options);
} else {
_.assign(mergedOptions, options);
}
return format(methodInfo, mergedOptions);
};
return compose;
}
Insert cell
// for example embedding (as highlighted source code), see: https://github.com/observablehq/htl#node-values
format = async (methodInfo, options) => html`
<div class="markdown-body">
${await formatSynopsis(methodInfo)}
${formatDescription(methodInfo, options)}
${formatExample(methodInfo)}
</div>`
Insert cell
formatSynopsis = async (methodInfo) => {
let literal = "<p class='synopsis'>";
literal += `<a href="${getDocumentationUrl(methodInfo.name)}">#</a>`;
literal += ` ${formatSignature(methodInfo)}`;
literal += ` · <a href="${getSourceUrl(methodInfo)}">Source</a>`;
if (await standalonePackageExists(methodInfo.name)) {
literal += `, <a href="${getStandalonePackageUrl(methodInfo.name)}">Package</a>`;
}
literal += "</p>";
return literal;
}
Insert cell
getDocumentationUrl = (methodName) => `#_${methodName}`
Insert cell
// for similar signature format, see: https://docs.python.org/3.3/library/functions.html
formatSignature = (methodInfo) => {
const params = getParams(methodInfo.comments);
return `_.<b>${methodInfo.name}</b>(${formatParams(params)})`;
}
Insert cell
getParams = (commentBlock) => {
const predicate = (tag) => _.eq(_.toLower(tag.tag), JsDocTag.PARAM);
return _.filter(commentBlock.tags, predicate);
}
Insert cell
formatParams = (params) => {
let literal = _.stubString();
let previousParam;
for (const currentParam of params) {
if (isObjectProperty(currentParam.name)) {
continue;
}
if (currentParam.optional) {
literal += "[";
}
if (!_.isUndefined(previousParam)) {
literal += ", ";
}
literal += `<i>${currentParam.name}`;
if (!_.isUndefined(currentParam.default)) {
if (!_.eq(currentParam.default, "[]")) {
literal += ` = ${formatDefault(currentParam.default)}`;
}
}

literal += "</i>"
previousParam = currentParam;
}
const openSquareBracketCount = _.sumBy(literal, char => _.eq(char, "["));
if (_.gt(openSquareBracketCount, 0)) {
literal += _.repeat("]", openSquareBracketCount);
}
return literal;
}
Insert cell
isObjectProperty = (paramName) => _.includes(paramName, ".")
Insert cell
formatDefault = (value) => _
.chain(value)
.replace("-", " - ")
.replace("[", "</i>[<i>")
.replace("]", "</i>]<i>")
.value()
Insert cell
formatDescription = (methodInfo, options) => {
let text = _.trim(methodInfo.comments.description);
text = removeNotePrefixes(text);
if (_.includes(options.tags, JsDocTag.RETURNS)) {
const tag = findTag(methodInfo.comments, JsDocTag.RETURNS);
text += `\n\nReturns ${tag.description}`;
}
if (_.eq(options.spacing, Spacing.COMPACT)) {
text = _.replace(text, /\n\n/g, " ");
}
text = formatQuotedCharacters(text);
text = removePeriodBeforeParenthesis(text);
text = endWithPeriod(text);
return formatText(text, methodInfo, options);
}
Insert cell
removeNotePrefixes = (text) => _.replace(text, /\*\*Note:\*\* /g, _.stubString())
Insert cell
formatQuotedCharacters = (text) => {
const regex = /"([^"])"|'([^'])'/g;
const replacer = (match, p1, p2) => `<code>${_.escapeRegExp(p1 ?? p2)}</code>`;
return _.replace(text, regex, replacer);
}
Insert cell
removePeriodBeforeParenthesis = (text) => _.replace(text, ". (e.g.", " (e.g.")
Insert cell
endWithPeriod = (text) => _.endsWith(text, ".") ? text : `${text}.`
Insert cell
formatText = (text, methodInfo, options) => {
const paramNames = getParams(methodInfo.comments)
.map(d => d.name);
const renderer = createRenderer(paramNames, options.methodNames);
marked.use({renderer}); // see: https://marked.js.org/using_pro
return marked.parse(text);
}
Insert cell
createRenderer = (paramNames, methodNames) => {
return {
codespan: (code) => {
if (_.includes(paramNames, code)) {
return `<i>${code}</i>`;
}
const result = /(?:_.)([\w.]+)/.exec(code);
if (!_.isNull(result)) {
const name = result[1];
if (_.includes(methodNames, name)) {
return `<a href="${getDocumentationUrl(name)}">${name}</a>`; // add prefix method
}
}
return false; // use original codespan renderer
},
link: (href, title, text) => {
const pureText = _.replace(text, /<code>|<\/code>/g, _.stubString());
return `<a href="${href}">${pureText}</a>`; // ignoring title
},
text: formatInvocationParams
}
}
Insert cell
formatInvocationParams = (text) => {
const regex = /:[ |\n]\((…?[A-z]+)(?:, (…?[|A-z]+))?(?:, (…?[A-z]+))?\)./;
const replacer = (match, ...groups) => {
const params = _
.chain(groups)
.slice(0, groups.length - 2) // 'offset' and 'string' arguments
.filter(group => !_.isUndefined(group))
.map(group => `<em>${group}</em>`)
.join(", ")
.value();
return `: (${params}).`;
};
return _.replace(text, regex, replacer);
}
Insert cell
marked = {
const marked = await require("marked@2");
marked.setOptions({smartypants: true}); // using "smart" typographic punctuation for things like quotes and dashes
return marked;
}
Insert cell
formatExample = (methodInfo) => {
const example = findTag(methodInfo.comments, "example");
if (_.isUndefined(example)) {
return _.stubString();
}
return formatCode(example.description);
}
Insert cell
// highlighting done by https://github.com/markedjs/marked, see: https://github.com/observablehq/stdlib#markdown
formatCode = (block) => {
return md`
~~~js
${block.trim()}
~~~`;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more