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.pull/34/head
rodzic
8e46e4d5f1
commit
3e1b66a984
|
@ -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"');
|
||||
});
|
||||
});
|
|
@ -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<keyof SvgSymbolMetadataBooleans> = 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;
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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
|
|
@ -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
|
Ładowanie…
Reference in New Issue