Skip to content

Commit

Permalink
Rewrite Parser in TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
izolate committed Sep 2, 2019
1 parent d78d580 commit 01fb608
Show file tree
Hide file tree
Showing 12 changed files with 761 additions and 267 deletions.
18 changes: 0 additions & 18 deletions .eslintrc.yml

This file was deleted.

49 changes: 49 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"parse5": "^5.1.0"
},
"devDependencies": {
"@types/html-minifier": "^3.5.3",
"@types/parse5": "^5.0.2",
"ava": "^2.3.0",
"husky": "^3.0.5",
"lint-staged": "^9.2.5",
Expand All @@ -40,8 +42,8 @@
]
},
"lint-staged": {
"*.js": [
"npm run lint:fix",
"*.ts": [
"npm run lint",
"git add"
]
},
Expand Down
43 changes: 43 additions & 0 deletions src/Attribute.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import test from 'ava';
import Attribute from './Attribute';

test('stringifies an ID', async t => {
const attr = new Attribute('id', 'foo');
t.is(attr.toString(), '#foo');
});

test('stringifies a single class selector', async t => {
const attr = new Attribute('class', 'foo');
t.is(attr.toString(), '.foo');
});

test('stringifies multiple class selectors', async t => {
const attr = new Attribute('class', 'foo bar baz qux');
t.is(attr.toString(), '.foo.bar.baz.qux');
});

test('stringifies a generic attribute', async t => {
const attr = new Attribute('type', 'number');
t.is(attr.toString(), "type='number'");
});

test('changes quote style', async t => {
const attr = new Attribute('quote-style', 'double', true);
t.is(attr.toString(), 'quote-style="double"');
});

test('escapes single quotes from values', async t => {
const attr = new Attribute(
'style',
"background-image: url('/path/to/nowhere')",
);
t.is(
attr.toString(),
"style='background-image: url(\\'/path/to/nowhere\\')'",
);
});

test('omits value if blank', async t => {
const attr = new Attribute('required');
t.is(attr.toString(), 'required');
});
53 changes: 53 additions & 0 deletions src/Attribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Represents a single attribute on a [PugNode].
*/
class Attribute {
// Attribute name
public name: string;
// Attribute value
public value: string;
// Quote style for values
public doubleQuotes: boolean = false;

constructor(name: string, value: string, doubleQuotes?: boolean) {
this.name = name;
this.value = value;
if (doubleQuotes) {
this.doubleQuotes = doubleQuotes;
}
}

/**
* Returns a quote character based on quote style.
*/
private get quote() {
return this.doubleQuotes ? '"' : '\'';
}

/**
* Creates a string representation of the attribute.
* e.g. key="value"
*/
public toString(): string {
switch (this.name) {
case 'id':
return `#${this.value}`;

case 'class':
return `.${this.value.split(' ').join('.')}`;

default: {
// If value is blank, return just the name (shorthand)
if (!this.value) {
return this.name;
}
// Add escaped single quotes (\') to attribute values
// to allow for surrounding single quotes.
const safeValue: string = this.value.replace(/'/g, '\\\'');
return `${this.name}=${this.quote}${safeValue}${this.quote}`;
}
}
}
}

export default Attribute;
27 changes: 27 additions & 0 deletions src/Options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Defines all the options for html2pug.
*/
export interface Options {
caseSensitive: boolean;
collapseBooleanAttributes: boolean;
collapseWhitespace: boolean;
commas: boolean;
doubleQuotes: boolean;
fragment: boolean;
preserveLineBreaks: boolean;
removeEmptyAttributes: boolean;
tabs: boolean;
}

// Default options for html2pug.
export const defaultOptions = {
caseSensitive: true,
collapseBooleanAttributes: true,
collapseWhitespace: true,
commas: true,
doubleQuotes: false,
fragment: false,
preserveLineBreaks: true,
removeEmptyAttributes: true,
tabs: false,
};
45 changes: 45 additions & 0 deletions src/Parser.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import test from 'ava';
import Html2Pug from './index';

test('converts a single HTML element', async t => {
const pug = Html2Pug('<h1 class="title">Hello, world!</h1>', {
fragment: true,
});
t.is(pug, 'h1.title Hello, world!');
});

test('converts a nested HTML fragment', async t => {
const pug = Html2Pug(
'<ul id="fruits" class="list"><li class="item">Mango</li><li class="item">Apple</li></ul>',
{
fragment: true,
},
);
const expected = `ul#fruits.list
li.item Mango
li.item Apple`;
t.is(pug, expected);
});

test('removes whitespace between HTML elements', t => {
const pug = Html2Pug(
`<ul class="list">
<li>one</li>
<li>two</li>
<li>three</li>
<li>four</li>
</ul>`,
{ fragment: true },
);

const expected = `ul.list
li one
li two
li three
li four`;

t.is(pug, expected);
});
Loading

0 comments on commit 01fb608

Please sign in to comment.