Skip to content

Commit b69d598

Browse files
committed
Initial commit
0 parents  commit b69d598

File tree

11 files changed

+535
-0
lines changed

11 files changed

+535
-0
lines changed

.eslintrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"env": {
3+
"node": true
4+
},
5+
"rules": {
6+
}
7+
}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
coverage

.travis.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
sudo: false
2+
language: node_js
3+
node_js:
4+
- "0.10"
5+
- "0.12"
6+
- "iojs"
7+
script: npm run travis
8+
9+
before_install:
10+
- '[ "${TRAVIS_NODE_VERSION}" != "0.10" ] || npm install -g npm'
11+
12+
after_success:
13+
- cat ./coverage/lcov.info | node_modules/.bin/coveralls --verbose
14+
- cat ./coverage/coverage.json | node_modules/codecov.io/bin/codecov.io.js
15+
- rm -rf ./coverage

README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# CSS Modules: CSS selector Tokenizer
2+
3+
Parses and stringifies CSS selectors.
4+
5+
``` js
6+
import Tokenizer from "css-selector-tokenizer";
7+
8+
let input = "a#content.active > div::first-line [data-content], a:visited";
9+
10+
Tokenizer.parse(input); // === expected
11+
let expected = {
12+
type: "selectors",
13+
nodes: [
14+
{
15+
type: "selector",
16+
nodes: [
17+
{ type: "element", name: "a" },
18+
{ type: "id", name: "content" },
19+
{ type: "class", name: "active" },
20+
{ type: "operator", name: ">", before: " ", after: " " },
21+
{ type: "element", name: "div" },
22+
{ type: "pseudo-element", name: "first-line" },
23+
{ type: "spacing", value: " " },
24+
{ type: "attribute", content: "data-content" },
25+
]
26+
},
27+
{
28+
type: "selector",
29+
nodes: [
30+
{ type: "element", name: "a" },
31+
{ type: "pseudo-class", name: "visited" }
32+
],
33+
before: " "
34+
}
35+
]
36+
}
37+
38+
Tokenizer.stringify(expected) // === input
39+
```
40+
41+
## Building
42+
43+
```
44+
npm install
45+
npm build
46+
npm test
47+
```
48+
49+
[![Build Status](https://travis-ci.org/css-modules/css-selector-tokenizer.svg?branch=master)](https://travis-ci.org/css-modules/css-selector-tokenizer)
50+
51+
* Lines: [![Coverage Status](https://coveralls.io/repos/css-modules/css-selector-tokenizer/badge.svg?branch=master)](https://coveralls.io/r/css-modules/css-selector-tokenizer?branch=master)
52+
* Statements: [![codecov.io](http://codecov.io/github/css-modules/css-selector-tokenizer/coverage.svg?branch=master)](http://codecov.io/github/css-modules/css-selector-tokenizer?branch=master)
53+
54+
## Development
55+
56+
- `npm watch` will watch `src` for changes and rebuild
57+
- `npm autotest` will watch `src` and `test` for changes and retest
58+
59+
## License
60+
61+
MIT
62+
63+
## With thanks
64+
65+
- Mark Dalgleish
66+
- Glen Maddern
67+
- Guy Bedford
68+
69+
---
70+
Tobias Koppers, 2015.

lib/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
exports.parse = require("./parse");
2+
exports.stringify = require("./stringify");

lib/parse.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"use strict";
2+
3+
var Parser = require("fastparse");
4+
5+
function commentMatch(match, content) {
6+
this.selector.nodes.push({
7+
type: "comment",
8+
content: content
9+
});
10+
}
11+
12+
function typeMatch(type) {
13+
return function(match, name) {
14+
this.selector.nodes.push({
15+
type: type,
16+
name: name
17+
});
18+
};
19+
}
20+
21+
function pseudoClassStartMatch(match, name) {
22+
var newToken = {
23+
type: "pseudo-class",
24+
name: name,
25+
content: ""
26+
};
27+
this.selector.nodes.push(newToken);
28+
this.token = newToken;
29+
this.brackets = 1;
30+
return "inBrackets";
31+
}
32+
33+
function operatorMatch(match, before, operator, after) {
34+
var token = {
35+
type: "operator",
36+
operator: operator
37+
};
38+
if(before) {
39+
token.before = before;
40+
}
41+
if(after) {
42+
token.after = after;
43+
}
44+
this.selector.nodes.push(token);
45+
}
46+
47+
function spacingMatch(match) {
48+
this.selector.nodes.push({
49+
type: "spacing",
50+
value: match
51+
});
52+
}
53+
54+
function allMatch() {
55+
this.selector.nodes.push({
56+
type: "all"
57+
});
58+
}
59+
60+
function attributeMatch(match, content) {
61+
this.selector.nodes.push({
62+
type: "attribute",
63+
content: content
64+
});
65+
}
66+
67+
function irrelevantSpacingStartMatch(match) {
68+
this.selector.before = match;
69+
}
70+
71+
function irrelevantSpacingEndMatch(match) {
72+
this.selector.after = match;
73+
}
74+
75+
function nextSelectorMatch(match, before, after) {
76+
var newSelector = {
77+
type: "selector",
78+
nodes: []
79+
};
80+
if(before) {
81+
this.selector.after = before;
82+
}
83+
if(after) {
84+
newSelector.before = after;
85+
}
86+
this.root.nodes.push(newSelector);
87+
this.selector = newSelector;
88+
}
89+
90+
function addToCurrent(match) {
91+
this.token.content += match;
92+
}
93+
94+
function bracketStart(match) {
95+
this.token.content += match;
96+
this.brackets++;
97+
}
98+
99+
function bracketEnd(match) {
100+
if(--this.brackets === 0) {
101+
return "selector";
102+
}
103+
this.token.content += match;
104+
}
105+
106+
var parser = new Parser({
107+
selector: {
108+
"/\\*([\\s\\S]*?)\\*/": commentMatch,
109+
"\\.([A-Za-z_\\-0-9]+)": typeMatch("class"),
110+
"#([A-Za-z_\\-0-9]+)": typeMatch("id"),
111+
":([A-Za-z_\\-0-9]+)\\(": pseudoClassStartMatch,
112+
":([A-Za-z_\\-0-9]+)": typeMatch("pseudo-class"),
113+
"::([A-Za-z_\\-0-9]+)": typeMatch("pseudo-element"),
114+
"([A-Za-z_\\-0-9]+)": typeMatch("element"),
115+
"\\[([^\\]]+)\\]": attributeMatch,
116+
"\\*": allMatch,
117+
"(\\s*)([>+~])(\\s*)": operatorMatch,
118+
"(\\s*),(\\s*)": nextSelectorMatch,
119+
"\\s+$": irrelevantSpacingEndMatch,
120+
"^\\s+": irrelevantSpacingStartMatch,
121+
"\\s+": spacingMatch
122+
},
123+
inBrackets: {
124+
"/\\*[\\s\\S]*?\\*/": addToCurrent,
125+
"\"([^\\\\\"]|\\\\.)*\"": addToCurrent,
126+
"'([^\\\\']|\\\\.)*'": addToCurrent,
127+
"[^()'\"/]+": addToCurrent,
128+
"\\(": bracketStart,
129+
"\\)": bracketEnd,
130+
".": addToCurrent
131+
}
132+
});
133+
134+
function parse(str) {
135+
var selectorNode = {
136+
type: "selector",
137+
nodes: []
138+
};
139+
var result = parser.parse("selector", str, {
140+
root: {
141+
type: "selectors",
142+
nodes: [
143+
selectorNode
144+
]
145+
},
146+
selector: selectorNode
147+
});
148+
return result.root;
149+
}
150+
151+
module.exports = parse;

lib/stringify.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"use strict";
2+
3+
var stringify;
4+
5+
function stringifyWithoutBeforeAfter(tree) {
6+
switch(tree.type) {
7+
case "selectors":
8+
return tree.nodes.map(stringify).join(",");
9+
case "selector":
10+
return tree.nodes.map(stringify).join("");
11+
case "element":
12+
return tree.name;
13+
case "class":
14+
return "." + tree.name;
15+
case "id":
16+
return "#" + tree.name;
17+
case "attribute":
18+
return "[" + tree.content + "]";
19+
case "spacing":
20+
return tree.value;
21+
case "pseudo-class":
22+
return ":" + tree.name + (typeof tree.content === "string" ? "(" + tree.content + ")" : "");
23+
case "pseudo-element":
24+
return "::" + tree.name;
25+
case "all":
26+
return "*";
27+
case "operator":
28+
return tree.operator;
29+
case "comment":
30+
return "/*" + tree.content + "*/";
31+
}
32+
}
33+
34+
35+
stringify = function stringify(tree) {
36+
var str = stringifyWithoutBeforeAfter(tree);
37+
if(tree.before) {
38+
str = tree.before + str;
39+
}
40+
if(tree.after) {
41+
str = str + tree.after;
42+
}
43+
return str;
44+
};
45+
46+
module.exports = stringify;

package.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "css-selector-tokenizer",
3+
"version": "0.1.0",
4+
"description": "Parses and stringifies CSS selectors",
5+
"main": "lib/index.js",
6+
"scripts": {
7+
"lint": "eslint lib",
8+
"pretest": "npm run lint",
9+
"test": "mocha",
10+
"autotest": "chokidar lib test -c 'npm test'",
11+
"precover": "npm run lint",
12+
"cover": "istanbul cover node_modules/mocha/bin/_mocha",
13+
"travis": "npm run cover -- --report lcovonly",
14+
"publish-patch": "npm version patch && git push && git push --tags && npm publish"
15+
},
16+
"repository": {
17+
"type": "git",
18+
"url": "https://github.com/css-modules/css-selector-tokenizer.git"
19+
},
20+
"keywords": [
21+
"css-modules",
22+
"selectors"
23+
],
24+
"files": [
25+
"lib"
26+
],
27+
"author": "Tobias Koppers @sokra",
28+
"license": "MIT",
29+
"bugs": {
30+
"url": "https://github.com/css-modules/css-selector-tokenizer/issues"
31+
},
32+
"homepage": "https://github.com/css-modules/css-selector-tokenizer",
33+
"dependencies": {
34+
"fastparse": "^1.1.1"
35+
},
36+
"devDependencies": {
37+
"chokidar-cli": "^0.2.1",
38+
"codecov.io": "^0.1.2",
39+
"coveralls": "^2.11.2",
40+
"eslint": "^0.21.2",
41+
"istanbul": "^0.3.14",
42+
"mocha": "^2.2.5"
43+
},
44+
"directories": {
45+
"test": "test"
46+
}
47+
}

test/parse.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"use strict";
2+
3+
/*globals describe it */
4+
5+
var assert = require("assert");
6+
var Tokenizer = require("../");
7+
8+
describe("parse", function() {
9+
var testCases = require("./test-cases");
10+
Object.keys(testCases).forEach(function(testCase) {
11+
it("should parse " + testCase, function() {
12+
var input = testCases[testCase][0];
13+
var expected = testCases[testCase][1];
14+
assert.deepEqual(Tokenizer.parse(input), expected);
15+
});
16+
});
17+
});

test/stringify.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"use strict";
2+
3+
/*globals describe it */
4+
5+
var assert = require("assert");
6+
var Tokenizer = require("../");
7+
8+
describe("stringify", function() {
9+
var testCases = require("./test-cases");
10+
Object.keys(testCases).forEach(function(testCase) {
11+
it("should stringify " + testCase, function() {
12+
var input = testCases[testCase][1];
13+
var expected = testCases[testCase][0];
14+
assert.deepEqual(Tokenizer.stringify(input), expected);
15+
});
16+
});
17+
});

0 commit comments

Comments
 (0)