From d580f007a7c59b8bb473d662ab068323bfc46d9b Mon Sep 17 00:00:00 2001 From: mikaello <2505178+mikaello@users.noreply.github.com> Date: Sat, 12 Sep 2026 06:57:37 +0200 Subject: [PATCH 1/4] feat: add documented public interface --- __tests__/Tinycolor_tests.res | 12 +- src/TinyColor.resi | 334 ++++++++++++++++++++++++++++++++++ 2 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 src/TinyColor.resi diff --git a/__tests__/Tinycolor_tests.res b/__tests__/Tinycolor_tests.res index 4366143..5506d88 100644 --- a/__tests__/Tinycolor_tests.res +++ b/__tests__/Tinycolor_tests.res @@ -73,7 +73,9 @@ describe("making tinycolor", () => { let input: TinyColor.rgbaRatio = {r: 0.1, g: 0.5, b: 0.7, a: 0.7} let a = TinyColor.makeFromRgbaRatio(input) - expect(Option.map(a, TinyColor.getOriginalInput))->toEqual(Some(TinyColor.rgbaRatioToJs(input))) + expect(Option.map(a, TinyColor.getOriginalInput))->toEqual( + Some(JSON.parseOrThrow(`{"r":0.1,"g":0.5,"b":0.7,"a":0.7}`)), + ) }) test("makeFromHsl() returns valid", () => { @@ -108,7 +110,9 @@ describe("making tinycolor", () => { let input: TinyColor.hslaRatio = {h: 0.1, s: 0.5, l: 0.7, a: 0.7} let a = TinyColor.makeFromHslaRatio(input) - expect(Option.map(a, TinyColor.getOriginalInput))->toEqual(Some(TinyColor.hslaRatioToJs(input))) + expect(Option.map(a, TinyColor.getOriginalInput))->toEqual( + Some(JSON.parseOrThrow(`{"h":0.1,"s":0.5,"l":0.7,"a":0.7}`)), + ) }) test("makeFromHsv() returns valid", () => { @@ -143,7 +147,9 @@ describe("making tinycolor", () => { let input: TinyColor.hsvaRatio = {h: 0.1, s: 0.5, v: 0.7, a: 0.7} let a = TinyColor.makeFromHsvaRatio(input) - expect(Option.map(a, TinyColor.getOriginalInput))->toEqual(Some(TinyColor.hsvaRatioToJs(input))) + expect(Option.map(a, TinyColor.getOriginalInput))->toEqual( + Some(JSON.parseOrThrow(`{"h":0.1,"s":0.5,"v":0.7,"a":0.7}`)), + ) }) test("makeFromCmyk() returns valid", () => { let a = TinyColor.makeFromCmyk({c: 0, m: 100, y: 100, k: 0}) diff --git a/src/TinyColor.resi b/src/TinyColor.resi new file mode 100644 index 0000000..efd3ecf --- /dev/null +++ b/src/TinyColor.resi @@ -0,0 +1,334 @@ +/** A validated TinyColor instance. */ +type t + +/** An RGB color with channels from 0 through 255. */ +type rgb = { + r: int, + g: int, + b: int, +} + +/** An RGB color with channels from 0 through 255 and alpha from 0.0 through 1.0. */ +type rgba = { + r: int, + g: int, + b: int, + a: float, +} + +/** An RGB color expressed as percentages and alpha from 0.0 through 1.0. */ +type rgbaPercentage = { + r: string, + g: string, + b: string, + a: float, +} + +/** An RGB color with channels expressed as ratios from 0.0 through 1.0. */ +type rgbRatio = { + r: float, + g: float, + b: float, +} + +/** An RGB color with channels and alpha expressed as ratios from 0.0 through 1.0. */ +type rgbaRatio = { + r: float, + g: float, + b: float, + a: float, +} + +/** An HSL color with hue in degrees and saturation and lightness as ratios. */ +type hsl = { + h: int, + s: float, + l: float, +} + +/** An HSL color with hue in degrees and the other values expressed as ratios. */ +type hsla = { + h: int, + s: float, + l: float, + a: float, +} + +/** An HSL color with every value expressed as a ratio from 0.0 through 1.0. */ +type hslRatio = { + h: float, + s: float, + l: float, +} + +/** An HSLA color with every value expressed as a ratio from 0.0 through 1.0. */ +type hslaRatio = { + h: float, + s: float, + l: float, + a: float, +} + +/** An HSV color with hue in degrees and saturation and value as ratios. */ +type hsv = { + h: int, + s: float, + v: float, +} + +/** An HSV color with hue in degrees and the other values expressed as ratios. */ +type hsva = { + h: int, + s: float, + v: float, + a: float, +} + +/** An HSV color with every value expressed as a ratio from 0.0 through 1.0. */ +type hsvRatio = { + h: float, + s: float, + v: float, +} + +/** An HSVA color with every value expressed as a ratio from 0.0 through 1.0. */ +type hsvaRatio = { + h: float, + s: float, + v: float, + a: float, +} + +/** A CMYK color with values from 0 through 100. */ +type cmyk = { + c: int, + m: int, + y: int, + k: int, +} + +/** Returns whether a TinyColor instance is valid. */ +let isValid: t => bool + +/** Returns whether a color has no saturation. */ +let isMonochrome: t => bool + +/** Creates a color from a CSS color name, hex value, or color function. */ +let makeFromString: string => option + +/** Creates a color from a non-negative numeric color value. */ +let makeFromNumber: int => option + +/** Creates a color from RGB channels from 0 through 255. */ +let makeFromRgb: rgb => option + +/** Creates a color from RGB channels from 0 through 255 and alpha from 0.0 through 1.0. */ +let makeFromRgba: rgba => option + +/** Creates a color from RGB channel ratios from 0.0 through 1.0. */ +let makeFromRgbRatio: rgbRatio => option + +/** Creates a color from RGB channel and alpha ratios from 0.0 through 1.0. */ +let makeFromRgbaRatio: rgbaRatio => option + +/** Creates a color from a hue in degrees and saturation and lightness ratios. */ +let makeFromHsl: hsl => option + +/** Creates a color from a hue in degrees and saturation, lightness, and alpha ratios. */ +let makeFromHsla: hsla => option + +/** Creates a color from HSL ratios from 0.0 through 1.0. */ +let makeFromHslRatio: hslRatio => option + +/** Creates a color from HSLA ratios from 0.0 through 1.0. */ +let makeFromHslaRatio: hslaRatio => option + +/** Creates a color from a hue in degrees and saturation and value ratios. */ +let makeFromHsv: hsv => option + +/** Creates a color from a hue in degrees and saturation, value, and alpha ratios. */ +let makeFromHsva: hsva => option + +/** Creates a color from HSV ratios from 0.0 through 1.0. */ +let makeFromHsvRatio: hsvRatio => option + +/** Creates a color from HSVA ratios from 0.0 through 1.0. */ +let makeFromHsvaRatio: hsvaRatio => option + +/** Creates a color from CMYK values from 0 through 100. */ +let makeFromCmyk: cmyk => option + +/** Returns the format used to create the color. */ +let getFormat: t => string + +/** Returns the original color input as JSON. */ +let getOriginalInput: t => JSON.t + +/** Returns perceived brightness from 0.0 through 255.0. */ +let getBrightness: t => float + +/** Returns whether the color is considered light. */ +let isLight: t => bool + +/** Returns whether the color is considered dark. */ +let isDark: t => bool + +/** Returns relative luminance from 0.0 through 1.0. */ +let getLuminance: t => float + +/** Returns alpha from 0.0 through 1.0. */ +let getAlpha: t => float + +/** Returns a copy with the supplied alpha value. */ +let setAlpha: (float, t) => t + +/** Returns the numeric RGB representation of the color. */ +let toNumber: t => int + +/** Returns a copy of the color. */ +let clone: t => t + +/** Composites the color over a background color. */ +let onBackground: (t, t) => t + +/** Converts the color to HSVA. */ +let toHsv: t => hsva + +/** Converts the color to an HSV or HSVA string. */ +let toHsvString: t => string + +/** Converts the color to HSLA. */ +let toHsl: t => hsla + +/** Converts the color to an HSL or HSLA string. */ +let toHslString: t => string + +/** Converts the color to a hex value without a leading hash. */ +let toHex: t => string + +/** Converts the color to a six-digit hex string. */ +let toHexString: t => string + +/** Converts the color to an eight-digit hex value without a leading hash. */ +let toHex8: t => string + +/** Converts the color to an eight-digit hex string. */ +let toHex8String: t => string + +/** Converts the color to the shortest equivalent hex string. */ +let toHexShortString: t => string + +/** Converts the color to RGBA. */ +let toRgb: t => rgba + +/** Converts the color to an RGB or RGBA string. */ +let toRgbString: t => string + +/** Converts the color to percentage RGBA. */ +let toPercentageRgb: t => rgbaPercentage + +/** Converts the color to a percentage RGB or RGBA string. */ +let toPercentageRgbString: t => string + +/** Converts the color to CMYK. */ +let toCmyk: t => cmyk + +/** Converts the color to a CMYK string. */ +let toCmykString: t => string + +/** Returns the CSS color name when one exactly matches. */ +let toName: t => option + +/** Creates a Microsoft gradient filter from two color strings. */ +let toMsFilter: (string, string) => string + +/** Converts the color to a string using its original format when possible. */ +let toString: t => string + +/** Lightens the color by a percentage from 0 through 100. */ +let lighten: (~value: int=?, t) => option + +/** Brightens the color by a percentage from 0 through 100. */ +let brighten: (~value: int=?, t) => option + +/** Darkens the color by a percentage from 0 through 100. */ +let darken: (~value: int=?, t) => option + +/** Mixes the color with white by a percentage from 0 through 100. */ +let tint: (~value: int=?, t) => option + +/** Mixes the color with black by a percentage from 0 through 100. */ +let shade: (~value: int=?, t) => option + +/** Decreases saturation by a percentage from 0 through 100. */ +let desaturate: (~value: int=?, t) => option + +/** Increases saturation by a percentage from 0 through 100. */ +let saturate: (~value: int=?, t) => option + +/** Rotates the hue by the supplied number of degrees. */ +let spin: (~value: int=?, t) => t + +/** Mixes two colors using a percentage from 0 through 100. */ +let mix: (~value: int=?, t, t) => option + +/** Returns the greyscale equivalent of the color. */ +let greyscale: t => t + +/** Returns an analogous color scheme. */ +let analogous: (t, ~results: int=?, ~slices: int=?, unit) => array + +/** Returns a monochromatic color scheme. */ +let monochromatic: (t, ~results: int=?, unit) => array + +/** Returns a split-complementary color scheme. */ +let splitcomplement: t => array + +/** Returns a triadic color scheme. */ +let triad: t => array + +/** Returns a tetradic color scheme. */ +let tetrad: t => array + +/** Returns a color scheme with the requested number of colors. */ +let polyad: (t, ~n: int=?, unit) => array + +/** Returns the complementary color. */ +let complement: t => t + +/** Returns whether two colors have the same RGB and alpha values. */ +let equals: (t, t) => bool + +/** Returns a random color. */ +let random: ( + ~hue: [#red | #orange | #yellow | #green | #blue | #purple | #pink | #monochrome]=?, + ~luminosity: [#bright | #light | #dark]=?, + ~seed: int=?, + ~alpha: float=?, + unit, +) => t + +/** Returns the requested number of random colors. */ +let randomMultiple: ( + ~hue: [#red | #orange | #yellow | #green | #blue | #purple | #pink | #monochrome]=?, + ~luminosity: [#bright | #light | #dark]=?, + ~seed: int=?, + ~alpha: float=?, + ~count: int, + unit, +) => array + +/** Returns the WCAG contrast ratio between two colors. */ +let readability: (t, t) => float + +/** Returns whether two colors meet the requested WCAG readability level. */ +let isReadable: (~level: [#AA | #AAA]=?, ~size: [#small | #large]=?, t, t) => bool + +/** Returns the most readable color from a list of candidates. */ +let mostReadable: ( + ~includeFallbackColors: bool=?, + ~level: [#AA | #AAA]=?, + ~size: [#small | #large]=?, + array, + t, +) => t From 7a597a6cc9dd7ea7eab0fbb7c6e29b4768554bb8 Mon Sep 17 00:00:00 2001 From: mikaello <2505178+mikaello@users.noreply.github.com> Date: Sat, 12 Sep 2026 22:08:12 +0200 Subject: [PATCH 2/4] docs: publish generated API reference --- .github/workflows/docs.yml | 52 ++++++ .gitignore | 3 + README.md | 6 + package.json | 1 + scripts/generate-docs.mjs | 322 +++++++++++++++++++++++++++++++++++++ 5 files changed, 384 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 scripts/generate-docs.mjs diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..aa604ea --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,52 @@ +name: Publish API documentation + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pages-${{ github.ref }} + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Use Node.js 24 + uses: actions/setup-node@v7 + with: + node-version: 24.x + cache: npm + - run: npm ci + - name: Generate API documentation + run: npm run docs:build + - name: Configure GitHub Pages + if: github.event_name != 'pull_request' + uses: actions/configure-pages@v6 + - name: Upload GitHub Pages artifact + if: github.event_name != 'pull_request' + uses: actions/upload-pages-artifact@v5 + with: + path: docs-site + + deploy: + if: github.event_name != 'pull_request' + needs: build + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.gitignore b/.gitignore index 8ca74db..16ecd06 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ yarn-error.log* # Dependency directories node_modules/ +# Generated API documentation +docs-site/ + # Optional npm cache directory .npm diff --git a/README.md b/README.md index a91cb0e..f85d5ff 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,12 @@ let isReadableInCombination = switch (redString, blueRgb) { See all available functions in the [original TinyColor repo](https://github.com/scttcper/tinycolor) and example usage of all functions in [the tests](https://github.com/mikaello/rescript-tinycolor/blob/master/__tests__/Tinycolor_tests.res). +## API documentation + +The generated [API reference](https://mikaello.github.io/rescript-tinycolor/) includes searchable signatures and links back to their ReScript source. + +To build it locally, run `npm run docs:build` and open `docs-site/index.html`. + ## Differences from original - It is not possible to create an invalid tinycolor instance, it will either return `Some(t)` if it is valid, or `None` if it is invalid. E.g. an invalid instance can occur if you create a color with a string not corresponding to a valid color (`beautifulRed` is not a valid color) or you provide RGB values outside the valid range (0-255). diff --git a/package.json b/package.json index 6de1771..42cb34b 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "5.1.2", "scripts": { "build": "rescript build", + "docs:build": "npm run build && node scripts/generate-docs.mjs", "start": "rescript build -w", "clean": "rescript clean", "check": "rescript format --check", diff --git a/scripts/generate-docs.mjs b/scripts/generate-docs.mjs new file mode 100644 index 0000000..7bfe0ab --- /dev/null +++ b/scripts/generate-docs.mjs @@ -0,0 +1,322 @@ +import {execFileSync} from "node:child_process" +import {existsSync, mkdirSync, readFileSync, writeFileSync} from "node:fs" +import {dirname, join} from "node:path" +import {fileURLToPath} from "node:url" + +const root = join(dirname(fileURLToPath(import.meta.url)), "..") +const outputDirectory = join(root, "docs-site") +const sourceFile = existsSync(join(root, "src/TinyColor.resi")) + ? "src/TinyColor.resi" + : "src/TinyColor.res" +const tools = join(root, "node_modules/rescript/cli/rescript-tools.js") +const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) + +const extracted = execFileSync(process.execPath, [tools, "doc", sourceFile], { + cwd: root, + encoding: "utf8", +}) + +if (extracted.trim() === "") { + throw new Error(`No documentation was extracted from ${sourceFile}`) +} + +const documentation = JSON.parse(extracted) +const implementationDetails = new Set([ + "callIfValidModificationValue", + "isFraction", + "isValidHue", + "make", + "mostReadableConfigType", + "mostReadableNullable", + "randomConfigType", + "returnSomeIfValid", + "validateCmyk", + "validateColorNumber", + "validateHsl", + "validateHsla", + "validateHsv", + "validateHsva", + "validateRgb", + "validateRgba", + "wcagOptionType", +]) + +const apiItems = documentation.items.filter( + item => + item.source?.filepath?.startsWith("src/") && + !implementationDetails.has(item.name), +) + +if (apiItems.length === 0) { + throw new Error(`No public API entries were extracted from ${sourceFile}`) +} + +const categories = [ + ["Types", item => item.kind === "type"], + ["Create", item => item.name.startsWith("makeFrom")], + [ + "Inspect", + item => + item.name.startsWith("get") || + ["isDark", "isLight", "isMonochrome", "isValid"].includes(item.name), + ], + ["Convert", item => item.name.startsWith("to")], + [ + "Adjust", + item => + [ + "brighten", + "clone", + "darken", + "desaturate", + "greyscale", + "lighten", + "mix", + "onBackground", + "saturate", + "setAlpha", + "shade", + "spin", + "tint", + ].includes(item.name), + ], + [ + "Combine", + item => + [ + "analogous", + "complement", + "monochromatic", + "polyad", + "splitcomplement", + "tetrad", + "triad", + ].includes(item.name), + ], + ["Utilities", () => true], +] + +const grouped = new Map(categories.map(([name]) => [name, []])) +for (const item of apiItems) { + const category = categories.find(([, matches]) => matches(item))[0] + grouped.get(category).push(item) +} + +const escapeHtml = value => + String(value) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'") + +const itemId = item => `${item.kind}-${item.name.toLowerCase()}` +const repository = "https://github.com/mikaello/rescript-tinycolor" + +const renderItem = item => { + const docs = item.docstrings + .map(doc => `

${escapeHtml(doc)}

`) + .join("") + const sourceUrl = `${repository}/blob/master/${item.source.filepath}#L${item.source.line}` + + return ` +
+
+
+ ${escapeHtml(item.kind)} +

${escapeHtml(item.name)}

+
+ Source ↗ +
+ ${docs} +
${escapeHtml(item.signature)}
+
` +} + +const navigation = [...grouped] + .filter(([, items]) => items.length > 0) + .map( + ([category, items]) => ` + + ${category} + ${items.length} + `, + ) + .join("") + +const sections = [...grouped] + .filter(([, items]) => items.length > 0) + .map( + ([category, items]) => ` +
+
+

API group

+

${category}

+
+
${items.map(renderItem).join("")}
+
`, + ) + .join("") + +const html = ` + + + + + + + rescript-tinycolor · API + + + +
+
+

Generated ReScript API reference

+

Small library.
Clear colors.

+

Fast, typed color manipulation and conversion for ReScript, powered by TinyColor.

+
+ npm install rescript-tinycolor + GitHub ↗ + v${escapeHtml(packageJson.version)} · ${apiItems.length} API entries +
+
+
+
+ +
+ ${sections} +

No API entries match that search.

+
+
+
Generated from ${sourceFile} with the ReScript documentation extractor.
+ + + +` + +mkdirSync(outputDirectory, {recursive: true}) +writeFileSync(join(outputDirectory, "index.html"), html) +writeFileSync(join(outputDirectory, "api.json"), `${JSON.stringify(documentation, null, 2)}\n`) +writeFileSync(join(outputDirectory, ".nojekyll"), "") + +console.log(`Generated ${apiItems.length} API entries from ${sourceFile}`) From c2aad5423bec0ecca133f7454d17b0af059f50e7 Mon Sep 17 00:00:00 2001 From: mikaello <2505178+mikaello@users.noreply.github.com> Date: Sat, 12 Sep 2026 22:28:04 +0200 Subject: [PATCH 3/4] refactor: infer API documentation structure --- .github/workflows/docs.yml | 5 +- package.json | 2 +- .../{generate-docs.mjs => generate-docs.mts} | 140 +++++++----------- 3 files changed, 56 insertions(+), 91 deletions(-) rename scripts/{generate-docs.mjs => generate-docs.mts} (80%) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index aa604ea..435618c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,7 +4,6 @@ on: push: branches: [master] pull_request: - branches: [master] workflow_dispatch: permissions: @@ -19,10 +18,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - name: Use Node.js 24 + - name: Use Node.js 26 uses: actions/setup-node@v7 with: - node-version: 24.x + node-version: 26.x cache: npm - run: npm ci - name: Generate API documentation diff --git a/package.json b/package.json index 42cb34b..b223132 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "5.1.2", "scripts": { "build": "rescript build", - "docs:build": "npm run build && node scripts/generate-docs.mjs", + "docs:build": "npm run build && node scripts/generate-docs.mts", "start": "rescript build -w", "clean": "rescript clean", "check": "rescript format --check", diff --git a/scripts/generate-docs.mjs b/scripts/generate-docs.mts similarity index 80% rename from scripts/generate-docs.mjs rename to scripts/generate-docs.mts index 7bfe0ab..f550ac9 100644 --- a/scripts/generate-docs.mjs +++ b/scripts/generate-docs.mts @@ -1,15 +1,39 @@ import {execFileSync} from "node:child_process" -import {existsSync, mkdirSync, readFileSync, writeFileSync} from "node:fs" +import {mkdirSync, readFileSync, writeFileSync} from "node:fs" import {dirname, join} from "node:path" import {fileURLToPath} from "node:url" +type Source = { + filepath: string + line: number + col: number +} + +type ApiItem = { + kind: string + name: string + signature: string + docstrings: string[] + source: Source +} + +type Documentation = { + items: ApiItem[] +} + +type PackageJson = { + name: string + version: string + repository: string | {url: string} +} + const root = join(dirname(fileURLToPath(import.meta.url)), "..") const outputDirectory = join(root, "docs-site") -const sourceFile = existsSync(join(root, "src/TinyColor.resi")) - ? "src/TinyColor.resi" - : "src/TinyColor.res" +const sourceFile = "src/TinyColor.resi" const tools = join(root, "node_modules/rescript/cli/rescript-tools.js") -const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) +const packageJson = JSON.parse( + readFileSync(join(root, "package.json"), "utf8"), +) as PackageJson const extracted = execFileSync(process.execPath, [tools, "doc", sourceFile], { cwd: root, @@ -20,89 +44,23 @@ if (extracted.trim() === "") { throw new Error(`No documentation was extracted from ${sourceFile}`) } -const documentation = JSON.parse(extracted) -const implementationDetails = new Set([ - "callIfValidModificationValue", - "isFraction", - "isValidHue", - "make", - "mostReadableConfigType", - "mostReadableNullable", - "randomConfigType", - "returnSomeIfValid", - "validateCmyk", - "validateColorNumber", - "validateHsl", - "validateHsla", - "validateHsv", - "validateHsva", - "validateRgb", - "validateRgba", - "wcagOptionType", -]) - -const apiItems = documentation.items.filter( - item => - item.source?.filepath?.startsWith("src/") && - !implementationDetails.has(item.name), -) +const documentation = JSON.parse(extracted) as Documentation +const apiItems = documentation.items if (apiItems.length === 0) { throw new Error(`No public API entries were extracted from ${sourceFile}`) } -const categories = [ - ["Types", item => item.kind === "type"], - ["Create", item => item.name.startsWith("makeFrom")], - [ - "Inspect", - item => - item.name.startsWith("get") || - ["isDark", "isLight", "isMonochrome", "isValid"].includes(item.name), - ], - ["Convert", item => item.name.startsWith("to")], - [ - "Adjust", - item => - [ - "brighten", - "clone", - "darken", - "desaturate", - "greyscale", - "lighten", - "mix", - "onBackground", - "saturate", - "setAlpha", - "shade", - "spin", - "tint", - ].includes(item.name), - ], - [ - "Combine", - item => - [ - "analogous", - "complement", - "monochromatic", - "polyad", - "splitcomplement", - "tetrad", - "triad", - ].includes(item.name), - ], - ["Utilities", () => true], -] +const formatKind = (kind: string): string => + `${kind.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, first => first.toUpperCase())}s` -const grouped = new Map(categories.map(([name]) => [name, []])) +const grouped = new Map() for (const item of apiItems) { - const category = categories.find(([, matches]) => matches(item))[0] - grouped.get(category).push(item) + const category = formatKind(item.kind) + grouped.set(category, [...(grouped.get(category) ?? []), item]) } -const escapeHtml = value => +const escapeHtml = (value: unknown): string => String(value) .replaceAll("&", "&") .replaceAll("<", "<") @@ -110,14 +68,22 @@ const escapeHtml = value => .replaceAll('"', """) .replaceAll("'", "'") -const itemId = item => `${item.kind}-${item.name.toLowerCase()}` -const repository = "https://github.com/mikaello/rescript-tinycolor" +const itemId = (item: ApiItem): string => + `${item.kind}-${item.name.toLowerCase()}` +const repositoryValue = + typeof packageJson.repository === "string" + ? packageJson.repository + : packageJson.repository.url +const repository = repositoryValue + .replace(/^git\+/, "") + .replace(/^git@github\.com:/, "https://github.com/") + .replace(/\.git$/, "") -const renderItem = item => { +const renderItem = (item: ApiItem): string => { const docs = item.docstrings .map(doc => `

${escapeHtml(doc)}

`) .join("") - const sourceUrl = `${repository}/blob/master/${item.source.filepath}#L${item.source.line}` + const sourceUrl = `${repository}/blob/HEAD/${item.source.filepath}#L${item.source.line}` return `
@@ -150,7 +116,7 @@ const sections = [...grouped] ([category, items]) => `
-

API group

+

Extracted kind

${category}

${items.map(renderItem).join("")}
@@ -163,9 +129,9 @@ const html = ` - + - rescript-tinycolor · API + ${escapeHtml(packageJson.name)} · API