From 3e1b66a984b6c8fd7d4fc0cc9dbeb5f504027da4 Mon Sep 17 00:00:00 2001 From: Atul Varma Date: Fri, 26 Feb 2021 21:30:38 -0500 Subject: [PATCH] Add support for TOML symbol metadata. (#33) This fixes #20 by adding support for TOML-based metadata for symbols. There's now a template TOML file in `svg/_template.toml` that documents all the different metadata properties. The file can be copied to match the name of an SVG file (but with a `.toml` extension) and it will be processed by the vocabulary builder when the site is generated. Currently there are only two properties documented, `always_nest` and `always_be_nested` (as per the needs outlined in https://github.com/toolness/mystic-symbolic/issues/17#issuecomment-786696966), but they don't actually do anything yet (actual support for _using_ the metadata will come in another file). Right now the TOML files are validated quite stringently: if a file contains the name of a property it doesn't understand, or the type of the property is wrong, it will raise an error. We can revisit this if it becomes burdensome. --- lib/svg-symbol-metadata.test.ts | 46 +++++++++++++++++++++++++++++++++ lib/svg-symbol-metadata.ts | 44 +++++++++++++++++++++++++++++++ lib/svg-symbol.tsx | 2 ++ lib/vocabulary-builder.ts | 17 +++++++++++- package-lock.json | 5 ++++ package.json | 1 + svg/_template.toml | 17 ++++++++++++ svg/eye.toml | 7 +++++ 8 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 lib/svg-symbol-metadata.test.ts create mode 100644 lib/svg-symbol-metadata.ts create mode 100644 svg/_template.toml create mode 100644 svg/eye.toml diff --git a/lib/svg-symbol-metadata.test.ts b/lib/svg-symbol-metadata.test.ts new file mode 100644 index 0000000..5ed1110 --- /dev/null +++ b/lib/svg-symbol-metadata.test.ts @@ -0,0 +1,46 @@ +import path from "path"; +import fs from "fs"; +import toml from "toml"; +import { validateSvgSymbolMetadata } from "./svg-symbol-metadata"; + +const templatePath = path.join(__dirname, "..", "svg", "_template.toml"); + +test("metadata template is valid SVG symbol metadata", () => { + validateSvgSymbolMetadata( + toml.parse( + fs.readFileSync(templatePath, { + encoding: "utf-8", + }) + ) + ); +}); + +describe("validateSvgSymbolMetadata()", () => { + it("works with valid metadata", () => { + expect( + validateSvgSymbolMetadata({ + always_nest: true, + always_be_nested: true, + }) + ).toEqual({ + always_nest: true, + always_be_nested: true, + }); + }); + + it("raises errors when a property is of the wrong type", () => { + expect(() => + validateSvgSymbolMetadata({ + always_nest: "true", + }) + ).toThrow('Expected "always_nest" to be a boolean, but it is a string!'); + }); + + it("raises errors when a property is unrecognized", () => { + expect(() => + validateSvgSymbolMetadata({ + blarp: true, + }) + ).toThrow('Unrecognized SVG symbol metadata property "blarp"'); + }); +}); diff --git a/lib/svg-symbol-metadata.ts b/lib/svg-symbol-metadata.ts new file mode 100644 index 0000000..c8700c9 --- /dev/null +++ b/lib/svg-symbol-metadata.ts @@ -0,0 +1,44 @@ +type SvgSymbolMetadataBooleans = { + /** + * If true, this indicates that the symbol should always have + * a symbol nested within its nesting area(s). + */ + always_nest?: boolean; + + /** + * If true, this indicates that the symbol should always + * be nested inside another symbol's nesting area. + */ + always_be_nested?: boolean; +}; + +const METADATA_BOOLEANS: Set = new Set([ + "always_nest", + "always_be_nested", +]); + +function isSvgSymbolMetadataBoolean( + key: string +): key is keyof SvgSymbolMetadataBooleans { + return METADATA_BOOLEANS.has(key as any); +} + +export type SvgSymbolMetadata = SvgSymbolMetadataBooleans; + +export function validateSvgSymbolMetadata(obj: any): SvgSymbolMetadata { + const result: SvgSymbolMetadata = {}; + for (let key in obj) { + const value: unknown = obj[key]; + if (isSvgSymbolMetadataBoolean(key)) { + if (typeof value !== "boolean") { + throw new Error( + `Expected "${key}" to be a boolean, but it is a ${typeof value}!` + ); + } + result[key] = value; + } else { + throw new Error(`Unrecognized SVG symbol metadata property "${key}"`); + } + } + return result; +} diff --git a/lib/svg-symbol.tsx b/lib/svg-symbol.tsx index e233a07..2463f5a 100644 --- a/lib/svg-symbol.tsx +++ b/lib/svg-symbol.tsx @@ -3,6 +3,7 @@ import { SVGProps } from "react"; import { BBox } from "../vendor/bezier-js"; import { FILL_REPLACEMENT_COLOR, STROKE_REPLACEMENT_COLOR } from "./colors"; import { Specs } from "./specs"; +import type { SvgSymbolMetadata } from "./svg-symbol-metadata"; import { VisibleSpecs } from "./visible-specs"; const DEFAULT_UNIFORM_STROKE_WIDTH = 1; @@ -11,6 +12,7 @@ export type SvgSymbolData = { name: string; bbox: BBox; layers: SvgSymbolElement[]; + meta?: SvgSymbolMetadata; specs?: Specs; }; diff --git a/lib/vocabulary-builder.ts b/lib/vocabulary-builder.ts index 8ae5e23..67b8b07 100644 --- a/lib/vocabulary-builder.ts +++ b/lib/vocabulary-builder.ts @@ -4,6 +4,8 @@ import cheerio from "cheerio"; import { getSvgBoundingBox } from "./bounding-box"; import { extractSpecs } from "./specs"; import { SvgSymbolData, SvgSymbolElement } from "./svg-symbol"; +import toml from "toml"; +import { validateSvgSymbolMetadata } from "./svg-symbol-metadata"; const SUPPORTED_SVG_TAG_ARRAY: SvgSymbolElement["tagName"][] = ["g", "path"]; const SUPPORTED_SVG_TAGS = new Set(SUPPORTED_SVG_TAG_ARRAY); @@ -109,11 +111,24 @@ export function build() { const vocab: SvgSymbolData[] = []; for (let filename of filenames) { if (path.extname(filename) === SVG_EXT) { - console.log(`Adding ${filename} to vocabulary.`); + let filenames = filename; + let metaToml: string | null = null; + const metaFilename = `${path.basename(filename, SVG_EXT)}.toml`; + const metaFilepath = path.join(SVG_DIR, metaFilename); + if (fs.existsSync(metaFilepath)) { + filenames += ` and ${metaFilename}`; + metaToml = fs.readFileSync(metaFilepath, { + encoding: "utf-8", + }); + } + console.log(`Adding ${filenames} to vocabulary.`); const svgMarkup = fs.readFileSync(path.join(SVG_DIR, filename), { encoding: "utf-8", }); const symbol = convertSvgMarkupToSymbolData(filename, svgMarkup); + if (metaToml) { + symbol.meta = validateSvgSymbolMetadata(toml.parse(metaToml)); + } vocab.push(symbol); } } diff --git a/package-lock.json b/package-lock.json index 943d539..d63314f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9682,6 +9682,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", diff --git a/package.json b/package.json index 596344b..3c64d16 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "prettier": "^2.2.1", "react": "^17.0.1", "react-dom": "^17.0.1", + "toml": "^3.0.0", "typescript": "^4.1.3" }, "browserslist": [ diff --git a/svg/_template.toml b/svg/_template.toml new file mode 100644 index 0000000..fc62d01 --- /dev/null +++ b/svg/_template.toml @@ -0,0 +1,17 @@ +# This can be used as a template for an SVG symbol's metadata. +# Just be sure you rename it to have the same name as the SVG, only +# with a a ".toml" extension instead of an ".svg" extension. +# +# So for example, if you want to define metadata for "antler.svg", +# you can make a copy of this file and call it "antler.toml". +# +# Also note that any lines that begin with a "#" are comments, +# so they will be ignored by the code. + +# If true, this indicates that the symbol should always have +# a symbol nested within its nesting area(s). +always_nest = false + +# If true, this indicates that the symbol should always +# be nested inside another symbol's nesting area. +always_be_nested = false diff --git a/svg/eye.toml b/svg/eye.toml new file mode 100644 index 0000000..9e7123e --- /dev/null +++ b/svg/eye.toml @@ -0,0 +1,7 @@ +# If true, this indicates that the symbol should always have +# a symbol nested within its nesting area(s). +always_nest = true + +# If true, this indicates that the symbol should always +# be nested inside another symbol's nesting area. +always_be_nested = false