{"id":18482305,"url":"https://github.com/openstyles/usercss-meta","last_synced_at":"2025-06-17T23:06:14.516Z","repository":{"id":39667995,"uuid":"116712038","full_name":"openstyles/usercss-meta","owner":"openstyles","description":"Parse usercss styles supported by the Stylus userstyle manager","archived":false,"fork":false,"pushed_at":"2023-03-14T16:30:39.000Z","size":1820,"stargazers_count":17,"open_issues_count":14,"forks_count":5,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-06-03T11:54:20.785Z","etag":null,"topics":["css","parser","stylus","usercss"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/openstyles.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-01-08T18:20:13.000Z","updated_at":"2025-01-31T17:07:59.000Z","dependencies_parsed_at":"2024-12-03T09:16:56.038Z","dependency_job_id":null,"html_url":"https://github.com/openstyles/usercss-meta","commit_stats":{"total_commits":97,"total_committers":6,"mean_commits":"16.166666666666668","dds":"0.12371134020618557","last_synced_commit":"b5d6e708e5e989f0be8873b79f92845e0d9202ea"},"previous_names":["stylishthemes/parse-usercss"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/openstyles/usercss-meta","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openstyles%2Fusercss-meta","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openstyles%2Fusercss-meta/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openstyles%2Fusercss-meta/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openstyles%2Fusercss-meta/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/openstyles","download_url":"https://codeload.github.com/openstyles/usercss-meta/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openstyles%2Fusercss-meta/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259732777,"owners_count":22903086,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","parser","stylus","usercss"],"created_at":"2024-11-06T12:27:49.329Z","updated_at":"2025-06-17T23:06:09.497Z","avatar_url":"https://github.com/openstyles.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# usercss-meta\n\u003e Parse usercss metadata supported by the Stylus userstyle manager\n\n\n## Install\n\n[NPM](https://www.npmjs.com/package/usercss-meta)\n\n```\n$ npm install --save usercss-meta\n```\n\nunpkg.com CDN:\n\n* \u003chttps://unpkg.com/usercss-meta/dist/usercss-meta.js\u003e\n* \u003chttps://unpkg.com/usercss-meta/dist/usercss-meta.min.js\u003e\n\nThis module depends on `URL` parser. In Node.js, the module requires `url` module. In the browser build, it uses global variable `URL`.\n\n## Usage\n\n```js\nconst usercssMeta = require('usercss-meta');\n\nconst {metadata} = usercssMeta.parse(`/* ==UserStyle==\n@name        test\n@namespace   github.com/openstyles/stylus\n@version     0.1.0\n@description my userstyle\n@author      Me\n@var text my-color \"Select a color\" #123456\n==/UserStyle== */`);\n\n/* =\u003e {\n  \"vars\": {\n    \"my-color\": {\n      \"type\": \"text\",\n      \"label\": \"Select a color\",\n      \"name\": \"my-color\",\n      \"value\": null,\n      \"default\": \"#123456\",\n      \"options\": null\n    }\n  },\n  \"name\": \"test\",\n  \"namespace\": \"github.com/openstyles/stylus\",\n  \"version\": \"0.1.0\",\n  \"description\": \"my userstyle\",\n  \"author\": \"Me\"\n}\n*/\n\nusercssMeta.stringify(metadata, {alignKeys: true});\n\n/* =\u003e `/* ==UserStyle==\n@name        test\n@namespace   github.com/openstyles/stylus\n@version     0.1.0\n@description my userstyle\n@author      Me\n@var         text my-color \"Select a color\" #123456\n==/UserStyle== *\\/`\n\n*/\n```\n\n## API Reference\n\nThis module exports following members:\n\n* To parse metadata:\n  * `parse`: Function. Parse metadata and return an object.\n  * `createParser`: Function. Create a metadata parser.\n  * `ParseError`: Class.\n  * `util`: Object. A collection of parser utilities.\n* To stringify metadata:\n  * `stringify`: Function. Stringify metadata object and return the string.\n  * `createStringifier`: Function. Create a metadata stringifier.\n\n### parse\n\n```js\nconst parseResult = parse(text: String, options?: Object);\n```\n\nThis is a shortcut of\n\n```js\ncreateParser(options).parse(text);\n```\n\n### createParser\n\n```js\nconst parser = createParser({\n  unknownKey?: String,\n  mandatoryKeys?: Array\u003ckey: String\u003e\n  parseKey?: Object,\n  parseVar?: Object,\n  validateKey?: Object,\n  validateVar?: Object,\n  allowErrors?: Boolean\n});\n```\n\n`unknownKey` decides how to parse unknown keys. Possible values are:\n\n- `ignore`: The directive is ignored. Default.\n- `assign`: Assign the text value (characters before `\\s*\\n`) to result object.\n- `throw`: Throw a `ParseError`.\n\n`mandatoryKeys` marks multiple keys as mandatory. If some keys are missing then throw a `ParseError`. Default: `['name', 'namespace', 'version']`.\n\n`parseKey` is a `key`/`parseFunction` map. It allows users to extend the parser. Example:\n\n```js\nconst parser = createParser({\n  mandatoryKeys: [],\n  parseKey: {\n    myKey: util.parseNumber\n  }\n});\nconst {metadata} = parser.parse(`\n  /* ==UserStyle==\n  @myKey 123456\n  ==/UserStyle==\n`);\nassert.equal(metadata.myKey, 123456);\n```\n\n`parseVar` is a `variableType`/`parseFunction` map. It extends the parser to parse additional variable types. For example:\n\n```js\nconst parser = createParser({\n  mandatoryKeys: [],\n  parseVar: {\n    myvar: util.parseNumber\n  }\n});\nconst {metadata} = parser.parse(`/* ==UserStyle==\n@var myvar var-name 'Customized variable' 123456\n==/UserStyle== */`);\nconst va = metadata.vars['var-name'];\nassert.equal(va.type, 'myvar');\nassert.equal(va.label, 'Customized variable');\nassert.equal(va.default, 123456);\n```\n\n`validateKey` is a `key`/`validateFunction` map, which is used to validate the metadata value. The function accepts a `state` object:\n\n```js\nconst parser = createParser({\n  validateKey: {\n    updateURL: state =\u003e {\n      if (/example\\.com/.test(state.value)) {\n        throw new ParseError({\n          message: 'Example.com is not a good URL',\n          index: state.valueIndex\n        });\n      }\n    }\n  }\n});\n```\n\nThere are some builtin validators, which can be overwritten:\n\n|Key|Description|\n|---|-----------|\n|`version`|Ensure the value matches [semver-regex](https://github.com/sindresorhus/semver-regex) then strip the leading `v` or `=`.|\n|`homepageURL`|Ensure it is a valid URL and the protocol must be `http` or `https`.|\n|`updateURL`|Same as above.|\n|`supportURL`|Same as above.|\n\n`validateVar` is a `variableType`/`validateFunction` map, which is used to validate variables. The function accepts a `state` object:\n\n```js\nconst parser = createParser({\n  validateVar: {\n    color: state =\u003e {\n      if (state.value === 'red') {\n        throw new ParseError({\n          message: '`red` is not allowed',\n          index: state.valueIndex\n        });\n      }\n    }\n  }\n});\n```\n\nBuiltin validators:\n\n|Variable Type|Description|\n|-------------|-----------|\n|`checkbox`|Ensure the value is 0 or 1.|\n|`number`|Ensure sure the value is a number, doesn't exceed the minimum/maximum, and is a multiple of the step value.|\n|`range`|Same as above.|\n\nIf `allowErrors` is `true`, the parser will collect parsing errors while `parser.parse()` and return them as `parseResult.errors`. Otherwise, the first parsing error will be thrown.\n\n### parser.parse\n\n```js\nconst {\n  metadata: Object,\n  errors: Array\n} = parser.parse(text: String);\n```\n\nParse the text (metadata header) and return the result.\n\n### parser.validateVar\n\n```js\nparser.validateVar(varObj);\n```\n\nValidate the value of the variable object. This function uses the validators defined in `createParser`.\n\n`varObj` is the variable object in `metadata.vars`:\n\n```js\nconst {metadata} = parse(text);\n\n/* modify metadata.vars['some-var'].value ... */\n\nfor (const varObj of Object.values(metadata.vars)) {\n  validateVar(varObj);\n}\n```\n\n### ParseError\n\n```js\nthrow new ParseError(properties: Object);\n```\n\nUse this class to initiate a parse error.\n\n`properties` would be assigned to the error object. There are some special properties:\n\n* `code` - error code.\n* `message` - error message.\n* `index` - the string index where the error occurs.\n* `args` - an array of values that is used to compose the error message. This allows other clients to generate i18n error message.\n\nA table of errors thrown by the parser:\n\n|`err.code`|`err.args`|Description|\n|----------|----------|-----------|\n|`invalidCheckboxDefault`||Expect 0 or 1.|\n|`invalidRange`|Variable type|Expect a number or an array.|\n|`invalidRangeMultipleUnits`|Variable type|Two different units are defined.|\n|`invalidRangeTooManyValues`|Variable type|Too many values in the array.|\n|`invalidRangeValue`|Variable type|Values in the array must be number, string, or null.|\n|`invalidRangeDefault`|Variable type|The default value of `@var range` must be a number. This error may be thrown when parsing `number` or `range` variables.|\n|`invalidRangeMin`|Variable type|The value is smaller than the minimum value.|\n|`invalidRangeMax`|Variable type|The value is larger than the maximum value.|\n|`invalidRangeStep`|Variable type|The value is not a multiple of the step value.|\n|`invalidRangeUnits`|`[VARIABLE_TYPE, UNITS]`|The value is not a valid CSS unit.|\n|`invalidNumber`||Expect a number.|\n|`invalidSelect`||The value of `@var select` must be an array or an object.|\n|`invalidSelectValue`||The value in the array/object must be a string.|\n|`invalidSelectEmptyOptions`||The options list of `@var select` is empty.|\n|`invalidSelectLabel`||The label of the option is empty.|\n|`invalidSelectMultipleDefaults`||Multiple options are specified as the default value.|\n|`invalidSelectNameDuplicated`||Found duplicated option names.|\n|`invalidString`||Expect a string that is quoted with `'`, `\"`, or `` ` ``.|\n|`invalidURLProtocol`|Protocol of the URL|Only http and https are allowed.|\n|`invalidVersion`|Version string|https://github.com/sindresorhus/semver-regex|\n|`invalidWord`||Expect a word.|\n|`missingChar`|A list of valid characters|Expect a specific character.|\n|`missingEOT`||Expect `\u003c\u003cEOT ...` data.|\n|`missingMandatory`|A list of missing keys|This error doesn't have `err.index`.|\n|`missingValue`||Expect a non-whitespace value.|\n|`unknownJSONLiteral`|Literal value|JSON has only 3 literals: `true`, `false`, and `null`.|\n|`unknownMeta`|`[META_KEY, SUGGESTED_META_KEY]`|Unknown `@metadata`. It may suggest the correct metadata name if there is a typo. `SUGGESTED_META_KEY` can be null|\n|`unknownVarType`|`[META_KEY, VARIABLE_TYPE]`|Unknown variable type. `META_KEY` could be `var` or `advanced`.|\n\n### util\n\nA collection of parser utilities. Some of them might be useful when extending the parser.\n\n* `eatWhitespace(state)`: Move `state.lastIndex` to next non-whitespace character.\n* `parseEOT(state)`: Parse EOT multiline string used by xStyle extension.\n* `parseJSON(state)`: Parse JSON value. Note that the JSON parser can parse some additional syntax like single quoted string, backtick quoted multiline string, etc.\n* `parseNumber(state)`: Parse numbers.\n* `parseString(state)`: Parse quoted string.\n* `parseStringToEnd(state)`: Parse the text value before line feed.\n* `parseWord(state)`: Parse a word. (`[\\w-]+`)\n\n### stringify\n\n```js\nconst text = stringify(metadata: Object, options?: Object);\n```\n\nThis is a shortcut of:\n\n```js\ncreateStringifier(options).stringify(metadata);\n```\n\n### createStringifier\n\n```js\nconst stringifier = createStringifier(options?: Object);\n```\n\n`options` may contain following properties:\n\n* `alignKeys`: Boolean. Decide whether to align metadata keys. Default: `false`.\n* `space`: Number|String. Same as the `space` parameter for `JSON.stringify`.\n* `format`: String. Possible values are `'stylus'` and `'xstyle'`. This changes how variables are stringified (`@var` v.s. `@advanced`). Default: `'stylus'`.\n* `stringifyKey`: Object. Extend the stringifier to handle specified keys.\n\n  The object is a map of `key: stringifyFunction` pair. `stringifyFunction` would receive one argument:\n\n  - `value`: The value of the key, which is the same as `metadataObject[key]`.\n\n  The function should return a string or an array of strings.\n\n* `stringifyVar`: Object. Extend the stringifier to handle custom variable type.\n\n  The object is a map of `varType: stringifyFunction` pair. The function would receive three arguments:\n\n  - `variable`: The variable which should be stringified, which is the same as `metadataObject.vars[variable.name]`.\n  - `format`: The `format` parameter of the option.\n  - `space`: The `space` parameter of the option.\n\n  The function should return a string which represents the *default value* of the variable.\n\n## Related\n\n- [Stylus userstyle manager](https://github.com/openstyles/stylus) - source of most of this code\n- [usercss metadata spec](https://github.com/openstyles/stylus/wiki/UserCSS-authors)\n- [xStyle metadata spec](https://github.com/FirefoxBar/xStyle/wiki/Style-format#userless-representation) - Also supported by this parser\n\n## License\n\nMIT\n\n## Run tests\n\nThis repo includes 3 tests:\n\n* `xo` linter - which could be invoked with `xo` command.\n* `ava` test - which could be invoked with `ava` command.\n* Browser test - we currently support Chrome 49+. To run the test:\n\n  1. Run `npm run build` to build the browser dist.\n  2. Run `node browser-test` to generate browser test.\n  3. Open `browser-test.html` with a browser.\n  4. Open the console and ensure everything is OK.\n\n## Changelog\n\n* 0.12.0 (Aug 1, 2021)\n\n  - Add: detect typo in `unknownMeta` error.\n  - Change: `unknownMeta` error has two arguments now.\n\n* 0.11.0 (Jul 6, 2021)\n\n  - Change: the version validator no longer follows semver strictly. Implement your own validator if you need strict version check.\n\n* 0.10.1 (Jul 6, 2021)\n\n  - Fix: remove incompat features. Pass Chrome 49 browser test.\n\n* 0.10.0 (Nov 19, 2020)\n\n  - Fix: precision issue when validating decimals.\n  - Change: allow hyphen in key name.\n  - Change: bump node version to 8.3.0.\n\n* 0.9.0 (Nov 26, 2018)\n\n  - The repository is moved.\n  - **Change: `parseStringToEnd` now throws an error if matched nothing.**\n  - Add: `missingValue` error.\n\n* 0.8.4 (Nov 20, 2018)\n\n  - Add: support Chrome 49.\n\n* 0.8.3 (Nov 7, 2018)\n\n  - Add: `invalidSelectLabel`/`invalidSelectNameDuplicated` errors.\n  - Add: `invalidSelect`/`invalidSelectValue` errors.\n  - Add: parse number exponent.\n  - Fix: version validator doesn't match the entire string.\n  - Fix: step validator doesn't match against min/max values.\n\n* 0.8.2 (Oct 3, 2018)\n\n  - Add: `invalidRangeUnits` error.\n  - Fix: empty variable would make the parser consume the data after `\\n`.\n  - Fix: step validator is broken.\n\n* 0.8.1 (Sep 26, 2018)\n\n  - Add: attach variable type to range errors.\n\n* 0.8.0 (Sep 23, 2018)\n\n  - Bump dependencies. Move `semver-regex` to package dependencies.\n  - Change: while parsing `@advanced dropdown`, the result type would be `select`.\n  - Add: the parser/stringifier for `@var number` and `@var range`.\n  - Add: parser method `parser.validateVar`.\n  - Add: now `parseNumber` and `parseJSON` accept decimals without leading zeros e.g. `.5` like CSS.\n  - Add: asterisk syntax in `@var select`.\n  - Add: `validateKey` and `validateVar` arguments to `createParser`.\n  - Fix: when stringifying `@var select` in xstyle format, it should produce `@advanced dropdown` instead of `@advanced select`.\n  - Fix: should throw an error with `@var dropdown`.\n  - Fix: don't assign `advanced` key to metadata object.\n\n* 0.7.1 (Sep 9, 2018)\n\n  - **Breaking: the return value of `parser.parse` is changed.**\n  - **Breaking: the signature of `ParseError` is changed.**\n  - Add: `createParser` now accepts `allowErrors` arg.\n  - Change: some error messages are changed.\n\n* 0.6.1 (Jul 22, 2018)\n\n  - Fix: `stringify` would throw if the value is number instead of string.\n\n* 0.6.0 (Jul 13, 2018)\n\n  - **Change: the `url` module is shimmed with `self.URL` by using `pkg.browser`.**\n  - Fix: stringify multi-line description.\n\n* 0.5.0 (May 16, 2018)\n\n  - **Change: the ParseResult object doesn't contain `vars` key if there is no variable in the input.**\n  - Fix: `var` key is accidentally assigned to ParseResult object.\n\n* 0.4.0 (May 9, 2018)\n\n  - Rewrite the parser, cleanup unused stuff.\n  - Add stringify feature.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenstyles%2Fusercss-meta","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenstyles%2Fusercss-meta","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenstyles%2Fusercss-meta/lists"}