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
Atul Varma 2021-02-26 21:30:38 -05:00 zatwierdzone przez GitHub
rodzic 8e46e4d5f1
commit 3e1b66a984
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
8 zmienionych plików z 138 dodań i 1 usunięć

Wyświetl plik

@ -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"');
});
});

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;
};

Wyświetl plik

@ -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);
}
}

5
package-lock.json wygenerowano
Wyświetl plik

@ -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",

Wyświetl plik

@ -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": [

17
svg/_template.toml 100644
Wyświetl plik

@ -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

7
svg/eye.toml 100644
Wyświetl plik

@ -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