Fix bin/publish: copy docs.dist from project root

Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

View File

@@ -0,0 +1,331 @@
# 14.0.2 / 2021-05-10
- Remove remaining direct import of `postcss` package ([#455](https://github.com/postcss/postcss-import/issues/455), [#456](https://github.com/postcss/postcss-import/pull/456))
# 14.0.1 / 2021-03-31
- Fix bug with `@charset` statements in media imports ([#448](https://github.com/postcss/postcss-import/issues/448), [#453](https://github.com/postcss/postcss-import/pull/453))
# 14.0.0 / 2020-12-14
This release should not have breaking changes for the vast majority of users; only those with `@charset` statements in their CSS _may_ be affected.
- **BREAKING:** Error if multiple incompatible `@charset` statements ([#447](https://github.com/postcss/postcss-import/pull/447))
- **BREAKING:** Warn if `@charset` statements are not at the top of files ([#447](https://github.com/postcss/postcss-import/pull/447))
- Fix handing of `@charset` ([#436](https://github.com/postcss/postcss-import/issues/436), [#447](https://github.com/postcss/postcss-import/pull/447))
# 13.0.0 / 2020-10-20
- **BREAKING:** Require Node 10+ ([#429](https://github.com/postcss/postcss-import/pull/429))
- **BREAKING:** Upgrade to postcss v8 and require it as a `peerDependency` ([#427](https://github.com/postcss/postcss-import/issues/427), [#432](https://github.com/postcss/postcss-import/pull/432))
- Update dependencies
# 12.0.1 / 2018-10-22
- Add `plugin` property to dependency messages ([#379](https://github.com/postcss/postcss-import/issues/379), [#380](https://github.com/postcss/postcss-import/pull/380))
# 12.0.0 - 2018-08-04
- Removed: Support for Node.js v4
- Changed: Uses PostCSS v7 (https://github.com/postcss/postcss/releases/tag/7.0.0)
# 11.1.0 - 2018-02-10
- Added: `filter` option
# 11.0.0 - 2017-09-16
- Changed: A syntax error in an imported file now throws an error instead of just warning ([#264](https://github.com/postcss/postcss-import/issues/264))
- Changed: Symlink handling to be consistent with Node.js `require` ([#300](https://github.com/postcss/postcss-import/pull/300))
# 10.0.0 - 2017-05-12
- Removed: Support for Node.js versions less than 4.5.x ([#283](https://github.com/postcss/postcss-import/pull/283))
- Changed: Upgraded to Postcss v6 ([#283](https://github.com/postcss/postcss-import/pull/283))
- Removed: jspm support ([#283](https://github.com/postcss/postcss-import/pull/283))
- Removed: deprecated `addDependencyTo` option
- Removed: `onImport` option
- Changed: Doesn't depend on promise-each ([#281](https://github.com/postcss/postcss-import/pull/281))
# 9.1.0 - 2017-01-10
- Added: `addModulesDirectories` option ([#256](https://github.com/postcss/postcss-import/pull/256))
# 9.0.0 - 2016-12-02
- Removed: `transform` option
([#250](https://github.com/postcss/postcss-import/pull/250))
- Removed: `pkg-resolve` is no longer a dependency; this should fix some issues
with webpack. jspm users must manually install `pkg-resolve` if they want to
load jspm modules (see https://github.com/postcss/postcss-import#jspm-usage
for more info) ([#243](https://github.com/postcss/postcss-import/pull/243))
- Changed: If a file is not found, it will now throw an error instead of just
raising a warning ([#247](https://github.com/postcss/postcss-import/pull/247))
- Changed: If a custom resolver does not return an absolute path, the default
resolver will be applied to the returned path.
([#249](https://github.com/postcss/postcss-import/pull/249))
- Changed: postcss-import will try to guess the correct parser for imported
files, based on the file extension.
([#245](https://github.com/postcss/postcss-import/pull/245))
- Changed: Deprecated `addDependencyTo` option, it is not needed if using
postcss-loader >= v1.0.0
([#251](https://github.com/postcss/postcss-import/pull/251))
# 8.2.0 - 2016-11-09
- Fixed: Warn about all `@import`s after other CSS declarations
([#240](https://github.com/postcss/postcss-import/pull/240))
- Added: `dependency` message
([#241](https://github.com/postcss/postcss-import/pull/241))
# 8.1.3 - 2016-11-03
- Fixed: Nested import ordering
([#236](https://github.com/postcss/postcss-import/pull/236) - @RyanZim)
# 8.1.2 - 2016-05-07
- Fixed: prevent JSPM to throw unrecoverable error
([#205](https://github.com/postcss/postcss-import/pull/205))
# 8.1.1 - 2016-05-04
- Fixed: JSPM support
([#194](https://github.com/postcss/postcss-import/pull/194))
# 8.1.0 - 2016-04-04
- Added: JSPM browser field
([#186](https://github.com/postcss/postcss-import/pull/186))
# 8.0.2 - 2015-01-27
- Fixed: Comments between imports statements are ignored
([#164](https://github.com/postcss/postcss-import/pull/164))
# 8.0.1 - 2015-01-27
- Fixed: missing "lib" folder
([#161](https://github.com/postcss/postcss-import/issues/161))
# 8.0.0 - 2015-01-27
**All imports statements must be at the top of your file now, per CSS specification.**
You should use [postcss-reporter](https://github.com/postcss/postcss-reporter) to see the warnings raised.
- Removed: async mode/option (now async by default)
([#107](https://github.com/postcss/postcss-import/pull/107))
- Removed: "bower_components" not supported by default anymore,
use "path" option to add it back
- Removed: `encoding` option. Encoding can be specified in custom `load` option
```js
postcssImport({
load: function(filename) {
return fs.readFileSync(filename, "utf-8")
}
})
```
([#144](https://github.com/postcss/postcss-import/pull/144))
- Removed: glob support
([#146](https://github.com/postcss/postcss-import/pull/146))
Globs can be implemented with custom `resolve` option
```js
postcssImport({
resolve: function(id, base) {
return glob.sync(path.join(base, id))
}
})
```
([#116](https://github.com/postcss/postcss-import/pull/116))
- Changed: custom resolve has more responsibility for paths resolving.
See [resolve option](https://github.com/postcss/postcss-import#resolve)
for more information about this change
([#116](https://github.com/postcss/postcss-import/pull/116))
- Changed: support promise in `transform` option and `undefined` result will be
skipped
([#147](https://github.com/postcss/postcss-import/pull/147))
- Changed: `options.plugins` are applied to unprocessed ast before imports
detecting
([157](https://github.com/postcss/postcss-import/pull/157))
- Added: custom resolve function can return array of paths
([#120](https://github.com/postcss/postcss-import/pull/120))
- Added: custom syntax in imported files support
([#130](https://github.com/postcss/postcss-import/pull/130))
- Added: support custom `load` option
([#144](https://github.com/postcss/postcss-import/pull/144))
- Added: detect css extension in package.json `main` field
([153](https://github.com/postcss/postcss-import/pull/153))
**Note:**
_If you miss options/default behavior (glob etc), a new plugin will handle all
those things.
Please follow issue [#145](https://github.com/postcss/postcss-import/issues/145)
_
# 7.1.3 - 2015-11-05
- Fixed: ensure node 0.12 compatibility, round 2
([#93](https://github.com/postcss/postcss-import/pull/93))
# 7.1.2 - 2015-11-05
- Fixed: performance issue because of cloned options
([#90](https://github.com/postcss/postcss-import/pull/90))
# 7.1.1 - 2015-11-05
- Added: ensure node 0.12 compatibility
# 7.0.0 - 2015-08-25
- Removed: compatibility with postcss v4.x
([#75](https://github.com/postcss/postcss-import/pull/75))
- Added: compatibility with postcss v5.x
([#76](https://github.com/postcss/postcss-import/pull/76))
- Added: lighter package by upgrading some dependencies
([#73](https://github.com/postcss/postcss-import/issues/73))
# 6.2.0 - 2015-07-21
- Added: `skipDuplicates` option now allows you to **not** skip duplicated files
([#67](https://github.com/postcss/postcss-import/issues/67))
# 6.1.1 - 2015-07-07
- Fixed: Prevent mutability issue, round 2
([#44](https://github.com/postcss/postcss-import/issues/44))
- Added: `plugins` option, to run some postcss plugin on imported files
([#55](https://github.com/postcss/postcss-import/issues/55))
- Added: `bower_components` is now part of the default paths
([#66](https://github.com/postcss/postcss-import/issues/66))
- Added: `async` option allow to use enable PostCSS async API usage.
Note that it's not enabling async fs read yet. It has been added to fix breaking
change introduced by 6.1.0.
# 6.1.0 - 2015-07-07 **YANKED**
_This release was not respecting semver and introduced a major breaking change.
It has been unpublished for now._
# 6.0.0 - 2015-06-17
- Changed: warnings messages are now using postcss message api (4.1.x)
- Added: warning when a import statement has not been closed correctly
([#42](https://github.com/postcss/postcss-import/issues/42))
# 5.2.2 - 2015-04-19
- Fixed: globbed imports work for module directories ([#37](https://github.com/postcss/postcss-import/pull/37))
# 5.2.1 - 2015-04-17
- Fixed: glob import now works with single quote `@import` ([#36](https://github.com/postcss/postcss-import/pull/36))
# 5.2.0 - 2015-04-15
- Added: [glob](https://www.npmjs.com/package/glob) pattern are now supported if `glob` option is set to true ([#34](https://github.com/postcss/postcss-import/pull/34))
- Added: plugin can now be added to PostCSS without calling it as a function ([#27](https://github.com/postcss/postcss-import/pull/27))
# 5.1.1 - 2015-04-10
- Fixed: regression of 5.1.0: files which only contain same @import rules were skip ([#31](https://github.com/postcss/postcss-import/issues/31))
# 5.1.0 - 2015-03-27
- Added: files with the same content will only be imported once. Previously, only the full path was used to determine if a file has already been imported in a given scope.
Now, we also test create a hash with the content of the file to check if a file with the same content has not already been imported.
This might be usefull if some modules you import are importing the same library from different places (eg: normalize might be as dep for several modules located in different places in `node_modules`)
([#29](https://github.com/postcss/postcss-import/pull/28))
# 5.0.3 - 2015-02-16
- Fixed: regression of 5.0.2: AST parent references were not updated ([#25](https://github.com/postcss/postcss-import/issues/25))
# 5.0.2 - 2015-02-14
- Fixed: indentation and code style are now preserved ([#20](https://github.com/postcss/postcss-import/issues/20))
# 5.0.1 - 2015-02-13
- Fixed: breaking bug with remote stylesheets ([#21](https://github.com/postcss/postcss-import/issues/21) & [#22](https://github.com/postcss/postcss-import/issues/22))
# 5.0.0 - 2015-01-26
- Added: compatibility with postcss v4.x
- Removed: compatibility with postcss v3.x
- Fixed: relative imports (./ and ../) should work using `path` option only (no need for `from`) ([#14](https://github.com/postcss/postcss-import/issues/14))
# 4.1.1 - 2015-01-05
- Fixed: irregular whitespace that throw syntax error in some environnements
# 4.1.0 - 2014-12-12
- Added: `web_modules` is now in module directories that are used to resolve `@import` ([#13](https://github.com/postcss/postcss-import/issues/13)).
# 4.0.0 - 2014-12-11
- Added: windows compatibility (by building on AppVeyor)
- Added: `root` option
# 3.2.0 - 2014-11-24
- Added: `onImport` callback offers a way to get list of imported files ([ref](https://github.com/postcss/postcss-import/issues/9))
# 3.1.0 - 2014-11-24
- Added: ability to consume local modules (fix [#12](https://github.com/postcss/postcss-import/issues/12))
# 3.0.0 - 2014-11-21
- Added: ability to consume node modules ([ref](https://github.com/postcss/postcss-import/issues/7)).
This means you don't have to add `node_modules` in the path anymore (or using `@import "../node_modules/..."`).
Also, `index.css` can be ommited.
This means something like this
```css
@import "../node_modules/my-css-on-npm/index.css";
```
can be written like this
```css
@import "my-css-on-npm";
```
Dependencies of dependencies should be resolved as well.
_Note that npm resolution is done after the default local behavior._
- Changed: When importing a file multiple times in the same scope (same level of media queries), file will only be imported the first time.
This is done to avoid having multiples outputs of a npm dep used multiples times in different modules.
# 2.0.0 - 2014-11-12
- Added: compatibility with postcss v3.x
- Removed: compatibility with postcss v2.x
# 1.0.3 - 2014-10-29
- Fixed: relative import path stack
# 1.0.2 - 2014-09-16
- Added: Move ignored import at top & adjust related media queries, to make them work (fix [#2](https://github.com/postcss/postcss-import/issues/2))
- Added: Ignore scheme-relative absolute URLs
- Removed: `parse-import` module dependency
# 1.0.1 - 2014-08-26
- Fixed: GNU message format
- Added: Support empty files ([cssnext/#24](https://github.com/putaindecode/cssnext/issues/24))
# 1.0.0 - 2014-08-10
✨ First release based on [rework-import](https://github.com/reworkcss/rework-import) v1.2.0 (mainly for fixtures)

20
vendor/spatie/ignition/node_modules/postcss-import/LICENSE generated vendored Executable file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Maxime Thirouin, Jason Campbell & Kevin Mårtensson
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

230
vendor/spatie/ignition/node_modules/postcss-import/README.md generated vendored Executable file
View File

@@ -0,0 +1,230 @@
# postcss-import
[![Build](https://img.shields.io/travis/postcss/postcss-import/master)](https://travis-ci.org/postcss/postcss-import)
[![Version](https://img.shields.io/npm/v/postcss-import)](https://github.com/postcss/postcss-import/blob/master/CHANGELOG.md)
[![postcss compatibility](https://img.shields.io/npm/dependency-version/postcss-import/peer/postcss)](https://postcss.org/)
> [PostCSS](https://github.com/postcss/postcss) plugin to transform `@import`
rules by inlining content.
This plugin can consume local files, node modules or web_modules.
To resolve path of an `@import` rule, it can look into root directory
(by default `process.cwd()`), `web_modules`, `node_modules`
or local modules.
_When importing a module, it will look for `index.css` or file referenced in
`package.json` in the `style` or `main` fields._
You can also provide manually multiples paths where to look at.
**Notes:**
- **This plugin should probably be used as the first plugin of your list.
This way, other plugins will work on the AST as if there were only a single file
to process, and will probably work as you can expect**.
- This plugin works great with
[postcss-url](https://github.com/postcss/postcss-url) plugin,
which will allow you to adjust assets `url()` (or even inline them) after
inlining imported files.
- In order to optimize output, **this plugin will only import a file once** on
a given scope (root, media query...).
Tests are made from the path & the content of imported files (using a hash
table).
If this behavior is not what you want, look at `skipDuplicates` option
- **If you are looking for glob, or sass like imports (prefixed partials)**,
please look at
[postcss-easy-import](https://github.com/trysound/postcss-easy-import)
(which use this plugin under the hood).
- Imports which are not modified (by `options.filter` or because they are remote
imports) are moved to the top of the output.
- **This plugin attempts to follow the CSS `@import` spec**; `@import`
statements must precede all other statements (besides `@charset`).
## Installation
```console
$ npm install postcss-import
```
## Usage
Unless your stylesheet is in the same place where you run postcss
(`process.cwd()`), you will need to use `from` option to make relative imports
work.
```js
// dependencies
const fs = require("fs")
const postcss = require("postcss")
const atImport = require("postcss-import")
// css to be processed
const css = fs.readFileSync("css/input.css", "utf8")
// process css
postcss()
.use(atImport())
.process(css, {
// `from` option is needed here
from: "css/input.css"
})
.then((result) => {
const output = result.css
console.log(output)
})
```
`css/input.css`:
```css
/* can consume `node_modules`, `web_modules` or local modules */
@import "cssrecipes-defaults"; /* == @import "../node_modules/cssrecipes-defaults/index.css"; */
@import "normalize.css"; /* == @import "../node_modules/normalize.css/normalize.css"; */
@import "foo.css"; /* relative to css/ according to `from` option above */
@import "bar.css" (min-width: 25em);
body {
background: black;
}
```
will give you:
```css
/* ... content of ../node_modules/cssrecipes-defaults/index.css */
/* ... content of ../node_modules/normalize.css/normalize.css */
/* ... content of css/foo.css */
@media (min-width: 25em) {
/* ... content of css/bar.css */
}
body {
background: black;
}
```
Checkout the [tests](test) for more examples.
### Options
### `filter`
Type: `Function`
Default: `() => true`
Only transform imports for which the test function returns `true`. Imports for
which the test function returns `false` will be left as is. The function gets
the path to import as an argument and should return a boolean.
#### `root`
Type: `String`
Default: `process.cwd()` or _dirname of
[the postcss `from`](https://github.com/postcss/postcss#node-source)_
Define the root where to resolve path (eg: place where `node_modules` are).
Should not be used that much.
_Note: nested `@import` will additionally benefit of the relative dirname of
imported files._
#### `path`
Type: `String|Array`
Default: `[]`
A string or an array of paths in where to look for files.
#### `plugins`
Type: `Array`
Default: `undefined`
An array of plugins to be applied on each imported files.
#### `resolve`
Type: `Function`
Default: `null`
You can provide a custom path resolver with this option. This function gets
`(id, basedir, importOptions)` arguments and should return a path, an array of
paths or a promise resolving to the path(s). If you do not return an absolute
path, your path will be resolved to an absolute path using the default
resolver.
You can use [resolve](https://github.com/substack/node-resolve) for this.
#### `load`
Type: `Function`
Default: null
You can overwrite the default loading way by setting this option.
This function gets `(filename, importOptions)` arguments and returns content or
promised content.
#### `skipDuplicates`
Type: `Boolean`
Default: `true`
By default, similar files (based on the same content) are being skipped.
It's to optimize output and skip similar files like `normalize.css` for example.
If this behavior is not what you want, just set this option to `false` to
disable it.
#### `addModulesDirectories`
Type: `Array`
Default: `[]`
An array of folder names to add to [Node's resolver](https://github.com/substack/node-resolve).
Values will be appended to the default resolve directories:
`["node_modules", "web_modules"]`.
This option is only for adding additional directories to default resolver. If
you provide your own resolver via the `resolve` configuration option above, then
this value will be ignored.
#### Example with some options
```js
const postcss = require("postcss")
const atImport = require("postcss-import")
postcss()
.use(atImport({
path: ["src/css"],
}))
.process(cssString)
.then((result) => {
const { css } = result
})
```
## `dependency` Message Support
`postcss-import` adds a message to `result.messages` for each `@import`. Messages are in the following format:
```
{
type: 'dependency',
file: absoluteFilePath,
parent: fileContainingTheImport
}
```
This is mainly for use by postcss runners that implement file watching.
---
## CONTRIBUTING
* ⇄ Pull requests and ★ Stars are always welcome.
* For bugs and feature requests, please create an issue.
* Pull requests must be accompanied by passing automated tests (`$ npm test`).
## [Changelog](CHANGELOG.md)
## [License](LICENSE)

301
vendor/spatie/ignition/node_modules/postcss-import/index.js generated vendored Executable file
View File

@@ -0,0 +1,301 @@
"use strict"
// builtin tooling
const path = require("path")
// internal tooling
const joinMedia = require("./lib/join-media")
const resolveId = require("./lib/resolve-id")
const loadContent = require("./lib/load-content")
const processContent = require("./lib/process-content")
const parseStatements = require("./lib/parse-statements")
function AtImport(options) {
options = {
root: process.cwd(),
path: [],
skipDuplicates: true,
resolve: resolveId,
load: loadContent,
plugins: [],
addModulesDirectories: [],
...options,
}
options.root = path.resolve(options.root)
// convert string to an array of a single element
if (typeof options.path === "string") options.path = [options.path]
if (!Array.isArray(options.path)) options.path = []
options.path = options.path.map(p => path.resolve(options.root, p))
return {
postcssPlugin: "postcss-import",
Once(styles, { result, atRule, postcss }) {
const state = {
importedFiles: {},
hashFiles: {},
}
if (styles.source && styles.source.input && styles.source.input.file) {
state.importedFiles[styles.source.input.file] = {}
}
if (options.plugins && !Array.isArray(options.plugins)) {
throw new Error("plugins option must be an array")
}
return parseStyles(result, styles, options, state, []).then(bundle => {
applyRaws(bundle)
applyMedia(bundle)
applyStyles(bundle, styles)
})
function applyRaws(bundle) {
bundle.forEach((stmt, index) => {
if (index === 0) return
if (stmt.parent) {
const { before } = stmt.parent.node.raws
if (stmt.type === "nodes") stmt.nodes[0].raws.before = before
else stmt.node.raws.before = before
} else if (stmt.type === "nodes") {
stmt.nodes[0].raws.before = stmt.nodes[0].raws.before || "\n"
}
})
}
function applyMedia(bundle) {
bundle.forEach(stmt => {
if (!stmt.media.length || stmt.type === "charset") return
if (stmt.type === "import") {
stmt.node.params = `${stmt.fullUri} ${stmt.media.join(", ")}`
} else if (stmt.type === "media")
stmt.node.params = stmt.media.join(", ")
else {
const { nodes } = stmt
const { parent } = nodes[0]
const mediaNode = atRule({
name: "media",
params: stmt.media.join(", "),
source: parent.source,
})
parent.insertBefore(nodes[0], mediaNode)
// remove nodes
nodes.forEach(node => {
node.parent = undefined
})
// better output
nodes[0].raws.before = nodes[0].raws.before || "\n"
// wrap new rules with media query
mediaNode.append(nodes)
stmt.type = "media"
stmt.node = mediaNode
delete stmt.nodes
}
})
}
function applyStyles(bundle, styles) {
styles.nodes = []
// Strip additional statements.
bundle.forEach(stmt => {
if (["charset", "import", "media"].includes(stmt.type)) {
stmt.node.parent = undefined
styles.append(stmt.node)
} else if (stmt.type === "nodes") {
stmt.nodes.forEach(node => {
node.parent = undefined
styles.append(node)
})
}
})
}
function parseStyles(result, styles, options, state, media) {
const statements = parseStatements(result, styles)
return Promise.resolve(statements)
.then(stmts => {
// process each statement in series
return stmts.reduce((promise, stmt) => {
return promise.then(() => {
stmt.media = joinMedia(media, stmt.media || [])
// skip protocol base uri (protocol://url) or protocol-relative
if (
stmt.type !== "import" ||
/^(?:[a-z]+:)?\/\//i.test(stmt.uri)
) {
return
}
if (options.filter && !options.filter(stmt.uri)) {
// rejected by filter
return
}
return resolveImportId(result, stmt, options, state)
})
}, Promise.resolve())
})
.then(() => {
let charset
const imports = []
const bundle = []
function handleCharset(stmt) {
if (!charset) charset = stmt
// charsets aren't case-sensitive, so convert to lower case to compare
else if (
stmt.node.params.toLowerCase() !==
charset.node.params.toLowerCase()
) {
throw new Error(
`Incompatable @charset statements:
${stmt.node.params} specified in ${stmt.node.source.input.file}
${charset.node.params} specified in ${charset.node.source.input.file}`
)
}
}
// squash statements and their children
statements.forEach(stmt => {
if (stmt.type === "charset") handleCharset(stmt)
else if (stmt.type === "import") {
if (stmt.children) {
stmt.children.forEach((child, index) => {
if (child.type === "import") imports.push(child)
else if (child.type === "charset") handleCharset(child)
else bundle.push(child)
// For better output
if (index === 0) child.parent = stmt
})
} else imports.push(stmt)
} else if (stmt.type === "media" || stmt.type === "nodes") {
bundle.push(stmt)
}
})
return charset
? [charset, ...imports.concat(bundle)]
: imports.concat(bundle)
})
}
function resolveImportId(result, stmt, options, state) {
const atRule = stmt.node
let sourceFile
if (atRule.source && atRule.source.input && atRule.source.input.file) {
sourceFile = atRule.source.input.file
}
const base = sourceFile
? path.dirname(atRule.source.input.file)
: options.root
return Promise.resolve(options.resolve(stmt.uri, base, options))
.then(paths => {
if (!Array.isArray(paths)) paths = [paths]
// Ensure that each path is absolute:
return Promise.all(
paths.map(file => {
return !path.isAbsolute(file)
? resolveId(file, base, options)
: file
})
)
})
.then(resolved => {
// Add dependency messages:
resolved.forEach(file => {
result.messages.push({
type: "dependency",
plugin: "postcss-import",
file,
parent: sourceFile,
})
})
return Promise.all(
resolved.map(file => {
return loadImportContent(result, stmt, file, options, state)
})
)
})
.then(result => {
// Merge loaded statements
stmt.children = result.reduce((result, statements) => {
return statements ? result.concat(statements) : result
}, [])
})
}
function loadImportContent(result, stmt, filename, options, state) {
const atRule = stmt.node
const { media } = stmt
if (options.skipDuplicates) {
// skip files already imported at the same scope
if (
state.importedFiles[filename] &&
state.importedFiles[filename][media]
) {
return
}
// save imported files to skip them next time
if (!state.importedFiles[filename]) state.importedFiles[filename] = {}
state.importedFiles[filename][media] = true
}
return Promise.resolve(options.load(filename, options)).then(
content => {
if (content.trim() === "") {
result.warn(`${filename} is empty`, { node: atRule })
return
}
// skip previous imported files not containing @import rules
if (state.hashFiles[content] && state.hashFiles[content][media])
return
return processContent(
result,
content,
filename,
options,
postcss
).then(importedResult => {
const styles = importedResult.root
result.messages = result.messages.concat(importedResult.messages)
if (options.skipDuplicates) {
const hasImport = styles.some(child => {
return child.type === "atrule" && child.name === "import"
})
if (!hasImport) {
// save hash files to skip them next time
if (!state.hashFiles[content]) state.hashFiles[content] = {}
state.hashFiles[content][media] = true
}
}
// recursion: import @import from imported file
return parseStyles(result, styles, options, state, media)
})
}
)
}
},
}
}
AtImport.postcss = true
module.exports = AtImport

View File

@@ -0,0 +1,17 @@
"use strict"
module.exports = function (parentMedia, childMedia) {
if (!parentMedia.length && childMedia.length) return childMedia
if (parentMedia.length && !childMedia.length) return parentMedia
if (!parentMedia.length && !childMedia.length) return []
const media = []
parentMedia.forEach(parentItem => {
childMedia.forEach(childItem => {
if (parentItem !== childItem) media.push(`${parentItem} and ${childItem}`)
})
})
return media
}

View File

@@ -0,0 +1,5 @@
"use strict"
const readCache = require("read-cache")
module.exports = filename => readCache(filename, "utf-8")

View File

@@ -0,0 +1,145 @@
"use strict"
// external tooling
const valueParser = require("postcss-value-parser")
// extended tooling
const { stringify } = valueParser
function split(params, start) {
const list = []
const last = params.reduce((item, node, index) => {
if (index < start) return ""
if (node.type === "div" && node.value === ",") {
list.push(item)
return ""
}
return item + stringify(node)
}, "")
list.push(last)
return list
}
module.exports = function (result, styles) {
const statements = []
let nodes = []
styles.each(node => {
let stmt
if (node.type === "atrule") {
if (node.name === "import") stmt = parseImport(result, node)
else if (node.name === "media") stmt = parseMedia(result, node)
else if (node.name === "charset") stmt = parseCharset(result, node)
}
if (stmt) {
if (nodes.length) {
statements.push({
type: "nodes",
nodes,
media: [],
})
nodes = []
}
statements.push(stmt)
} else nodes.push(node)
})
if (nodes.length) {
statements.push({
type: "nodes",
nodes,
media: [],
})
}
return statements
}
function parseMedia(result, atRule) {
const params = valueParser(atRule.params).nodes
return {
type: "media",
node: atRule,
media: split(params, 0),
}
}
function parseCharset(result, atRule) {
if (atRule.prev()) {
return result.warn("@charset must precede all other statements", {
node: atRule,
})
}
return {
type: "charset",
node: atRule,
media: [],
}
}
function parseImport(result, atRule) {
let prev = atRule.prev()
if (prev) {
do {
if (
prev.type !== "comment" &&
(prev.type !== "atrule" ||
(prev.name !== "import" && prev.name !== "charset"))
) {
return result.warn(
"@import must precede all other statements (besides @charset)",
{ node: atRule }
)
}
prev = prev.prev()
} while (prev)
}
if (atRule.nodes) {
return result.warn(
"It looks like you didn't end your @import statement correctly. " +
"Child nodes are attached to it.",
{ node: atRule }
)
}
const params = valueParser(atRule.params).nodes
const stmt = {
type: "import",
node: atRule,
media: [],
}
// prettier-ignore
if (
!params.length ||
(
params[0].type !== "string" ||
!params[0].value
) &&
(
params[0].type !== "function" ||
params[0].value !== "url" ||
!params[0].nodes.length ||
!params[0].nodes[0].value
)
) {
return result.warn(`Unable to find uri in '${ atRule.toString() }'`, {
node: atRule,
})
}
if (params[0].type === "string") stmt.uri = params[0].value
else stmt.uri = params[0].nodes[0].value
stmt.fullUri = stringify(params[0])
if (params.length > 2) {
if (params[1].type !== "space") {
return result.warn("Invalid import media statement", { node: atRule })
}
stmt.media = split(params, 2)
}
return stmt
}

View File

@@ -0,0 +1,59 @@
"use strict"
// builtin tooling
const path = require("path")
// placeholder tooling
let sugarss
module.exports = function processContent(
result,
content,
filename,
options,
postcss
) {
const { plugins } = options
const ext = path.extname(filename)
const parserList = []
// SugarSS support:
if (ext === ".sss") {
if (!sugarss) {
try {
sugarss = require("sugarss")
} catch {} // Ignore
}
if (sugarss)
return runPostcss(postcss, content, filename, plugins, [sugarss])
}
// Syntax support:
if (result.opts.syntax && result.opts.syntax.parse) {
parserList.push(result.opts.syntax.parse)
}
// Parser support:
if (result.opts.parser) parserList.push(result.opts.parser)
// Try the default as a last resort:
parserList.push(null)
return runPostcss(postcss, content, filename, plugins, parserList)
}
function runPostcss(postcss, content, filename, plugins, parsers, index) {
if (!index) index = 0
return postcss(plugins)
.process(content, {
from: filename,
parser: parsers[index],
})
.catch(err => {
// If there's an error, try the next parser
index++
// If there are no parsers left, throw it
if (index === parsers.length) throw err
return runPostcss(postcss, content, filename, plugins, parsers, index)
})
}

View File

@@ -0,0 +1,42 @@
"use strict"
// external tooling
const resolve = require("resolve")
const moduleDirectories = ["web_modules", "node_modules"]
function resolveModule(id, opts) {
return new Promise((res, rej) => {
resolve(id, opts, (err, path) => (err ? rej(err) : res(path)))
})
}
module.exports = function (id, base, options) {
const paths = options.path
const resolveOpts = {
basedir: base,
moduleDirectory: moduleDirectories.concat(options.addModulesDirectories),
paths,
extensions: [".css"],
packageFilter: function processPackage(pkg) {
if (pkg.style) pkg.main = pkg.style
else if (!pkg.main || !/\.css$/.test(pkg.main)) pkg.main = "index.css"
return pkg
},
preserveSymlinks: false,
}
return resolveModule(`./${id}`, resolveOpts)
.catch(() => resolveModule(id, resolveOpts))
.catch(() => {
if (paths.indexOf(base) === -1) paths.unshift(base)
throw new Error(
`Failed to find '${id}'
in [
${paths.join(",\n ")}
]`
)
})
}

View File

@@ -0,0 +1,22 @@
Copyright (c) Bogdan Chadkin <trysound@yandex.ru>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,263 @@
# postcss-value-parser
[![Travis CI](https://travis-ci.org/TrySound/postcss-value-parser.svg)](https://travis-ci.org/TrySound/postcss-value-parser)
Transforms CSS declaration values and at-rule parameters into a tree of nodes, and provides a simple traversal API.
## Usage
```js
var valueParser = require('postcss-value-parser');
var cssBackgroundValue = 'url(foo.png) no-repeat 40px 73%';
var parsedValue = valueParser(cssBackgroundValue);
// parsedValue exposes an API described below,
// e.g. parsedValue.walk(..), parsedValue.toString(), etc.
```
For example, parsing the value `rgba(233, 45, 66, .5)` will return the following:
```js
{
nodes: [
{
type: 'function',
value: 'rgba',
before: '',
after: '',
nodes: [
{ type: 'word', value: '233' },
{ type: 'div', value: ',', before: '', after: ' ' },
{ type: 'word', value: '45' },
{ type: 'div', value: ',', before: '', after: ' ' },
{ type: 'word', value: '66' },
{ type: 'div', value: ',', before: ' ', after: '' },
{ type: 'word', value: '.5' }
]
}
]
}
```
If you wanted to convert each `rgba()` value in `sourceCSS` to a hex value, you could do so like this:
```js
var valueParser = require('postcss-value-parser');
var parsed = valueParser(sourceCSS);
// walk() will visit all the of the nodes in the tree,
// invoking the callback for each.
parsed.walk(function (node) {
// Since we only want to transform rgba() values,
// we can ignore anything else.
if (node.type !== 'function' && node.value !== 'rgba') return;
// We can make an array of the rgba() arguments to feed to a
// convertToHex() function
var color = node.nodes.filter(function (node) {
return node.type === 'word';
}).map(function (node) {
return Number(node.value);
}); // [233, 45, 66, .5]
// Now we will transform the existing rgba() function node
// into a word node with the hex value
node.type = 'word';
node.value = convertToHex(color);
})
parsed.toString(); // #E92D42
```
## Nodes
Each node is an object with these common properties:
- **type**: The type of node (`word`, `string`, `div`, `space`, `comment`, or `function`).
Each type is documented below.
- **value**: Each node has a `value` property; but what exactly `value` means
is specific to the node type. Details are documented for each type below.
- **sourceIndex**: The starting index of the node within the original source
string. For example, given the source string `10px 20px`, the `word` node
whose value is `20px` will have a `sourceIndex` of `5`.
### word
The catch-all node type that includes keywords (e.g. `no-repeat`),
quantities (e.g. `20px`, `75%`, `1.5`), and hex colors (e.g. `#e6e6e6`).
Node-specific properties:
- **value**: The "word" itself.
### string
A quoted string value, e.g. `"something"` in `content: "something";`.
Node-specific properties:
- **value**: The text content of the string.
- **quote**: The quotation mark surrounding the string, either `"` or `'`.
- **unclosed**: `true` if the string was not closed properly. e.g. `"unclosed string `.
### div
A divider, for example
- `,` in `animation-duration: 1s, 2s, 3s`
- `/` in `border-radius: 10px / 23px`
- `:` in `(min-width: 700px)`
Node-specific properties:
- **value**: The divider character. Either `,`, `/`, or `:` (see examples above).
- **before**: Whitespace before the divider.
- **after**: Whitespace after the divider.
### space
Whitespace used as a separator, e.g. ` ` occurring twice in `border: 1px solid black;`.
Node-specific properties:
- **value**: The whitespace itself.
### comment
A CSS comment starts with `/*` and ends with `*/`
Node-specific properties:
- **value**: The comment value without `/*` and `*/`
- **unclosed**: `true` if the comment was not closed properly. e.g. `/* comment without an end `.
### function
A CSS function, e.g. `rgb(0,0,0)` or `url(foo.bar)`.
Function nodes have nodes nested within them: the function arguments.
Additional properties:
- **value**: The name of the function, e.g. `rgb` in `rgb(0,0,0)`.
- **before**: Whitespace after the opening parenthesis and before the first argument,
e.g. ` ` in `rgb( 0,0,0)`.
- **after**: Whitespace before the closing parenthesis and after the last argument,
e.g. ` ` in `rgb(0,0,0 )`.
- **nodes**: More nodes representing the arguments to the function.
- **unclosed**: `true` if the parentheses was not closed properly. e.g. `( unclosed-function `.
Media features surrounded by parentheses are considered functions with an
empty value. For example, `(min-width: 700px)` parses to these nodes:
```js
[
{
type: 'function', value: '', before: '', after: '',
nodes: [
{ type: 'word', value: 'min-width' },
{ type: 'div', value: ':', before: '', after: ' ' },
{ type: 'word', value: '700px' }
]
}
]
```
`url()` functions can be parsed a little bit differently depending on
whether the first character in the argument is a quotation mark.
`url( /gfx/img/bg.jpg )` parses to:
```js
{ type: 'function', sourceIndex: 0, value: 'url', before: ' ', after: ' ', nodes: [
{ type: 'word', sourceIndex: 5, value: '/gfx/img/bg.jpg' }
] }
```
`url( "/gfx/img/bg.jpg" )`, on the other hand, parses to:
```js
{ type: 'function', sourceIndex: 0, value: 'url', before: ' ', after: ' ', nodes: [
type: 'string', sourceIndex: 5, quote: '"', value: '/gfx/img/bg.jpg' },
] }
```
### unicode-range
The unicode-range CSS descriptor sets the specific range of characters to be
used from a font defined by @font-face and made available
for use on the current page (`unicode-range: U+0025-00FF`).
Node-specific properties:
- **value**: The "unicode-range" itself.
## API
```
var valueParser = require('postcss-value-parser');
```
### valueParser.unit(quantity)
Parses `quantity`, distinguishing the number from the unit. Returns an object like the following:
```js
// Given 2rem
{
number: '2',
unit: 'rem'
}
```
If the `quantity` argument cannot be parsed as a number, returns `false`.
*This function does not parse complete values*: you cannot pass it `1px solid black` and expect `px` as
the unit. Instead, you should pass it single quantities only. Parse `1px solid black`, then pass it
the stringified `1px` node (a `word` node) to parse the number and unit.
### valueParser.stringify(nodes[, custom])
Stringifies a node or array of nodes.
The `custom` function is called for each `node`; return a string to override the default behaviour.
### valueParser.walk(nodes, callback[, bubble])
Walks each provided node, recursively walking all descendent nodes within functions.
Returning `false` in the `callback` will prevent traversal of descendent nodes (within functions).
You can use this feature to for shallow iteration, walking over only the *immediate* children.
*Note: This only applies if `bubble` is `false` (which is the default).*
By default, the tree is walked from the outermost node inwards.
To reverse the direction, pass `true` for the `bubble` argument.
The `callback` is invoked with three arguments: `callback(node, index, nodes)`.
- `node`: The current node.
- `index`: The index of the current node.
- `nodes`: The complete nodes array passed to `walk()`.
Returns the `valueParser` instance.
### var parsed = valueParser(value)
Returns the parsed node tree.
### parsed.nodes
The array of nodes.
### parsed.toString()
Stringifies the node tree.
### parsed.walk(callback[, bubble])
Walks each node inside `parsed.nodes`. See the documentation for `valueParser.walk()` above.
# License
MIT © [Bogdan Chadkin](mailto:trysound@yandex.ru)

View File

@@ -0,0 +1,172 @@
declare namespace postcssValueParser {
interface BaseNode {
/**
* The offset inside the CSS value at which the node starts
*/
sourceIndex: number;
/**
* The node's characteristic value
*/
value: string;
}
interface ClosableNode {
/**
* Whether the parsed CSS value ended before the node was properly closed
*/
unclosed?: true;
}
interface AdjacentAwareNode {
/**
* The token at the start of the node
*/
before: string;
/**
* The token at the end of the node
*/
after: string;
}
interface CommentNode extends BaseNode, ClosableNode {
type: "comment";
}
interface DivNode extends BaseNode, AdjacentAwareNode {
type: "div";
}
interface FunctionNode extends BaseNode, ClosableNode, AdjacentAwareNode {
type: "function";
/**
* Nodes inside the function
*/
nodes: Node[];
}
interface SpaceNode extends BaseNode {
type: "space";
}
interface StringNode extends BaseNode, ClosableNode {
type: "string";
/**
* The quote type delimiting the string
*/
quote: '"' | "'";
}
interface UnicodeRangeNode extends BaseNode {
type: "unicode-range";
}
interface WordNode extends BaseNode {
type: "word";
}
/**
* Any node parsed from a CSS value
*/
type Node =
| CommentNode
| DivNode
| FunctionNode
| SpaceNode
| StringNode
| UnicodeRangeNode
| WordNode;
interface CustomStringifierCallback {
/**
* @param node The node to stringify
* @returns The serialized CSS representation of the node
*/
(nodes: Node): string | undefined;
}
interface WalkCallback {
/**
* @param node The currently visited node
* @param index The index of the node in the series of parsed nodes
* @param nodes The series of parsed nodes
* @returns Returning `false` will prevent traversal of descendant nodes (only applies if `bubble` was set to `true` in the `walk()` call)
*/
(node: Node, index: number, nodes: Node[]): void | boolean;
}
/**
* A CSS dimension, decomposed into its numeric and unit parts
*/
interface Dimension {
number: string;
unit: string;
}
/**
* A wrapper around a parsed CSS value that allows for inspecting and walking nodes
*/
interface ParsedValue {
/**
* The series of parsed nodes
*/
nodes: Node[];
/**
* Walk all parsed nodes, applying a callback
*
* @param callback A visitor callback that will be executed for each node
* @param bubble When set to `true`, walking will be done inside-out instead of outside-in
*/
walk(callback: WalkCallback, bubble?: boolean): this;
}
interface ValueParser {
/**
* Decompose a CSS dimension into its numeric and unit part
*
* @param value The dimension to decompose
* @returns An object representing `number` and `unit` part of the dimension or `false` if the decomposing fails
*/
unit(value: string): Dimension | false;
/**
* Serialize a series of nodes into a CSS value
*
* @param nodes The nodes to stringify
* @param custom A custom stringifier callback
* @returns The generated CSS value
*/
stringify(nodes: Node | Node[], custom?: CustomStringifierCallback): string;
/**
* Walk a series of nodes, applying a callback
*
* @param nodes The nodes to walk
* @param callback A visitor callback that will be executed for each node
* @param bubble When set to `true`, walking will be done inside-out instead of outside-in
*/
walk(nodes: Node[], callback: WalkCallback, bubble?: boolean): void;
/**
* Parse a CSS value into a series of nodes to operate on
*
* @param value The value to parse
*/
new (value: string): ParsedValue;
/**
* Parse a CSS value into a series of nodes to operate on
*
* @param value The value to parse
*/
(value: string): ParsedValue;
}
}
declare const postcssValueParser: postcssValueParser.ValueParser;
export = postcssValueParser;

View File

@@ -0,0 +1,28 @@
var parse = require("./parse");
var walk = require("./walk");
var stringify = require("./stringify");
function ValueParser(value) {
if (this instanceof ValueParser) {
this.nodes = parse(value);
return this;
}
return new ValueParser(value);
}
ValueParser.prototype.toString = function() {
return Array.isArray(this.nodes) ? stringify(this.nodes) : "";
};
ValueParser.prototype.walk = function(cb, bubble) {
walk(this.nodes, cb, bubble);
return this;
};
ValueParser.unit = require("./unit");
ValueParser.walk = walk;
ValueParser.stringify = stringify;
module.exports = ValueParser;

View File

@@ -0,0 +1,304 @@
var openParentheses = "(".charCodeAt(0);
var closeParentheses = ")".charCodeAt(0);
var singleQuote = "'".charCodeAt(0);
var doubleQuote = '"'.charCodeAt(0);
var backslash = "\\".charCodeAt(0);
var slash = "/".charCodeAt(0);
var comma = ",".charCodeAt(0);
var colon = ":".charCodeAt(0);
var star = "*".charCodeAt(0);
var uLower = "u".charCodeAt(0);
var uUpper = "U".charCodeAt(0);
var plus = "+".charCodeAt(0);
var isUnicodeRange = /^[a-f0-9?-]+$/i;
module.exports = function(input) {
var tokens = [];
var value = input;
var next,
quote,
prev,
token,
escape,
escapePos,
whitespacePos,
parenthesesOpenPos;
var pos = 0;
var code = value.charCodeAt(pos);
var max = value.length;
var stack = [{ nodes: tokens }];
var balanced = 0;
var parent;
var name = "";
var before = "";
var after = "";
while (pos < max) {
// Whitespaces
if (code <= 32) {
next = pos;
do {
next += 1;
code = value.charCodeAt(next);
} while (code <= 32);
token = value.slice(pos, next);
prev = tokens[tokens.length - 1];
if (code === closeParentheses && balanced) {
after = token;
} else if (prev && prev.type === "div") {
prev.after = token;
} else if (
code === comma ||
code === colon ||
(code === slash &&
value.charCodeAt(next + 1) !== star &&
(!parent ||
(parent && parent.type === "function" && parent.value !== "calc")))
) {
before = token;
} else {
tokens.push({
type: "space",
sourceIndex: pos,
value: token
});
}
pos = next;
// Quotes
} else if (code === singleQuote || code === doubleQuote) {
next = pos;
quote = code === singleQuote ? "'" : '"';
token = {
type: "string",
sourceIndex: pos,
quote: quote
};
do {
escape = false;
next = value.indexOf(quote, next + 1);
if (~next) {
escapePos = next;
while (value.charCodeAt(escapePos - 1) === backslash) {
escapePos -= 1;
escape = !escape;
}
} else {
value += quote;
next = value.length - 1;
token.unclosed = true;
}
} while (escape);
token.value = value.slice(pos + 1, next);
tokens.push(token);
pos = next + 1;
code = value.charCodeAt(pos);
// Comments
} else if (code === slash && value.charCodeAt(pos + 1) === star) {
token = {
type: "comment",
sourceIndex: pos
};
next = value.indexOf("*/", pos);
if (next === -1) {
token.unclosed = true;
next = value.length;
}
token.value = value.slice(pos + 2, next);
tokens.push(token);
pos = next + 2;
code = value.charCodeAt(pos);
// Operation within calc
} else if (
(code === slash || code === star) &&
parent &&
parent.type === "function" &&
parent.value === "calc"
) {
token = value[pos];
tokens.push({
type: "word",
sourceIndex: pos - before.length,
value: token
});
pos += 1;
code = value.charCodeAt(pos);
// Dividers
} else if (code === slash || code === comma || code === colon) {
token = value[pos];
tokens.push({
type: "div",
sourceIndex: pos - before.length,
value: token,
before: before,
after: ""
});
before = "";
pos += 1;
code = value.charCodeAt(pos);
// Open parentheses
} else if (openParentheses === code) {
// Whitespaces after open parentheses
next = pos;
do {
next += 1;
code = value.charCodeAt(next);
} while (code <= 32);
parenthesesOpenPos = pos;
token = {
type: "function",
sourceIndex: pos - name.length,
value: name,
before: value.slice(parenthesesOpenPos + 1, next)
};
pos = next;
if (name === "url" && code !== singleQuote && code !== doubleQuote) {
next -= 1;
do {
escape = false;
next = value.indexOf(")", next + 1);
if (~next) {
escapePos = next;
while (value.charCodeAt(escapePos - 1) === backslash) {
escapePos -= 1;
escape = !escape;
}
} else {
value += ")";
next = value.length - 1;
token.unclosed = true;
}
} while (escape);
// Whitespaces before closed
whitespacePos = next;
do {
whitespacePos -= 1;
code = value.charCodeAt(whitespacePos);
} while (code <= 32);
if (parenthesesOpenPos < whitespacePos) {
if (pos !== whitespacePos + 1) {
token.nodes = [
{
type: "word",
sourceIndex: pos,
value: value.slice(pos, whitespacePos + 1)
}
];
} else {
token.nodes = [];
}
if (token.unclosed && whitespacePos + 1 !== next) {
token.after = "";
token.nodes.push({
type: "space",
sourceIndex: whitespacePos + 1,
value: value.slice(whitespacePos + 1, next)
});
} else {
token.after = value.slice(whitespacePos + 1, next);
}
} else {
token.after = "";
token.nodes = [];
}
pos = next + 1;
code = value.charCodeAt(pos);
tokens.push(token);
} else {
balanced += 1;
token.after = "";
tokens.push(token);
stack.push(token);
tokens = token.nodes = [];
parent = token;
}
name = "";
// Close parentheses
} else if (closeParentheses === code && balanced) {
pos += 1;
code = value.charCodeAt(pos);
parent.after = after;
after = "";
balanced -= 1;
stack.pop();
parent = stack[balanced];
tokens = parent.nodes;
// Words
} else {
next = pos;
do {
if (code === backslash) {
next += 1;
}
next += 1;
code = value.charCodeAt(next);
} while (
next < max &&
!(
code <= 32 ||
code === singleQuote ||
code === doubleQuote ||
code === comma ||
code === colon ||
code === slash ||
code === openParentheses ||
(code === star &&
parent &&
parent.type === "function" &&
parent.value === "calc") ||
(code === slash &&
parent.type === "function" &&
parent.value === "calc") ||
(code === closeParentheses && balanced)
)
);
token = value.slice(pos, next);
if (openParentheses === code) {
name = token;
} else if (
(uLower === token.charCodeAt(0) || uUpper === token.charCodeAt(0)) &&
plus === token.charCodeAt(1) &&
isUnicodeRange.test(token.slice(2))
) {
tokens.push({
type: "unicode-range",
sourceIndex: pos,
value: token
});
} else {
tokens.push({
type: "word",
sourceIndex: pos,
value: token
});
}
pos = next;
}
}
for (pos = stack.length - 1; pos; pos -= 1) {
stack[pos].unclosed = true;
}
return stack[0].nodes;
};

View File

@@ -0,0 +1,48 @@
function stringifyNode(node, custom) {
var type = node.type;
var value = node.value;
var buf;
var customResult;
if (custom && (customResult = custom(node)) !== undefined) {
return customResult;
} else if (type === "word" || type === "space") {
return value;
} else if (type === "string") {
buf = node.quote || "";
return buf + value + (node.unclosed ? "" : buf);
} else if (type === "comment") {
return "/*" + value + (node.unclosed ? "" : "*/");
} else if (type === "div") {
return (node.before || "") + value + (node.after || "");
} else if (Array.isArray(node.nodes)) {
buf = stringify(node.nodes, custom);
if (type !== "function") {
return buf;
}
return (
value +
"(" +
(node.before || "") +
buf +
(node.after || "") +
(node.unclosed ? "" : ")")
);
}
return value;
}
function stringify(nodes, custom) {
var result, i;
if (Array.isArray(nodes)) {
result = "";
for (i = nodes.length - 1; ~i; i -= 1) {
result = stringifyNode(nodes[i], custom) + result;
}
return result;
}
return stringifyNode(nodes, custom);
}
module.exports = stringify;

View File

@@ -0,0 +1,120 @@
var minus = "-".charCodeAt(0);
var plus = "+".charCodeAt(0);
var dot = ".".charCodeAt(0);
var exp = "e".charCodeAt(0);
var EXP = "E".charCodeAt(0);
// Check if three code points would start a number
// https://www.w3.org/TR/css-syntax-3/#starts-with-a-number
function likeNumber(value) {
var code = value.charCodeAt(0);
var nextCode;
if (code === plus || code === minus) {
nextCode = value.charCodeAt(1);
if (nextCode >= 48 && nextCode <= 57) {
return true;
}
var nextNextCode = value.charCodeAt(2);
if (nextCode === dot && nextNextCode >= 48 && nextNextCode <= 57) {
return true;
}
return false;
}
if (code === dot) {
nextCode = value.charCodeAt(1);
if (nextCode >= 48 && nextCode <= 57) {
return true;
}
return false;
}
if (code >= 48 && code <= 57) {
return true;
}
return false;
}
// Consume a number
// https://www.w3.org/TR/css-syntax-3/#consume-number
module.exports = function(value) {
var pos = 0;
var length = value.length;
var code;
var nextCode;
var nextNextCode;
if (length === 0 || !likeNumber(value)) {
return false;
}
code = value.charCodeAt(pos);
if (code === plus || code === minus) {
pos++;
}
while (pos < length) {
code = value.charCodeAt(pos);
if (code < 48 || code > 57) {
break;
}
pos += 1;
}
code = value.charCodeAt(pos);
nextCode = value.charCodeAt(pos + 1);
if (code === dot && nextCode >= 48 && nextCode <= 57) {
pos += 2;
while (pos < length) {
code = value.charCodeAt(pos);
if (code < 48 || code > 57) {
break;
}
pos += 1;
}
}
code = value.charCodeAt(pos);
nextCode = value.charCodeAt(pos + 1);
nextNextCode = value.charCodeAt(pos + 2);
if (
(code === exp || code === EXP) &&
((nextCode >= 48 && nextCode <= 57) ||
((nextCode === plus || nextCode === minus) &&
nextNextCode >= 48 &&
nextNextCode <= 57))
) {
pos += nextCode === plus || nextCode === minus ? 3 : 2;
while (pos < length) {
code = value.charCodeAt(pos);
if (code < 48 || code > 57) {
break;
}
pos += 1;
}
}
return {
number: value.slice(0, pos),
unit: value.slice(pos)
};
};

View File

@@ -0,0 +1,22 @@
module.exports = function walk(nodes, cb, bubble) {
var i, max, node, result;
for (i = 0, max = nodes.length; i < max; i += 1) {
node = nodes[i];
if (!bubble) {
result = cb(node, i, nodes);
}
if (
result !== false &&
node.type === "function" &&
Array.isArray(node.nodes)
) {
walk(node.nodes, cb, bubble);
}
if (bubble) {
cb(node, i, nodes);
}
}
};