diff --git a/.circleci/config.yml b/.circleci/config.yml index ea10aed..5b8e50f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ jobs: build: docker: # specify the version you desire here - - image: circleci/node:8.11.1 + - image: circleci/node:12.16.3 # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images diff --git a/scripts/make_doc.sh b/scripts/make_doc.sh index 88a12ad..6d1bf3a 100755 --- a/scripts/make_doc.sh +++ b/scripts/make_doc.sh @@ -1,73 +1,35 @@ #!/usr/bin/env bash set -e +# we'll modify files in src/ and then revert our changes using +# git checkout src/* so make sure they have no local changes +if git status --porcelain | grep 'src/'; then + echo "Can't generate the docs when the git checkout isn't clean" + exit 1 +fi + npm install --no-package-lock tsc -p tsconfig.docgen.json # https://github.com/TypeStrong/typedoc/issues/564 # i would like typedoc to group functions in categories but it's not supported # yet. So I hack it with their support for external modules... - -# we'll modify the files and then revert our changes using -# git reset --HARD so make sure there are no local changes -if [[ $(git status --porcelain) ]]; then - echo "Can't generate the docs when the git checkout isn't clean" - exit 1 -fi - -# pre-process files node dist/scripts/make_doc_extra/make_doc_preprocess.js # trick for the 'Option' & 'Either' constants which typedoc skips as it clashes # with the 'Option' & 'Either' type synomym -sed -i "s/const Option/const optionGlabiboulga/" src/Option.ts -sed -i "s/const Either/const eitherGlabiboulga/" src/Either.ts -sed -i "s/const LinkedList/const linkedListGlabiboulga/" src/LinkedList.ts -sed -i "s/const Stream/const streamGlabiboulga/" src/Stream.ts -sed -i "s/const Function0/const function0Glabiboulga/" src/Function.ts -sed -i "s/const Function1/const function1Glabiboulga/" src/Function.ts -sed -i "s/const Function2/const function2Glabiboulga/" src/Function.ts -sed -i "s/const Function3/const function3Glabiboulga/" src/Function.ts -sed -i "s/const Function4/const function4Glabiboulga/" src/Function.ts -sed -i "s/const Function5/const function5Glabiboulga/" src/Function.ts -sed -i "s/const Predicate/const predicateGlabiboulga/" src/Predicate.ts +node dist/scripts/make_doc_extra/replace_in_ts.js # generate with typedoc ./node_modules/typedoc/bin/typedoc --exclude "**/make_doc_extra/*.ts" --mode file --out apidoc --excludePrivate --excludeExternals --excludeNotExported --ignoreCompilerErrors --tsconfig tsconfig.prepublish.json src/index.ts # revert the 'Option' & 'Either' constant rename -find apidoc -name "*.html" -exec sed -i 's/optionglabiboulga/Option/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/optionGlabiboulga/Option/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/eitherglabiboulga/Either/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/eitherGlabiboulga/Either/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/linkedlistglabiboulga/LinkedList/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/linkedListGlabiboulga/LinkedList/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/streamglabiboulga/Stream/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/streamGlabiboulga/Stream/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function0glabiboulga/Function0/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function0Glabiboulga/Function0/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function1glabiboulga/Function1/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function1Glabiboulga/Function1/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function2glabiboulga/Function2/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function2Glabiboulga/Function2/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function3glabiboulga/Function3/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function3Glabiboulga/Function3/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function4glabiboulga/Function4/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function4Glabiboulga/Function4/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function5glabiboulga/Function5/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/function5Glabiboulga/Function5/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/predicateglabiboulga/Predicate/g' \{\} \; -find apidoc -name "*.html" -exec sed -i 's/predicateGlabiboulga/Predicate/g' \{\} \; - -# modify the output to say 'File' instead of 'Module' -find apidoc -name "*.html" -exec sed -i 's/Module/File/g' \{\} \; +node dist/scripts/make_doc_extra/replace_in_html.js -# modify the paths to say 'files' instead of 'modules' mv apidoc/modules apidoc/files -find apidoc -name "*.html" -exec sed -i 's/modules/files/g' \{\} \; node dist/scripts/make_doc_extra/make_doc_extra.js # we're happy with the output now, revert the changes I made # to the files to make typedoc think they're external modules -git reset --hard HEAD +git checkout src/* diff --git a/scripts/make_doc_extra/replace_in_html.ts b/scripts/make_doc_extra/replace_in_html.ts new file mode 100644 index 0000000..0402e9f --- /dev/null +++ b/scripts/make_doc_extra/replace_in_html.ts @@ -0,0 +1,36 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { FILE_REPLACEMENTS, replaceInFile, SimpleReplacement } from './replacements'; + +async function main() { + const allHtmlReplacements = FILE_REPLACEMENTS + .flatMap(([_, constReplacement]) => constReplacement.htmlReplacements) + .appendAll([ + new SimpleReplacement('Module', 'File'), + new SimpleReplacement('modules', 'files'), + ]); + + const promises: Promise[] = []; + for await (const htmlFile of findHtml('apidoc')) { + promises.push(replaceInFile(htmlFile, allHtmlReplacements)); + } + return Promise.all(promises); +} + +async function* findHtml(dir: string): AsyncIterableIterator { + const children = await fs.promises.readdir(dir, {withFileTypes: true}); + for (const child of children) { + if (child.isDirectory()) { + yield *findHtml(path.join(dir, child.name)) + } else if (child.isFile() && child.name.endsWith('.html')) { + yield path.join(dir, child.name); + } + } +} + +main() + .then() + .catch(err => { + console.error(err); + process.exit(1); + }); diff --git a/scripts/make_doc_extra/replace_in_ts.ts b/scripts/make_doc_extra/replace_in_ts.ts new file mode 100644 index 0000000..c7856be --- /dev/null +++ b/scripts/make_doc_extra/replace_in_ts.ts @@ -0,0 +1,18 @@ +import { replaceInFiles, FILE_REPLACEMENTS, SimpleReplacement } from './replacements'; +import { Vector } from '../../src'; + + +function main() { + return FILE_REPLACEMENTS + .map<[string, Vector]>( + ([file, replacement]) => [file, replacement.tsReplacements] + ) + .transform(replaceInFiles); +} + +main() + .then() + .catch(err => { + console.error(err); + process.exit(1); + }); diff --git a/scripts/make_doc_extra/replacements.ts b/scripts/make_doc_extra/replacements.ts new file mode 100644 index 0000000..8eb1bd9 --- /dev/null +++ b/scripts/make_doc_extra/replacements.ts @@ -0,0 +1,101 @@ +import * as fs from 'fs'; +import * as readline from 'readline'; +import { Vector } from '../../src'; +import * as path from 'path'; + +export class SimpleReplacement { + readonly src: RegExp; + readonly trg: string; + + constructor(_src: string, _trg: string) { + if (!_src.match(/^[a-zA-Z0-9<> ]+$/)) { + throw new Error('Not safe for regex: ' + _src); + } + this.src = new RegExp(_src, 'g'); + this.trg = _trg; + } +} + +const SUFFIX = 'Glabiboulga'; + +export class ConstReplacement { + readonly tsReplacements: Vector; + readonly htmlReplacements: Vector; + + constructor(constNames: string | Vector) { + const constNamesVector = constNames instanceof Vector ? constNames : Vector.of(constNames); + this.tsReplacements = constNamesVector + .map(constName => new SimpleReplacement( + `const ${constName}`, + `const ${constName}${SUFFIX}`, + )); + + this.htmlReplacements = constNamesVector + .flatMap(constName => Vector.of( + new SimpleReplacement( + `${constName}${SUFFIX}`.toLowerCase(), + constName, + ), + new SimpleReplacement( + this.insertWbrBetweenParts(`${constName}${SUFFIX}`), + constName, + ) + )); + } + + /** + * CamelCase42Name -> CamelCase42Name + */ + private insertWbrBetweenParts(text: string): string { + if (!text.match(/^[A-Z]/)) { + throw new Error('Must start with capital letter: ' + text); + } + + const part = /[A-Z][^A-Z]*/g; + function* parts(): IterableIterator { + for ( ; ; ) { + const res = part.exec(text); + if (!res) { + return; + } + yield res[0]; + } + } + return Vector.ofIterable(parts()).mkString(''); + } +} + +export const FILE_REPLACEMENTS = Vector.of<[string, ConstReplacement]>( + [path.join('src', 'Option.ts'), new ConstReplacement('Option')], + [path.join('src', 'Either.ts'), new ConstReplacement('Either')], + [path.join('src', 'LinkedList.ts'), new ConstReplacement('LinkedList')], + [path.join('src', 'Stream.ts'), new ConstReplacement('Stream')], + [path.join('src', 'Function.ts'), new ConstReplacement(Vector.of(0, 1, 2, 3, 4, 5).map(i => `Function${i}`))], + [path.join('src', 'Predicate.ts'), new ConstReplacement('Predicate')], +) + +export function replaceInFiles(fileReplacements: Vector<[string, Vector]>): Promise { + const promises = fileReplacements + .map(([file, replacements]) => replaceInFile(file, replacements)); + return Promise.all(promises); +} + +export async function replaceInFile(file: string, replacements: Vector): Promise { + return new Promise(async (resolve, reject) => { + const inInterface = readline.createInterface({input: fs.createReadStream(file)}); + try { + const lines: string[] = []; + for await (const line of inInterface) { + lines.push( + replacements + .foldLeft(line, (l, repl) => l.replace(repl.src, repl.trg)) + ); + } + resolve(lines.map(l => l + '\n').join('')); + } catch (err) { + reject(err); + } finally { + inInterface.close(); + } + }).then(lines => fs.promises.writeFile(file, lines)); +} diff --git a/tsconfig.benchmarks.json b/tsconfig.benchmarks.json index 4f325e4..1f0739a 100644 --- a/tsconfig.benchmarks.json +++ b/tsconfig.benchmarks.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.json", "include": [ "src/**/*", "benchmarks/**/*" diff --git a/tsconfig.docgen.json b/tsconfig.docgen.json index 957108d..261608d 100644 --- a/tsconfig.docgen.json +++ b/tsconfig.docgen.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.json", "include": [ "src/**/*", "scripts/**/*" diff --git a/tsconfig.base.json b/tsconfig.json similarity index 76% rename from tsconfig.base.json rename to tsconfig.json index de67ba6..8e78909 100644 --- a/tsconfig.base.json +++ b/tsconfig.json @@ -10,6 +10,9 @@ "outDir": "./dist", "declaration": true, "sourceMap": true, - "lib": ["es2015", "dom"] // I need DOM because of typedoc.. - } + "lib": ["es2015", "dom", "esnext.asynciterable"] + }, + "include": [ + "**/*" + ] } diff --git a/tsconfig.prepublish.json b/tsconfig.prepublish.json index 5b85fa1..6b00026 100644 --- a/tsconfig.prepublish.json +++ b/tsconfig.prepublish.json @@ -1,5 +1,8 @@ { - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.json", + "compilerOptions": { + "lib": ["es2015", "dom"] + }, "include": [ "src/**/*" ] diff --git a/tsconfig.test.json b/tsconfig.test.json index 02bdc2a..52d80a5 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.json", "include": [ "src/**/*", "tests/**/*"