{"id":18950062,"url":"https://github.com/salesforce/omakase","last_synced_at":"2025-04-15T23:31:41.953Z","repository":{"id":45533130,"uuid":"48444369","full_name":"salesforce/omakase","owner":"salesforce","description":"Java-based, plugin-oriented CSS3+ parser","archived":false,"fork":false,"pushed_at":"2023-11-29T21:47:39.000Z","size":22258,"stargazers_count":15,"open_issues_count":3,"forks_count":12,"subscribers_count":17,"default_branch":"master","last_synced_at":"2024-04-08T00:13:08.193Z","etag":null,"topics":["ast","css","css-parser","java","salesforce"],"latest_commit_sha":null,"homepage":"","language":"CSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/salesforce.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":"contributing/intellij/Omakase Code Style.xml","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null}},"created_at":"2015-12-22T17:11:24.000Z","updated_at":"2024-01-12T18:06:46.000Z","dependencies_parsed_at":"2023-01-23T03:45:19.265Z","dependency_job_id":null,"html_url":"https://github.com/salesforce/omakase","commit_stats":{"total_commits":526,"total_committers":11,"mean_commits":47.81818181818182,"dds":"0.19961977186311786","last_synced_commit":"991f92a05f68325afe0da770e7363ec94d9c5a21"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce%2Fomakase","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce%2Fomakase/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce%2Fomakase/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salesforce%2Fomakase/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salesforce","download_url":"https://codeload.github.com/salesforce/omakase/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223689764,"owners_count":17186497,"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":["ast","css","css-parser","java","salesforce"],"created_at":"2024-11-08T13:20:32.123Z","updated_at":"2024-11-08T13:20:32.778Z","avatar_url":"https://github.com/salesforce.png","language":"CSS","funding_links":[],"categories":[],"sub_categories":[],"readme":"Omakase\n=======\n\n[![Build](https://github.com/salesforce/omakase/workflows/Java%20CI/badge.svg)](https://github.com/salesforce/omakase/actions)\n[![codecov](https://codecov.io/gh/salesforce/omakase/branch/master/graph/badge.svg)](https://codecov.io/gh/salesforce/omakase)\n[![Maven Central](https://img.shields.io/maven-central/v/com.salesforce/omakase.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.salesforce%22%20AND%20a:%22omakase%22)\n[![javadoc](https://javadoc.io/badge2/com.salesforce/omakase/javadoc.svg)](https://javadoc.io/doc/com.salesforce/omakase)\n[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)\n\nFast, Java-based, plugin-oriented CSS3+ parser.\n\nOmakase (お任せ o-_ma_-ka-say) has very few dependencies and doesn't need to execute Ruby or JavaScript code. It runs entirely in Java.\n\nFeatures\n--------\n\nOmakase can work as a parser, preprocessor, linter, minifier or all four. It gives you an AST that can be modified and validated.\n\n### Super fast\nOmakase is engineered for speed. Compared to other Java-based, modern CSS parsers, Omakase parses ~300 lines of code up to 3x faster and ~1000 lines up to 5x faster. For very large files (over 10k lines) Omakase can potentially save hundreds of ms.\n\n### Flexible\nOmakase is plugin-oriented, which means you can create plugins to modify, add, remove, validate or lint any aspect of the CSS.\n\nPlugins can also be used to extend the CSS grammar and syntax. This allows you to create common features like variables,  mixins and nesting using whatever format you like. In fact much of the built-in functionality uses plugins.\n\n### Awesome bundled plugins\nOmakase bundles several plugins, most notably:\n\n- [automatic vendor prefixing](#prefixer) (supported by [caniuse.com](https://github.com/Fyrd/caniuse))\n- [conditional blocks](#conditionals)\n- [RTL direction flipping](#directionflip)\n\n### Better error messaging\nOmakase is built 100% solely for parsing CSS, which means that the error messages are often more specific and easier to understand than from other parsers created from generic parser generators.\n\n### Easier programmatic usage\nOmakase is focused on runtime usage, and provides special features to make runtime parsing even faster, namely a 2-level parsing strategy. The first level separates the source into selectors, declarations and at-rules only. The (optional) second level breaks each unit down further, for example into class selectors, type selectors, id selectors, etc... This allows you to do a full pass during build time, but during runtime only parse the aspects of CSS that need to be reworked. This speed improvement is on top of the general performance numbers mentioned above.\n\nYou can also parse snippets of CSS on-the-fly, such as a single selector or declaration value.\n\n[Snapshot Javadocs](http://opensource.salesforce.com/omakase/ \"Generated from master\")\n\nHow to install\n--------------\n\nOmakase is hosted on Maven Central. You can add it as a Maven dependency in your pom.xml:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.salesforce\u003c/groupId\u003e\n  \u003cartifactId\u003eomakase\u003c/artifactId\u003e\n  \u003cversion\u003e1.4.4\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nBe sure to use the latest version, which is displayed at the top of this readme.\n\nYou can alternatively build jars from source by cloning this project locally and running `mvn install` from the project root (must have Java and Maven installed). Then use the jars as appropriate for your project.\n\nUsage\n-----\n\nAll parsing starts with the [`Omakase`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/Omakase.html) class. The CSS source is specified using the `#source(CharSequence)` method, optional plugins are then registered, and then parsing is performed with a call to `#process()`. An example of the most basic form of parsing is as follows:\n\n```java\nOmakase.source(input).process();\n```\n\nYou will almost always include one or more plugins though. Plugins are used for output/minification, automatic vendor prefixing, modifications to the AST, custom linting, and more.\n\nUnless you are specifically optimizing for performance, you should at least add the [`StandardValidation`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/core/StandardValidation.html) plugin, and it should be last, after other plugins.\n\nNote that only one instance of a plugin can be registered per parsing operation.\n\n### Output\n\nUse the [`StyleWriter`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/writer/StyleWriter.html) plugin to write the processed CSS:\n\n```java\nStyleWriter verbose = StyleWriter.verbose();\nStandardValidation validation = new StandardValidation();\nOmakase.source(input).use(verbose).use(validation).process();\nString out = verbose.write();\n```\n\nYou can also write to an `Appendable`\n\n```java\nStyleWriter verbose = StyleWriter.verbose();\nStandardValidation validation = new StandardValidation();\nOmakase.source(input).use(verbose).use(validation).process();\nStringBuilder builder = new StringBuilder();\nString out = verbose.writeTo(builder);\n```\n\nBy default, CSS is written out in _inline_ mode. Other available modes include _verbose_ and _compressed_. Verbose mode will output newlines, spaces, comments, etc... Inline mode will write each rule on a single line. Compressed mode will eliminate as many characters as possible, including newlines, spaces, etc...\n\n```java\nStyleWriter verbose = StyleWriter.verbose();\nStyleWriter inline = StyleWriter.inline();\nStyleWriter compressed = StyleWriter.compressed();\n```\n\nIn some cases you may want to write out an individual, stand-alone syntax unit:\n\n```java\nDeclaration declaration = new Declaration(Property.DISPLAY, KeywordValue.of(Keyword.NONE));\nString output = StyleWriter.inline().writeSingle(declaration);\n```\n\nYou can also override how any individual syntax unit is written. For more information see the [Custom writers](#custom-writers) section below.\n\n### Validation\n\nIn Omakase, _validation_ refers to both actual syntax validation (e.g., that the arguments to an `rgba` function are well-formed) as well as what is commonly known as _linting_ (e.g., that fonts are specified using relative units instead of pixels).\n\nAll validation is written and registered as plugins. To enable the standard validations, register an instance of the [`StandardValidation`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/core/StandardValidation.html) plugin:\n\n```java\nOmakase.source(input).use(new StandardValidation()).process();\n```\n\nThis auto-refines every selector, declaration and at-rule (see the [AutoRefine](#autorefine) section below for more information on refinement) and registers the standard list of built-in validators.\n\nKeep in mind that validation methods will always be invoked after rework methods, but otherwise they will be executed in the order that the plugin class was registered.\n\nYou can also add your own custom validators. For examples see the [Custom validation](#custom-validation) section below.\n\nNote that basic CSS grammar is verified independently of any plugins as part of the core parser.\n\n### Registering plugins\n\nWhen registering plugins there are important details to keep in mind:\n\n- Only one instance of a plugin can be registered.\n- Subscription methods will be executed in the order that its plugin class was registered.\n- All [`@Rework`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/broadcast/annotation/Rework.html) subscription methods will be executed before [`@Validate`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/broadcast/annotation/Validate.html), regardless of the order in which the plugins were registered. Essentially this means validation always happens after rework modification is fully completed.\n\n### Bundled plugins\n\n#### SyntaxTree\n\nThe [`SyntaxTree`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/core/SyntaxTree.html) plugin is an extremely simple plugin that only grabs and stores a reference to the parsed `Stylesheet` object. It's an easy way for you to get access to the `Stylesheet` object without having to write a custom plugin.\n\n```java\nSyntaxTree tree = new SyntaxTree();\nOmakase.source(input).use(tree).process();\nStylesheet stylesheet = tree.stylesheet();\nSystem.out.println(\"#statements = \" + stylesheet.statements().size());\n```\n\n#### AutoRefine\n\nThe [`AutoRefine`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/core/AutoRefine.html) plugin is responsible for automatically refining all or certain [`Refinable`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/ast/Refinable.html) objects. Currently this includes `Selector`, `Declaration`, `RawFunction` and `AtRule`. _Refinement_ refers to the process of taking a generic syntax string (e.g., \".class \u003e #id\") and parsing out the individuals units (e.g., `ClassSelector`, `Combinator`, `IdSelector`).\n\nUnless refinement occurs the syntax object may contain invalid CSS. For example a `Selector` may contain raw content consisting of \".class~!8391\", but no errors will be thrown until it is refined. The `AutoRefine` plugin can do this automatically:\n\n```java\nAutoRefine all = AutoRefine.everything()); // refine everything\nAutoRefiner selectors = AutoRefine.only(Match.SELECTORS); // refine selectors\nAutoRefiner declarations = AutoRefine.only(Match.DECLARATIONS); // refine declarations\n```\n\nUsing `StandardValidation` automatically includes `AutoRefine` unless otherwise configured.\n\nYou may be wondering when you *wouldn't* want auto refinement. The main use-case is during runtime or other performance-sensitive environments.\n\nYou first parse the CSS with full refinement. This ensures you actually have valid CSS. You can store this preprocessed source code in memory or on the filesystem. During runtime, you can parse the CSS again to perform dynamic substitutions. However this time, since you have already ensured that the CSS is valid, there is no need to parse more than what is necessary to perform the dynamic substitions. You can simply refine only those selectors or declarations that you need and nothing more. This will result in faster parsing performance. For more information on this see the section on [conditional refinement](#conditional-refinement).\n\n#### Conditionals\n\nConditionals allow you to vary the CSS output based on specific *true conditions*. Here's an example:\n\n```css\n.button {\n  background: linear-gradient(#aaa, #ddd);\n}\n\n@if (ie7) {\n  .button {\n    background: #aaa;\n  }\n}\n```\n\nIf \"ie7\" is passed in as a *true condition* then the block will be retained, otherwise it will be removed.\n\nTo use conditionals, register the [`Conditionals`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/conditionals/Conditionals.html) plugin:\n\n```java\nConditionals conditionals = new Conditionals(\"ie7\");\nOmakase.source(input).use(conditionals).process();\n```\n\nYou can manage the set of *true conditions* to print out variations of the CSS:\n\n```java\nConditionals conditionals = new Conditionals();\nStyleWriter writer = StyleWriter.compressed();\nOmakase.source(input).use(conditionals).use(writer).process();\n\n// ie7\nconditionals.config().replaceTrueConditions(\"ie7\");\nString ie7 = writer.write();\n\n// firefox\nconditionals.config().replaceTrueConditions(\"firefox\");\nString firefox = writer.write();\n\n// chrome\nconditionals.config().replaceTrueConditions(\"webkit\", \"chrome\");\nString chrome = writer.write();\n```\nYou can also use logical negation and logical or operators in the CSS:\n\n```css\n@if (!ie7) {\n  .button {\n    display: inline-block;\n  }\n}\n\n@if (ie8 || ie9 || ie10) {\n  .button {                                                                           \n    margin: 7px;\n  }\n}\n```\n\nFinally, if you would like to enable conditionals and validate them but hold off on actually evaluating them, you can specify `passthroughMode` as true:\n\n```java\nnew Conditionals(\"ie7\").config().passthroughMode(true);\nnew Conditionals(true).config().addTrueConditions(\"ie7\"); // same as above\n```\n\nOf course, any string can be used and referred to as a *true condition*, not just browsers. Finally, note that the default behavior is to automatically convert all conditions found in the input as well as the specified *true conditions* to **lower-case**. Thus, usage is not case-dependent.\n\n##### Conditionals Collector\n\nThe [`ConditionalsCollector`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/conditionals/ConditionalsCollector.html) plugin can be used when you need to know what conditions were actually used in the input CSS, say, to determine what CSS variations you need to write.\n\n```java\nStyleWriter writer = StyleWriter.compressed();\nConditionals conditionals = new Conditionals();\nConditionalsCollector collector = new ConditionalsCollector();\nOmakase.source(input).use(conditionals).use(collector).use(writer).process();\n\nMap\u003cString, String\u003e variations = new HashMap\u003cString, String\u003e();\nvariations.put(\"default\", writer.write());\n\nfor (String condition : collector.foundConditions()) {\n    conditionals.config().replaceTrueConditions(condition);\n    variations.put(condition, writer.write());\n}\n```\n\nNote that `ConditionalsCollector` automatically registers an instance of the `Conditionals` plugin as well. If you are explicitly adding the `Conditionals` plugin, it must be registered *before* the `ConditionalsCollector` instance.\n\n##### Conditionals Validator\n\nThe [`ConditionalsValidator`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/conditionals/ConditionalsValidator.html) plugin can be used to ensure only certain conditions can be used in the CSS:\n\n```java\nConditionalsValidator validation = new ConditionalsValidator(\"ie8\", \"ie9\", \"ie10\", \"chrome\", \"firefox\");\nOmakase.source(input).use(validation).process();\n```\n\nNote that `ConditionalsValidator` automatically registers an instance of the `Conditionals` plugin as well. If you are explicitly adding the `Conditionals` plugin, it must be registered *before* the `ConditionalsValidator` instance.\n\n#### UnquotedIEFilterPlugin\n\nIf you are in the unfortunate situation of using crappy legacy IE filters then the `UnquotedIEFilterPlugin` must be registered, otherwise syntax errors will occur.\n\n```java\nUnquotedIEFilterPlugin ieFilters = new UnquotedIEFilterPlugin();\nOmakase.source(input).use(ieFilters).process();\n```\n\nNote that *quoted* IE filters do not require this plugin. An example of an unquoted IE filter:\n\n```css\nfilter: progid:DXImageTransform.Microsoft.Shadow(color='#969696', Direction=145, Strength=3);\n```\n\ncompared to quoted:\n\n```css\n-ms-filter: \"progid:DXImageTransform.Microsoft.Shadow(color='#969696', Direction=145, Strength=3)\";\n```\n\nThis plugin must be registered before `StandardValidation` or `AutoRefine`.\n\n#### Prefixer\n\nThe [`Prefixer`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/prefixer/Prefixer.html) plugin enables automatic vendor prefixing. It will analyze all prefixable selectors, properties, at-rules\nfunction and keyword names, and automatically prepend prefixed-equivalents based on the specified level of browser support. For example:\n\n```css\n.class {\n  border-radius: 3px;\n}\n```\n\ngets transformed into:\n\n```css\n.class {\n  -webkit-border-radius: 3px;\n  -moz-border-radius: 3px;\n  border-radius: 3px;\n}\n```\n\nThe `Prefixer` plugin determines which prefixes to actually use based on whether the browsers you'd like to support actually need them. It maintains a list of every browser version and which properties they require prefixed to accomplish this. The data is retrieved from the famous [caniuse.com](http://caniuse.com) website [database](https://github.com/fyrd/caniuse).\n\nHere is how you would register the `Prefixer` plugin using the default browser level support:\n\n```java\nPrefixer prefixer = Prefixer.defaultBrowserSupport();\nOmakase.source(input).use(prefixer).process();\n```\n\nThe default browser version support includes the last six versions of iOS Safari, last five versions of Chrome and Firefox, last\nthree of Android, IE 7+, and the latest versions of Safari, IE Mobile and Opera Mini.\n\nYou can specify an alternative set of browsers to support as well:\n\n```java\nPrefixer prefixing = Prefixer.customBrowserSupport();\nprefixing.support().all(Browser.IE);\nprefixing.support().latest(Browser.FIREFOX);\nprefixing.support().browser(Browser.SAFARI, 6.1);\nprefixing.support().last(Browser.SAFARI, 2);\nOmakase.source(input).use(prefixing).process();\n```\n\nThis is cumulative, so you can also add extra support to the defaults instead.\n\nTo manually update the prefix data, see the [Scripts](#scripts) section below. Updating is a one-line shell command, and after an update the processed CSS automatically reflects any changes right away. This can be more efficient than using a mixin to handle vendor prefixes, as you would have to constantly check each prefixable property, selector, etc... to see if a prefix is still required.\n\nNote that the `Prefixer` plugin will **not** trigger _refinement_ of a selector, declaration or at-rule just to check if a prefix is needed. This means you need to register `AutoRefine` (or `StandardValidation`) if you would like all selectors, declarations, etc... to be considered. See the [AutoRefine](#autorefine) section above for more information.\n\n##### Pruning\n\nThe `Prefixer` plugin works well with existing CSS that is already littered with various vendor prefixes. By default, if a prefix is already present then it will be preserved as-is. That is, a duplicate prefix will not be added. This allows you to turn on and use the plugin right away without having to clean up your CSS file. It can even be a way for you to specify a value for the prefixed declaration that differs from the unprefixed one.\n\nOn the other hand, in many cases it will be more performant to actually have all unnecessary prefixes removed. You can do this with the `prune` method.\n\nFor example, take the following:\n\n```css\n.class {\n    -webkit-border-radius: 3px;\n    -moz-border-radius: 3px;\n    -ms-border-radius: 3px;\n    -o-border-radius: 3px;\n    border-radius: 3px;\n}\n```\n\nMany people still have CSS that looks like this, without realizing that border-radius has not required a prefix for `webkit` or `moz` for a very long time, and the `ms` and `o` prefixes were never actually needed at all.\n\nTurn on pruning to have these prefixes automatically removed:\n\n```java\nPrefixer prefixer = Prefixer.defaultBrowserSupport().prune(true);\n```\n\nThen go back and update your CSS files to remove them as well at your leisure.\n\n##### Rearranging\n\nSimilarly, sometimes people end up with CSS that looks like this:\n\n```css\n.class {\n    -webkit-border-radius: 3px;\n    border-radius: 3px;\n    -moz-border-radius: 3px;\n}\n```\n\nIt's always best practice for the unprefixed version to be *last*, that way if the browser supports the property unprefixed that is the one it uses. You can turn on rearranging to ensure that any prefixes that exist in the source file are moved around to make this true:\n\n```java\nPrefixer prefixer = Prefixer.defaultBrowserSupport().rearrange(true);\n```\n\n##### Prefixer Support List\n\nHere's a list of what's currently supported:\n\n    At Rule\n    ----------------------------\n    keyframes\n\n    Selector\n    ----------------------------\n    placeholder\n    selection\n\n    Property\n    ----------------------------\n    align-content\n    align-items\n    align-self\n    animation\n    animation-delay\n    animation-direction\n    animation-duration\n    animation-fill-mode\n    animation-iteration-count\n    animation-name\n    animation-play-state\n    animation-timing-function\n    appearance\n    backface-visibility\n    background-clip\n    background-origin\n    background-size\n    border-bottom-left-radius\n    border-bottom-right-radius\n    border-image\n    border-image-outset\n    border-image-repeat\n    border-image-slice\n    border-image-source\n    border-image-width\n    border-radius\n    border-top-left-radius\n    border-top-right-radius\n    box-shadow\n    box-sizing\n    column-count\n    column-fill\n    column-gap\n    column-rule\n    column-rule-color\n    column-rule-style\n    column-rule-width\n    column-span\n    column-width\n    columns\n    flex\n    flex-basis\n    flex-direction\n    flex-flow\n    flex-grow\n    flex-shrink\n    flex-wrap\n    hyphens\n    justify-content\n    order\n    perspective\n    perspective-origin\n    tab-size\n    transform\n    transform-origin\n    transform-style\n    transition\n    transition-delay\n    transition-duration\n    transition-property\n    transition-timing-function\n    user-select\n\n    Keyword\n    ----------------------------\n    flex\n    inline-flex\n\n    Function\n    ----------------------------\n    calc\n    linear-gradient\n    repeating-linear-gradient\n\n\nYou can view this yourself from the command line, as well as which of these will actually be auto-prefixed by default, by using the `omakase --prefixed-all` command. See the [Scripts](#scripts) section below.\n\n#### PrefixCleaner\n\nIt's usually a good idea to add the `PrefixCleaner` plugin after the `Prefixer` plugin:\n\n```java\nPrefixer prefixer = Prefixer.defaultBrowserSupport();\nPrefixCleaner cleaner = PrefixCleaner.mismatchedPrefixedUnits();\nOmakase.source(input).use(prefixer).use(cleaner).process();\n```\n\nThis will remove prefixed declarations inside of prefixed at rules, where the declaration's prefix doesn't match the at-rule's prefix.\n\n#### DirectionFlip\n\nThis plugin will handle flipping certain property names and values from left-to-right to right-to-left.\n\n```java\nDirectionFlipPlugin rtl = new DirectionFlipPlugin();\nOmakase.source(source).use(rtl).process();\n```\n\nIt flips property names such as `left` and `border-left` to `right` and `border-right`. It also rearranges certain values like padding, margin and border-radius shorthand so that the right and left units are swapped.\n\nIf you need to prevent certain declarations from being flipped, you can use a CSS annotation like so:\n\n```css\n.button {\n  /* @noflip */ padding-left: 10px;\n}\n```\n\nTake note of the [CSS annotation format](#css-annotations). For example, if you use two asterisks to start the comment block instead of one then it will not be recognized.\n\n### Creating custom plugins\n\nIn addition to the standard library plugins, you can create and register your own custom plugins. Custom plugins allow you to rework the processed CSS or add your own custom validation and linting rules. You can also use plugins to extend the CSS syntax and grammar.\n\nPlugins are essentially plain java objects that implement one of the _plugin interfaces_ and define one or more _subscription methods_ to a particular AST object (e.g., `Selector` or `Declaration`). The subscription method does the actual rework or validation as appropriate.\n\nPlugins are registered exactly the same as as any of the standard built-in plugins such as `StyleWriter` or `AutoRefine`.\n\nSee the [Subscribable Syntax Units](#subscribable-syntax-units) section below for the definitive list of all subscribable AST objects.\n\n#### Plugin interfaces\n\nTo get started, a plugin must first implement one or more of the _plugin interfaces_, listed as follows:\n\n- [**Plugin**](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/Plugin.html) - the basic plugin.\n- [**DependentPlugin**](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/DependentPlugin.html) - for plugins that have dependencies on other plugins.\n- [**GrammarPlugin**](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/GrammarPlugin.html) - for plugins that customize syntax and grammar.\n- [**ParserPlugin**](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/ParserPlugin.html) - for plugins customize individual parser behavior.\n- [**PostProcessingPlugin**](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/PostProcessingPlugin.html) for plugins that need notification after all processing has completed.\n\nMost plugins will implement just the `Plugin` or `DependentPlugin` interface.\n\n#### Custom rework\n\nThe term _rework_ refers to the process of changing the processed CSS source code, e.g., changing a class name, adding a declaration, removing a rule, etc... Omakase allows you to easily change nearly any aspect of the CSS. You can add, change or remove selectors, add, change or remove declarations, add, change or remove rules, and... you get the picture.\n\nEach AST object contains setters and getters for working with its values. In addition there are several utility classes.\n\nHere is an example of a simple rework operation to prefix every selector with a class name called \"myPrefix\":\n\n```java\npublic class PrefixAllSelectors implements Plugin {\n    @Rework\n    public void prefixClass(Selector selector) {\n        // create a new simple selector\n        ClassSelector prefix = new ClassSelector(\"myPrefix\");\n\n        // prepend the simple selector to the beginning of the selector\n        selector.parts().prepend(prefix);\n    }\n}\n```\n\nNotice the following details:\n\n- It implements `Plugin`\n- It has one subscription method with the `@Rework` annotation\n- The subscription method has one argument, which is the AST object to be reworked\n\nFor more advanced examples on performing rework see the [ReworkTest.java](src/test/java/com/salesforce/omakase/test/functional/ReworkTest.java) class.\n\n##### Dynamic AST creation and modification\n\nHere are some code examples illustrating some common operations during rework:\n\n```java\n// create a new selector\nSelector selector = new Selector(new ClassSelector(\".test\"));\n\n// another example\nIdSelector theId = new IdSelector(\"main\");\nClassSelector theClass = new ClassSelector(\"inner\");\nSelector selector = new Selector(theId, Combinator.descendant(), theClass);\n\n// append a simple selector part to an existing selector\nselector.parts().append(new ClassSelector(\"another-class\"));\n\n// append a selector to a rule\nrule.selectors().append(myNewSelector);\n\n// alternative to above\nsomeSelector.append(myNewSelector);\n\n// change a value on a class selector\nmyClassSelector.name(\"the-new-name\");\n\n// check if a selector has a particular (class|id|type) simple selector\nif (Selectors.hasClassSelector(selector, \"myClass\")) {...}\nif (Selectors.hasIdSelector(selector, \"myId\")) {...}\nif (Selectors.hasTypeSelector(selector, \"div\")) {...}\n\n// find the first matching (class|id|type) simple selector\nOptional\u003cClassSelector\u003e button = Selectors.findClassSelector(selector, \"button\");\nif (button.isPresent()) {\n    System.out.println(button.get());\n}\n\n// check if a 'div' type selector is adjoined to a class selector (e.g., \"div.selector1\")\nif(Selectors.hasTypeSelector(Selectors.adjoining(selector1), \"div\")) {...}\n\n// create a new declaration\nDeclaration declaration = new Declaration(Property.DISPLAY, KeywordValue.of(Keyword.NONE));\n\n// another example\nPropertyName name = PropertyName.of(\"new-prop\");\nDeclaration declaration = new Declaration(name, KeywordValue.of(\"blah\"));\n\n// another example\nNumericalValue val1 = NumericalValue.of(1, \"px\");\nNumericalValue val2 = NumericalValue.of(5, \"px\");\nPropertyValue value = PropertyValue.ofTerms(OperatorType.SPACE, val1, val2);\nDeclaration declaration = new Declaration(Property.MARGIN, value);\n\n// another example\nPropertyName prop = PropertyName.of(Property.BORDER_RADIUS).prefix(Prefix.WEBKIT);\nDeclaration declaration = new Declaration(prop, KeywordValue.of(Keyword.NONE));\n\n// append a declaration to a rule\nrule.declarations().append(myNewDeclaration);\n\n// create a new rule\nRule rule = new Rule();\nrule.selectors().append(new Selector(new ClassSelector(\"new\")));\nrule.declarations().append(new Declaration(Property.COLOR, HexColorValue.of(\"#fff\")));\nrule.declarations().append(new Declaration(Property.FONT_SIZE, NumericalValue.of(1.5).unit(\"em\")));\n\n// check if a declaration is for a specific property name\nif (declaration.isProperty(Property.DISPLAY)) {...}\nif (declaration.isProperty(\"new-prop\")) {...}\n\n// check if a declaration has a value\nPropertyValue value = declaration.propertyValue();\nOptional\u003cKeywordValue\u003e keyword = Values.asKeyword(value);\nif (keyword.isPresent()) {...}\n\n// use the method appropriate to what value you think it is (say, based on the property name)\nOptional\u003cHexColorValue\u003e color = Values.asHexColor(value);\nOptional\u003cNumericalValue\u003e number = Values.asNumerical(value);\nOptional\u003cStringValue\u003e string = Values.asString(value);\n\n// change a property value\nOptional\u003cKeywordValue\u003e keyword = Values.asKeyword(declaration.propertyValue());\nif (keyword.isPresent()) {\n    keyword.get().keyword(Keyword.NONE);\n}\n\n// remove (destroy) a selector, declaration, rule, etc...\nsomeSelector.destroy();\nsomeDeclaration.destroy();\nsomeRule.destroy();\n\n// for more examples see the many unit tests\n```\n\nKeep in mind that dynamically created units will be automatically delivered to all `@Rework` subscription methods interested in the syntax unit's type, as well as to `@Validate` subscriptions later on. Thus, dynamically created CSS is fully integrated with all of your custom rework and validation plugins.\n\nYou can remove any unit from the tree by calling `#destroy`. Note that doing this will prevent that unit from being delivered to any subsequent subscription methods. Destroyed units cannot be added back to the tree, but they can be cloned with `#copy`.\n\nThere are other utilities for working with units in the following utility classes:\n\n- [Selectors.java](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/util/Selectors.html)\n- [Declarations.java](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/util/Declarations.html)\n- [Values.java](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/util/Values.html)\n- [Parsers.java](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/util/Parsers.html)\n- [Args.java](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/util/Args.html)\n- [Others](https://opensource.salesforce.com/omakase/com/salesforce/omakase/util/package-summary.html)\n\n#### Custom validation\n\nBesides rework, you can also register subscription methods to perform validation and linting. Just like rework, you declare a method with the first parameter being the type of syntax unit you would like to validate. In addition there is a second parameter which is the [`ErrorManager`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/error/ErrorManager.html) used to report any problems.\n\nHere is an example of a class with two validation subscription methods:\n\n```java\npublic class Validations implements Plugin {\n    @Validate\n    public void validateRelativeFonts(Declaration declaration, ErrorManager em) {\n        if (!declaration.isProperty(Property.FONT_SIZE)) return; // only process font-size properties\n\n        Optional\u003cNumericalValue\u003e number = Values.asNumerical(declaration.propertyValue());\n\n        if (number.isPresent() \u0026\u0026 number.get().unit().isPresent() \u0026\u0026 number.get().unit().get().equals(\"px\")) {\n            em.report(ErrorLevel.FATAL, declaration, \"Font sizes must use relative units\");\n        }\n    }\n\n    @Validate\n    public void validateIncludesUnprefixed(Declaration declaration, ErrorManager em) {\n        // check that all prefixed declarations include an unprefixed declaration as well.\n\n        PropertyName propertyName = declaration.propertyName();\n\n        if (propertyName.isPrefixed()) {\n            boolean found = false;\n            String expected = propertyName.unprefixed();\n\n            // go through each declaration in the block, looking for one with the unprefixed name\n            for (Declaration d : declaration.group()) {\n                if (d.isProperty(expected)) {\n                    found = true;\n                    break;\n                }\n            }\n\n            if (!found) {\n                em.report(ErrorLevel.WARNING, declaration, \"Prefixed declaration without unprefixed version\");\n            }\n        }\n    }\n}\n```\n\nKeep in mind that all validators run after the rework phase has been completed. Validation methods should **not** change any aspect or content of the objects they are validating. They should only check values and reports errors as applicable.\n\n#### Dependent plugins\n\nAs mentioned above, many plugins, especially ones with `@Rework`, will need to register dependencies on other plugins. Here is an example of a plugin with dependencies:\n\n```java\npublic class Dependent implements DependentPlugin {\n    @Override\n    public void dependencies(PluginRegistry registry) {\n        registry.require(SelectorPlugin.class);\n        registry.require(MyPlugin.class, MyPlugin::new);\n    }\n}\n```\n\nThe [`#require`](https://opensource.salesforce.com/omakase/com/salesforce/omakase/PluginRegistry.html#require-java.lang.Class-) method takes the class of the plugin. If the plugin is already registered then the registered instance is simply returned. Otherwise one is automatically created and added to the registry. You can then proceed to configure the plugin as necessary for your use case.\n\nYou can also require your own custom plugins by using the [`#require(Class, Supplier)`](https://opensource.salesforce.com/omakase/com/salesforce/omakase/PluginRegistry.html#require-java.lang.Class-java.util.function.Supplier-) method.\n\n#### Performing both rework and validation\n\nNote that any particular plugin can have as many [`@Rework`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/broadcast/annotation/Rework.html) and [`@Validate`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/broadcast/annotation/Validate.html) annotated methods as it needs. That is, rework and validation do not need to be separated out in to multiple classes.\n\nYou can also subscribe to the exact same syntax type in multiple methods. However there is no guarantee to the execution order of subscription methods to the exact same syntax unit type for the exact same operation (rework or validate). This means, for example, that if two `@Rework` methods subscribed to `ClassSelector` are needed, and that execution order is important, then these methods should be separated out into their own classes. The classes should then be registered in the intended execution order.\n\nYou can also register anonymous inner classes as plugins too:\n\n```java\nOmakase.source(input)\n    .add(new StandardValidation())\n    .add(new Plugin() {\n        @Rework\n        public void rework(Declaration d) {\n            ...\n        }\n    })\n    .add(new Plugin() {\n        @Validate\n        public void validate(Declaration d, ErrorManager em) {\n            ...\n        }\n    })\n    .process();\n```\n\n#### Subscribing to interfaces\n\nNot only can you subscribe to concrete types such as `ClassSelector` and `IdSelector`, you can also subscribe to higher-level interfaces such as `Statement`, `SimpleSelector` or even the top-level `Syntax` interface.\n\nSubscribing to an interface type will allow you to receive all instances of that type, which can be useful in certain scenarios.\n\nWithin a particular class, the more specifically-typed subscription will be delivered before the more generally-typed subscriptions. For example, in a class with subscriptions to `ClassSelector`, `SimpleSelector` and `Syntax`, the methods will always be invoked in that exact order.\n\nSee the [Subscribable Syntax Units](#subscribable-syntax-units) section below for the definitive list of all subscribable AST objects.\n\n#### Observe\n\nBesides `@Rework` and `@Validate`, there is one another annotation that can be used to make a subscription method.\n\n`@Observe` can be used in place of `@Rework` when your intention is to simply utilize information from the AST object and you do not intend to make any changes. In terms of execution order, `@Observe` and `@Rework` are equivalent. Currently the only difference `@Observe` makes is providing a better description of what the method intends to do.\n\n#### Extending the CSS syntax\n\nOmakase provides a powerful mechanism for extending the standard CSS syntax. You can easily augment CSS with your own:\n\n- Custom functions\n- Custom at-rules\n- Custom selectors\n- Custom declarations\n\nUsing the [`@Refine`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/broadcast/annotation/Refine.html) annotation, you can subscribe to any `Refinable` syntax unit:\n\n```java\n@Refine\npublic void refine(RawFunction function, Grammar grammar, Broadcaster broadcaster) {\n}\n```\n\n`@Refine` methods must specify three parameters. The first is the unit to refine. The second is of type [`Grammar`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/parser/Grammar.html), which should be used to access internal parsers and tokens. The third is of type [`Broadcaster`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/broadcast/Broadcaster.html), which should be used to _broadcast_ refined syntax units.\n\nNote that after a subscription method handles a unit it will not be sent to subsequent refiners, so plugins should be registered in appropriate order.\n\nYou can optionally specify a string to the `@Refine` annotation, and only units that match that name will be delivered. Here is an example of a custom function:\n\n```java\n@Refine(\"myFunction\")\npublic void refine(RawFunction function, Grammar grammar, Broadcaster broadcaster) {\n  String args = function.args().trim();\n\n  // parse arguments, e.g., lookup a value, perform calculations...\n  // ...\n\n  // turn our parsed string into actual terms\n  Source source = new Source(parsedArgs);\n  grammar.parser().termSequenceParser().parse(source, grammar, broadcaster);\n}\n```\n\nYou can utilize the [`Source`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/parser/Source.html) class as a parsing utility, and nearly all of the library parsing functionality can be used standalone. This includes parsing rules, declarations, selectors, and even specific selectors like a class selector. Utilize the methods on the [`Grammar`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/parser/Grammar.html) instance and the [`Parsers`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/util/Parsers.html) class.\n\nFor a full example of a [`RawFunction`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/ast/RawFunction.html) refiner see [`UrlPlugin`](src/main/java/com/salesforce/omakase/plugin/syntax/UrlPlugin.java). For a full example of an `AtRule` refiner see [`Conditionals`](src/main/java/com/salesforce/omakase/plugin/conditionals) plugin and related classes. For more detailed examples see the [test samples](src/test/java/com/salesforce/omakase/sample/custom/).\n\nNote that generally speaking, by simply utilizing an internal parser, all parsed units will be automatically broadcasted to the given broadcaster. This means that a custom function could simply parse a string for terms and operators using the term sequence parser and all encountered terms and operators will be automatically added to the declaration that the custom function is in, no further work required. To avoid this, just use your own broadcaster instance instead of passing through the one given to you.\n\n### Conditional Refinement\n\nAs mentioned above, most of the time you want to include the `StandardValidation` or `AutoRefine` plugins to ensure that every AST object is refined and delivered to subscription methods. The alternative is to conditionally refine only the units that are necessary.\n\nThe easiest way to do this is with [`AutoRefine`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/core/AutoRefine.html), where you can specify to only refine selectors, declarations, etc:\n\n```java\n// skip refinement of selectors\nAutoRefine.only(Match.FUNCTIONS, Match.DECLARATIONS, Match.AT_RULES);\n```\n\nYou can take this further with a custom `@Refine` method that checks the raw content and refines if appropriate:\n\n```java\n@Refine\npublic void observe(Selector selector, Grammar grammar, Broadcaster broadcaster) {\n    if (selector.raw().get().content().contains(\".foo\")) {\n        SelectorPlugin.delegateRefinement(selector, grammar, broadcaster);\n    }\n}\n```\n\nYou can delegate refinement to the standard plugin ([`SelectorPlugin`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/syntax/SelectorPlugin.html), [`DeclarationPlugin`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/syntax/DeclarationPlugin.html), [`MediaPlugin`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/plugin/syntax/MediaPlugin.html)).\n\nUsing these methods you can eliminate unnecessary parsing for large sets of CSS in performance sensitive environments.\n\n### Custom error handling\n\nThe default [`ErrorManager`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/error/ErrorManager.html) is `DefaultErrorManager`, which will rethrow some errors immediately and log others at the end of parsing.\n\nYou can alternatively specify your own `ErrorManager` implementation and provide it during parser setup:\n\n```java\nOmakase.source(input).use(myCustomErrorManager).process();\n```\n\n### Custom writers\n\nOmakase allows you to hook into the writing process and override the output of any particular AST unit. This feature allows you to:\n\n- Conditionally stop the output of the unit.\n- Append or prepend something before or after the unit.\n- Conditional apply logic or append content based on an annotation from a CSS comment associated with the unit.\n\nHowever, it is not recommended to change the actual content of the unit using a custom writer, as this will bypass all rework and validation rules.\n\nThe first step is to create a new class that implements the [`CustomWriter`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/writer/CustomWriter.html) interface. This interface is parameterized with the type of unit that it is overriding:\n\n```java\npublic class MyCustomWriter implements CustomWriter\u003cSelector\u003e {\n    @Override\n    public boolean write(Selector selector, StyleWriter writer, StyleAppendable appendable) throws IOException {\n        appendable.append(\"/*CUSTOM OUTPUT*/\"); // arbitrary content\n        writer.writeInner(selector, appendable, false);\n        return true;\n    }\n}\n```\n\nInside of the `#write` method you can append any content to the output by using the given [`StyleAppendable`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/writer/StyleAppendable.html) as seen above. If you would like to append the default output of the unit as well then call the [`StyleWriter#writeInner`](https://opensource.salesforce.com/omakase/com/salesforce/omakase/writer/StyleWriter.html#writeInner-T-com.salesforce.omakase.writer.StyleAppendable-boolean-) method, passing false for the last parameter. This method should return true if it has handled the unit, or false to allow subsequent custom writers or the default writer to handle it instead.\n\nAfterwards, register this custom writer with the `StyleWriter` instance:\n\n```java\nStyleWriter writer = StyleWriter.compressed();\nwriter.addCustomWriter(Selector.class, new MyCustomWriter());\nOmakase.source(\".class{color:red}\").use(writer).process();\n```\n\n### Comments and CSS annotations\n\nAST objects are automatically associated with all comments that logically precede them. You can access the list of comments by calling the `Syntax#comments` method on the syntax unit. In other words, comments are linked to the AST object that directly follows them (with the exception of orphaned comments, as explained below). Take this example:\n\n```css\n/*0*/\n\n/*1*/.class /*2*/ /*3*/.class/*4*/.class /*5*/, /*6*/ p#id /*7*/ { /*8*/\n  /*9*/ border: /*10*/ 1px/*11*/ solid red /*12*/;/*13*/ /*14*/\n  /*15*/ margin: 1px; /*16*/\n  /*17*/\n}\n\n/*18*//*19*/\n```\n\n- **0, 1** - linked with the `Selector` starting with the content `.class...`\n- **2, 3** - linked with the `ClassSelector` segment that follows them\n- **4** - linked with the `ClassSelector` that follows it\n- **5** - an orphaned comment on the `Selector`\n- **6** - linked to the `Selector` starting with content `p#`...\n- **7** - an orphaned comment attached to the `Selector`\n- **8, 9** - linked to the `Declaration` starting with content `border`...\n- **10** - linked to the term (`NumericalValue`) with content `1px`\n- **11** - linked to the term (`KeywordValue`) with content `solid`\n- **12** - an orphaned comment attached to the `Declaration`\n- **13, 14, 15** - linked to the `Declaration` starting with content `margin`...\n- **16, 17** - orphaned comments linked to the `Rule`\n- **18, 19** - orphaned comments linked to the `Stylesheet`\n\nNote that without refinement, the comments on the inner segments of the `Selector`s and `Declaration`s will not be known.\n\n#### CSS Annotations\n\nComments can be used for _annotation directives_. For example:\n\n```css\n.class {\n  width: 20px;\n  margin-left: 20px;\n  /* @noflip */ float: left;\n}\n```\n\nIn this example, the `@noflip` annotation is used to provide information to the `DirectionFlip` plugin regarding not flipping that particular declaration value.\n\nCSS comment annotations start with `@` + the name of the annotation. They can also have arguments. There can be at most one annotation per CSS comment block, although multiple annotation comment blocks are allowed. For example:\n\n```css\n.class {\n  /* @markerAnnotation */ color: red;\n  /* @lang-switch ja eng */ font-family: Arial, sans-serif;\n  /* @custom1 */ /* @custom2 */ display: block;\n}\n```\n\nNo other content is allowed in the comment with the annotation. If there's any content before the start of the annotation the annotation will not be recognized, with the exception of bang comments:\n\n```css\n.class {\n  width: 20px;\n  margin-left: 20px;\n  /*! @noflip */ float: left;\n}\n```\n\nAny content that follows the annotation will be considered arguments for the annotation.\n\nAny number of custom annotations can be utilized by your plugins. To check for an annotation, there are convenience methods available on every syntax unit:\n\n```java\npublic class MyPlugin implements Plugin {\n    @Rework\n    public void rework(Declaration declaration) {\n        // read /* @markerAnnotation */\n        if (declaration.hasAnnotation(\"markerAnnotation\")) {\n            // do something\n        }\n\n        // read /* @browser ie7 */\n        Optional\u003cCssAnnotation\u003e annotation = declaration.annotation(\"browser\");\n        if (annotation.isPresent()) {\n            System.out.println(annotation.get().rawArgs());\n        }\n\n        // read /* @bug b-123456, b-123457 */\n        Optional\u003cCssAnnotation\u003e annotation = declaration.annotation(\"bug\");\n        if (annotation.isPresent()) {\n            ImmutableList\u003cString\u003e bugNumbers = annotation.get().commaSeparatedArgs();\n        }\n\n        // read /* @bug b-123456 b-123457 */\n        Optional\u003cCssAnnotation\u003e annotation = declaration.annotation(\"bug\");\n        if (annotation.isPresent()) {\n            ImmutableList\u003cString\u003e bugNumbers = annotation.get().spaceSeparatedArgs();\n        }\n\n        // read /* @details author=nathan, since=2.0 */\n        Optional\u003cCssAnnotation\u003e annotation = declaration.annotation(\"details\");\n        if (annotation.isPresent()) {\n            ImmutableMap\u003cString, String\u003e map = annotation.get().keyValueArgs('=');\n            String author = map.get(\"author\");\n        }\n\n        // print all annotations\n        for (CssAnnotation annotation : declaration.annotations()) {\n            System.out.println(annotation.name());\n            System.out.println(annotation.rawArgs());\n        }\n    }\n}\n```\n\nThe [`CssAnnotation`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/ast/CssAnnotation.html) class contains many powerful methods to parse various arg formats including space-delimited,\ncomma-delimited, key-value pairs and enum constants.\n\nIf you happen to have your hands on a specific [`Comment`](https://opensource.salesforce.com/omakase/index.html?com/salesforce/omakase/ast/Comment.html) instance, it has convenience methods as well.\n\nWhen an annotation is placed before a rule, it is associated with first selector instance in the rule, not the rule or the simple selector, as explained above. However for convenience, all of the `has*` and `get*` annotation methods will also check or include results from the first selector when called on a rule instance.\n\n#### Orphaned comments\n\nThe term _orphaned comment_ refers to a comment that does not logically precede any particular AST unit. There are four places where orphaned comments can be found, which are at the end of a selector, at the end of a declaration (before the semi-colon), at the end of a rule, and at the end of a stylesheet. Here are some examples:\n\n```css\n.class1 .class2 \u003e a:hover /* an orphaned comment */, #id a:hover /*another orphaned comment*/ {\n    color: red;\n    font-size: 1.3em /*an orphaned comment*/;\n    /* an orphaned comment */\n}\n\n/* an orphaned comment */\n```\n\nUse the `#orphanedComments` method on a `Selector`, `Declaration`, `Rule` or `Stylesheet` to retrieve them.\n\nSubscribable Syntax Units\n-------------------------\n\nFollowing is the list of all supported syntax types that you can subscribe to in `@Rework`, `@Validate` and `@Observe` annotated methods. Keep in mind that many syntax units require _refinement_ before they will be delivered. More information on this is available in the Usage section above.\n\n\u003cpre\u003e\n    Name                           Description                                               Enablement / Dependency     Type\n    ----------------------------   -------------------------------------------------------   -------------------------   ---------------\n01: Statement                      rule or at-rule                                           Automatic                   interface\n02: Syntax                         top level interface for all units                         Under certain conditions*   interface\n03: RawFunction                    a raw function before refinement                          Declaration refinement      class\n04: Rule                           (no description)                                          Automatic                   class\n05: Stylesheet                     (no description)                                          Automatic                   class\n06: AtRule                         (no description)                                          Automatic                   class\n07: FontDescriptor                 font descriptor within @font-face                         AtRule refinement           class\n08: MediaQueryList                 full media query string                                   AtRule refinement           class\n09: FunctionValue                  general interface for function terms                      Declaration refinement      interface\n10: Term                           a single segment of a property value                      Declaration refinement      interface\n11: Declaration                    (no description)                                          Automatic                   class\n12: GenericFunctionValue           unknown function value                                    Declaration refinement      class\n13: HexColorValue                  individual hex color value                                Declaration refinement      class\n14: KeywordValue                   individual keyword value                                  Declaration refinement      class\n15: LinearGradientFunctionValue    linear gradient function                                  Declaration refinement      class\n16: NumericalValue                 individual numerical value                                Declaration refinement      class\n17: PropertyValue                  interface for all property values                         Declaration refinement      class\n18: StringValue                    individual string value                                   Declaration refinement      class\n19: UnicodeRangeValue              unicode range value                                       Declaration refinement      class\n20: UrlFunctionValue               url function                                              Declaration refinement      class\n21: ConditionalAtRuleBlock         conditionals                                              AtRule refinement           class\n22: UnquotedIEFilter               proprietary microsoft filter                              Declaration refinement      class\n23: SelectorPart                   group interface for all selector segments                 Selector refinement         interface\n24: SimpleSelector                 parent interface for simple selectors                     Selector refinement         interface\n25: AttributeSelector              attribute selector segment                                Selector refinement         class\n26: ClassSelector                  class selector segment                                    Selector refinement         class\n27: IdSelector                     id selector segment                                       Selector refinement         class\n28: PseudoClassSelector            pseudo class selector segment                             Selector refinement         class\n29: PseudoElementSelector          pseudo element selector segment                           Selector refinement         class\n30: Selector                       (no description)                                          Automatic                   class\n31: TypeSelector                   type/element selector segment                             Selector refinement         class\n32: UniversalSelector              universal selector segment                                Selector refinement         class\n\nGenerated by PrintSubscribableSyntaxTable.java\n\u003c/pre\u003e\n\n**Notes:**\n\n* A subscription to `Syntax` will depend on which concrete syntax classes are enabled. To get _every_ syntax unit then utilize `new StandardValidaion()` or `AutoRefine#everything`.\n* Some orphaned comments will only be delivered if selectors and declarations are refined.\n\nInteractive Shell\n-----------------\n\nThis project comes with an interactive shell, which allows you to quickly see what Omakase will output when given specific input CSS, all in real-time.\n\nTo get started, you must first run the Omakase setup script. Under the Omakase project directory run this command from the terminal:\n\n    script/setup.sh\n\nThis will enable the `omakase` command from within this project folder. See the [Scripts](#scripts) section below for more information. Once this is done, you can start up the interactive shell by typing in the terminal:\n\n    omakase --interactive\n\nThis will display usage information that looks something like this:\n\n    Omakase Interactive Shell\n    enter ! on a new line to finish\n    enter !c for continuous mode (ctrl+c to exit)\n    enter !verbose for verbose output\n    enter !inline for inline output\n    enter !compressed for compressed output\n    enter !prefix-current for default prefixing\n    enter !prefix-all for all prefixing\n    enter !prefix-prune to enable prefix pruning\n    enter !prefix-rearrange to enable prefix rearranging\n    enter !prefix-off to remove prefixing support\n    enter !subl to use the sublime text editor (subl)\n    enter !mate to use the textmate editor (mate)\n\nType your input CSS, with `!` on a blank line demarking the end of the input. For example:\n\n    .test {\n      color: red;\n    }\n    !\n\nThis will output something like this:\n\n    ----------result----------\n\n    .test {color:red}\n\nFor more involved scenarios, you might prefer to use the built in support for Sublime Text and Atom.\n\nSublime Text requires the `subl` command to be installed on your PATH:\nhttps://www.sublimetext.com/docs/3/osx_command_line.html\n\nYou can test that this is working by simply typing `subl` in the terminal. It should open Sublime Text. If not, make sure ~/bin (or wherever you linked to) is on your path.\n\nAtom requires the `atom` command. You can install this by going to Menu -\u003e Install Shell Commands. Again you can test this if the `atom` command in terminal opens up Atom.\n\nDevelopment\n-----------\n\nBefore checking anything in, setup your IDE to conform to project standards. See and follow the instructions in the readme.md files inside of the `contributing/intellij` or `contributing/eclipse` folders.\n\nAs of right now the (strongly) preferred IDE for contribution is IntelliJ IDEA. This is mainly because the existing source code and style closely conforms to the IntelliJ settings included in the project. If you use eclipse or something else then be sure to following the existing coding conventions manually if need be.\n\n### Building\n\nThe project relies on the following technologies:\n\n1. git (duh)\n2. java 8 (make sure both the IDE and maven are setup to use it)\n3. maven 3+\n\nrun `mvn clean install` to get things going from the command line. It should build and run tests successfully. Afterwards you can import the maven project into your IDE (as an existing maven project) and go from there.\n\n### Dependencies\n\nNon-test dependencies include Google's **Guava** library. Dependencies shouldn't really increase beyond that as one of the goals is simplicity and self-containment.\n\n### Tests\n\nCurrently tests are built with junit 4 and [fest assertions](https://code.google.com/p/fest/#Fluent_Assertions). Junit should be self explanatory, but if you haven't used junit 4+ then keep in mind that it uses java _annotations_ instead of inheritance and method naming conventions.\n\nFest may be new to you, but it's quite simple and easy to get the hang of. If you have used hamcrest before then it's something like that. Basically it's a library of matchers and assertions. The actual assertions are fluent and look something like this:\n\n```java\nassertThat(someValue).isTrue();\nassertThat(someCollection).hasSize(3);\nassertThat(someCollection).containsExactly(value1, value2, value3);\n```\n\nThis makes the tests more readable and also provides much more useful error messages out the box.\n\nThe important takeaway is that **all** unit tests must be written using fest assertions and not the junit/hamcrest ones. Just follow the patterns established in the existing tests. Particularly make sure you import the correct classes.\n\n### Updating keywords, properties, etc...\n\nThere are several enums such as `Keyword.java` and `Browser.java` that contain values that will inevitably need to be updated. Most of these are stored in data files under `src/test/resources/data/`.\n\nYou can use a [script](#scripts) to regenerate the java source files after updating, or you can directly run the main method on the classes.\n\n### Scripts\n\nThe omakase CLI is a powerful tool for building the project, regenerating enum source files, running performance tests, and more.\n\nTo get started, under the project directory run this command from the shell:\n\n    script/setup.sh\n\nThis will setup links to the omakase CLI script. Now you can run the `omakase` command from within the project root:\n\n    ~/dev/omakase \u003e omakase\n\n    Usage: omakase [options]\n\n    Options:\n\n      -b (--build)                  build the project\n      -h (--help)                   print this help message\n      -i (--interactive, --shell)   interactive shell\n      -l (--local-only)             only regenerate local data, no prefix data (used with -u option)\n      -p (--perf) \u003cargs\u003e            performance test\n      -s (--syntax, --sub)          print the subscribable syntax table\n      -u (--update)                 regenerate data enum, data class and prefixes source files\n      -v (--prefixed-def)           print what is auto-prefixed by Prefixer.defaultBrowserSupport()\n      -w (--prefixed-all)           print all properties, at-rules, etc...that are supported by Prefixer\n\nFor example, updating the prefix info:\n\n    omakase --update\n\nPrinting the subscribable syntax table:\n\n    omakase --syntax\n\nRunning the performance test:\n\n    omakase -p\n\nArchitecture\n------------\n\nOmakase is a CSS parser built from the ground up. Unlike other open-source CSS parsers it is not built on a parser generator such as JavaCC or Antlr. Instead, it relies on many small and simple java objects that know how to consume various parts of CSS syntax, which also allows for easy extenstion of the syntax and grammar by consumers.\n\nThe project requires _Java 8_, _git_ and _maven_. The general architecture of the project can be summarized as follows:\n\n1. **Parsers** - Small, individual parser objects that process CSS source code.\n2. **AST Objects** - Simple representations of various CSS syntax units.\n3. **Plugins** - Observers that can subscribe to any AST object for refinement, rework or validation.\n4. **Broadcasters and Emitter** - The bridge between parsers and plugins.\n5. **Writers** - Outputs parsed CSS code.\n6. **ErrorHandlers** - Manages errors encountered when parsing CSS source code.\n\n### Parsers\n**Key Classes** `Token` `Tokens` `Source` `Parser` `BaseParserFactory`, `Grammar`\n\nParsers are simple java objects that know how to parse specific aspects of CSS syntax. Parsers do not maintain any state.\n\nParsers employ a 2-level parsing strategy. The first level will comb through the CSS source and extract the at-rules, selectors and declarations (and no more). Any errors at this grammatical level will be caught immediately (for example, a missing curly bracket to close a rule). However any errors at a more specific level (e.g., within a selector or within a declaration) will not be caught until that particular object is refined.\n\nThe second level occurs individually per instance, e.g., each particular `Selector` or `Declaration` instance.\n\nIt's important to understand that the second level may or may not be executed or may only be executed on certain instances.\n\nThis process allows us to be more specific about what actually gets parsed. For example, on a first parsing pass we may want to parse and validate everything, but on a second pass we may not care about fully parsing selectors anymore.\n\nWhen a `Parser` has successfully parsed some content, it will construct the appropriate AST object and give it to the `Broadcaster`. Ultimately, the `Broadcaster` will pass the AST object to the registered subscription methods for that particular AST object type.\n\n### AST Objects\n**Key Classes** `Syntax` `Refinable` `Groubable` `SyntaxCollection` `Selector` `Declaration`\n\nAST (Abstract Syntax Tree) objects are simple data objects representing various aspects o CSS syntax, e.g., selectors, declarations, etc... AST objects generally have getters and setters for various values. AST objects are also responsible for writing their own content out when a `StyleWriter` asks for it.\n\nAST objects generally contain little to no validation logic. Most validation is written in the form of a `Plugin` that subscribes to the unit it is going to validate.\n\n### Plugins\n**Key Classes** `Plugin` `DependentPlugin`\n\nA `Plugin` subscribes to one or more AST objects (one per method) to perform rework or validation. Methods on the plugin are annotated with `@Rework` or `@Validate` annotations as appropriate. These methods are known as subscription methods. Each subscription method is subscribed to one particular AST object type via its parameter. This parameter is how we know which methods to invoke when various AST objects are sent to be broadcasted.\n\nPlugins are registered during parser setup via `Omakase#use`. Plugins can and often do have dependencies on each other, which can be registered by implementing the `DependentPlugin` interface.\n\nThe general Omakase philosophy is that much of the internal logic as well as all of the consumer logic is organized into a set of plugins.\n\n### Broadcasters\n**Key Classes** `Broadcaster` `Emitter` `AnnotationScanner` `Subscription`\n\nA `Broadcaster` is something that handles broadcasts of various AST objects. You can think of this as the _observer_ pattern, or the _event listener_ pattern, where the AST object itself is the event, the plugins are the listeners, and the broadcasters are responsible for receiving the AST objects from the parsers and delivering it to all registered subscriptions.\n\nHowever, broadcasters do no necessarily deliver the AST objects right away. The `Broadcaster` interface utilizes a _chain_ pattern. That is, broadcasters can be wrapped inside of each other, relaying broadcasters to the next broadcaster down the chain. Near the bottom of the chain is usually the `EmittingBroadcaster`, which is the one responsible for using an `Emitter` to actually invoke the subscription methods.\n\n### Writers\n**Key Classes** `Writable` `StyleWriter` `StyleAppendable`\n\nWriters are fairly simple... they are responsible for taking the parsed `SyntaxTree` and writing it out as a CSS source code string.\n\n### ErrorManagers\n**Key Classes** `ErrorManager`\n\nError managers are responsible for dealing with errors during processing, including parser errors or errors generated from a validator plugin.\n\n### Dependencies Graph\n\nTo view statistics on dependencies, run:\n\n    mvn project-info-reports:dependencies\n    open target/site/dependencies.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalesforce%2Fomakase","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalesforce%2Fomakase","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalesforce%2Fomakase/lists"}