Clearing out old site code

This commit is contained in:
Jeff Avallone 2018-02-10 13:33:13 -05:00
parent f78e388e20
commit ae2b7c74dd
83 changed files with 2 additions and 13194 deletions

View File

@ -1,3 +0,0 @@
{
"presets": ["es2015"]
}

6
.gitignore vendored
View File

@ -1,5 +1 @@
node_modules
.sass-cache
build
docs
tmp
node_modules/

68
.jscsrc
View File

@ -1,68 +0,0 @@
{
"requireCurlyBraces": [
"if",
"else",
"for",
"while",
"do",
"try",
"catch"
],
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"case",
"return",
"try",
"typeof"
],
"requireSpaceBeforeBlockStatements": true,
"requireParenthesesAroundIIFE": true,
"requireSpacesInConditionalExpression": true,
"disallowSpacesInNamedFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInFunctionDeclaration": {
"beforeOpeningRoundBrace": true
},
"requireSpaceBetweenArguments": true,
"requireMultipleVarDecl": "onevar",
"requireVarDeclFirst": true,
"requireBlocksOnNewline": true,
"disallowEmptyBlocks": true,
"disallowSpacesInsideArrayBrackets": true,
"disallowSpacesInsideParentheses": true,
"requireCommaBeforeLineBreak": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"disallowSpaceBeforePostfixUnaryOperators": true,
"disallowSpaceBeforeBinaryOperators": [
","
],
"requireSpacesInForStatement": true,
"requireSpacesInAnonymousFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"requireSpaceBeforeBinaryOperators": true,
"requireSpaceAfterBinaryOperators": true,
"disallowKeywords": [
"with",
"continue"
],
"validateIndentation": 2,
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
"disallowTrailingComma": true,
"disallowKeywordsOnNewLine": [
"else"
],
"requireLineFeedAtFileEnd": true,
"requireCapitalizedConstructors": true,
"requireDotNotation": true,
"disallowNewlineBeforeBlockStatements": true,
"disallowMultipleLineStrings": true,
"requireSpaceBeforeObjectValues": true
}

View File

@ -1,10 +1,4 @@
language: node_js
node_js:
- "node"
addons:
firefox: "latest"
before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 3
script: yarn test

View File

@ -1,4 +1,4 @@
# Regexper [![Build Status](https://travis-ci.org/javallone/regexper-static.svg?branch=master)](https://travis-ci.org/javallone/regexper-static)
# Regexper [![Build Status](https://travis-ci.org/javallone/regexper-static.svg?branch=react)](https://travis-ci.org/javallone/regexper-static)
Code for the http://regexper.com/ site.
@ -20,14 +20,6 @@ To start a development server, run:
$ yarn start
This will build the site into the ./build directory, start a local start on port 8080, and begin watching the source files for modifications. The site will automatically be rebuilt when files are changed. Also, if you browser has the LiveReload extension, then the page will be reloaded.
These other gulp tasks are available:
$ gulp docs # Build documentation into the ./docs directory
$ gulp build # Build the site into the ./build directory
$ yarn test # Run JSCS lint and Karma tests
## License
See [LICENSE.txt](/LICENSE.txt) file for licensing details.

View File

@ -1,31 +0,0 @@
var path = require('path'),
_ = require('lodash'),
buildRoot = process.env.BUILD_PATH || path.join(__dirname, './build'),
buildPath = _.bind(path.join, path, buildRoot);
module.exports = {
buildRoot: buildRoot,
buildPath: buildPath,
globs: {
other: './src/**/*.!(hbs|scss|js|peg)',
templates: './src/**/*.hbs',
data: ['./lib/data/**/*.json', './lib/data/**/*.js'],
helpers: './lib/helpers/**/*.js',
partials: './lib/partials/**/*.hbs',
sass: './src/**/*.scss',
svg_sass: './src/sass/svg.scss',
js: ['./src/**/*.js', './src/**/*.peg'],
spec: './spec/**/*_spec.js',
lint: [
'./lib/**/*.js',
'./src/**/*.js',
'./spec/**/*.js',
'./*.js'
]
},
lintRoots: ['lib', 'src', 'spec'],
browserify: {
debug: true,
fullPaths: false
}
};

View File

@ -1,101 +0,0 @@
const gulp = require('gulp-help')(require('gulp')),
_ = require('lodash'),
notify = require('gulp-notify'),
folderToc = require('folder-toc'),
docco = require('gulp-docco'),
connect = require('gulp-connect'),
hb = require('gulp-hb'),
frontMatter = require('gulp-front-matter'),
rename = require('gulp-rename'),
config = require('./config'),
gutil = require('gulp-util'),
webpack = require('webpack')
webpackConfig = require('./webpack.config'),
fs = require('fs');
gulp.task('default', 'Auto-rebuild site on changes.', ['server', 'docs'], function() {
gulp.watch(config.globs.other, ['static']);
gulp.watch(_.flatten([
config.globs.templates,
config.globs.data,
config.globs.helpers,
config.globs.partials,
config.globs.svg_sass
]), ['markup']);
gulp.watch(_.flatten([
config.globs.sass,
config.globs.js
]), ['webpack']);
gulp.watch(config.globs.js, ['docs']);
});
gulp.task('docs', 'Build documentation into ./docs directory.', ['docs:files'], function() {
folderToc('./docs', {
filter: '*.html'
});
});
gulp.task('docs:files', false, function() {
return gulp.src(config.globs.js)
.pipe(docco())
.pipe(gulp.dest('./docs'));
});
gulp.task('server', 'Start development server.', ['build'], function() {
gulp.watch(config.buildPath('**/*'), function(file) {
return gulp.src(file.path).pipe(connect.reload());
});
return connect.server({
root: config.buildRoot,
livereload: true
});
});
gulp.task('build', 'Build site into ./build directory.', ['static', 'webpack', 'markup']);
gulp.task('static', 'Build static files into ./build directory.', function() {
return gulp.src(config.globs.other, { base: './src' })
.pipe(gulp.dest(config.buildRoot));
});
gulp.task('markup', 'Build markup into ./build directory.', ['webpack'], function() {
var hbStream = hb({
data: config.globs.data,
helpers: config.globs.helpers,
partials: config.globs.partials,
parsePartialName: function(option, file) {
return _.last(file.path.split(/\\|\//)).replace('.hbs', '');
},
bustCache: true
});
hbStream.partials({
svg_styles: fs.readFileSync(config.buildRoot + '/css/svg.css').toString()
});
if (process.env.GA_PROP) {
hbStream.data({
'gaPropertyId': process.env.GA_PROP
});
}
if (process.env.SENTRY_KEY) {
hbStream.data({
'sentryKey': process.env.SENTRY_KEY
});
}
return gulp.src(config.globs.templates)
.pipe(frontMatter())
.pipe(hbStream)
.on('error', notify.onError())
.pipe(rename({ extname: '.html' }))
.pipe(gulp.dest(config.buildRoot));
});
gulp.task('webpack', 'Build JS & CSS into ./build directory.', function(callback) {
webpack(webpackConfig, function(err, stats) {
if (err) {
throw new gutil.PluginError('webpack', err);
}
gutil.log('[webpack]', stats.toString());
callback();
});
});

View File

@ -1,35 +0,0 @@
module.exports = function(karma) {
karma.set({
frameworks: ['jasmine'],
files: [ 'spec/test_index.js' ],
preprocessors: {
'spec/test_index.js': ['webpack', 'sourcemap']
},
reporters: ['progress', 'notify'],
colors: true,
logLevel: karma.LOG_INFO,
browsers: ['Firefox'],
autoWatch: true,
singleRun: false,
webpack: {
devtool: 'inline-source-map',
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: require.resolve('snapsvg'),
loader: 'imports-loader?this=>window,fix=>module.exports=0'
},
{
test: /\.peg$/,
loader: require.resolve('./lib/canopy-loader')
}
]
}
}
});
};

View File

@ -1,6 +0,0 @@
var canopy = require('canopy');
module.exports = function(source) {
this.cacheable();
return canopy.compile(source);
};

View File

@ -1,174 +0,0 @@
[
{
"label": "February 10, 2018 Release",
"changes": [
"Adding 'sticky' and 'unicode' flag support",
"Encoding parenthesis in the permalink and browser URLs",
"Adding PNG download support"
]
},
{
"label": "July 31, 2016 Release",
"changes": [
"Merged code to enable automated testing with Travis CI from <a href=\"https://github.com/Byron\">Sebastian Thiel</a>",
"Merged feature to show an informational tooltip on loop labels from <a href=\"https://github.com/ThibWeb\">Thibaud Colas</a>",
"Fixed issue with '^' and '$' not being allowed in the middle of a fragment (see <a href=\"https://github.com/javallone/regexper-static/issues/29\">GitHub issue</a>)",
"Updating several dependencies",
"Some stylistic code cleanup"
]
},
{
"label": "May 31, 2016 Release",
"changes": [
"Putting separate CSS for generated SVG images back into the build. Downloaded images have been broken since the March 10 release because the SVG styles were merged into the page styles."
]
},
{
"label": "May 23, 2016 Release",
"changes": [
"Refactored tracking code to support latest Google Analytics setup"
]
},
{
"label": "March 10, 2016 Release",
"changes": [
"Embedding SVG icon images into markup",
"Some changes for minor performance improvements",
"Updating several dependencies"
]
},
{
"label": "March 8, 2016 Release",
"changes": [
"Replaced icon font with individual SVG images"
]
},
{
"label": "March 3, 2016 Release",
"changes": [
"Merged some code cleanup and a bugfix from <a href=\"https://github.com/Byron\">Sebastian Thiel</a>",
"Updated notice for IE8 users to no longer include link to legacy site"
]
},
{
"label": "December 21, 2015 Release",
"changes": [
"Updating NPM dependencies to fix JS error that only appeared when running site from a local development environment (see <a href=\"https://github.com/javallone/regexper-static/issues/21\">GitHub issue</a>)"
]
},
{
"label": "November 10, 2015 Release",
"changes": [
"Fixing Babel integration to include polyfills"
]
},
{
"label": "November 1, 2015 Release",
"changes": [
"Switching from Compass to node-sass and Bourbon (no more need for Ruby)",
"Switching to Babel instead of es6ify",
"Improving sourcemap generation",
"Cleanup of the build process"
]
},
{
"label": "October 31, 2015 Release",
"changes": [
"Reducing file size for title font",
"Cleaning up gulpfile",
"Upgrading most dependencies",
"Switching to Handlebars for template rendering"
]
},
{
"label": "September 17, 2015 Release",
"changes": [
"Fixing styling of labels on repetitions",
"Fixing issue with vertical centering of alternation expressions that include empty expressions (see <a href=\"https://github.com/javallone/regexper-static/pull/16\">GitHub issue</a>)"
]
},
{
"label": "September 2, 2015 Release",
"changes": [
"Merging fix for error reporting from (see <a href=\"https://github.com/javallone/regexper-static/pull/15\">GitHub pull request</a>)"
]
},
{
"label": "July 5, 2015 Release",
"changes": [
"Updating Creative Commons license badge URL so it isn't pointing to a redirecting URL anymore"
]
},
{
"label": "June 22, 2015 Release",
"changes": [
"Tweaking buggy Firefox hash detection code based on JavaScript errors that were logged"
]
},
{
"label": "June 16, 2015 Release",
"changes": [
"Fixes issue with expressions containing a \"%\" not rendering in Firefox (see <a href=\"https://github.com/javallone/regexper-static/issues/12\">GitHub issue</a>)",
"Fixed rendering in IE that was causing \"--&gt;\" to display at the top of the page."
]
},
{
"label": "April 14, 2015 Release",
"changes": [
"Rendering speed improved. Most users will probably not see much improvement since logging data indicates that expressing rendering time is typically less than 1 second. Using the <a href=\"http://www.ex-parrot.com/pdw/Mail-RFC822-Address.html\">RFC822 email regular expression</a> though shows a rendering speed improvement from ~120 seconds down to ~80 seconds.",
"Fixing a bug that would only occur when attempting to render an expression while another is in the process of rendering"
]
},
{
"label": "March 14, 2015 Release",
"changes": [
"Removing use of Q for promises in favor of \"native\" ES6 promises (even though they aren't quite native everywhere yet)"
]
},
{
"label": "March 13, 2015 Release",
"changes": [
"Fixes bug with numbering of nested subexpressions (see <a href=\"https://github.com/javallone/regexper-static/issues/7\">GitHub issue</a>)"
]
},
{
"label": "February 11, 2015 Release",
"changes": [
"Various adjustments to analytics: tracking expression rendering time and JS errors",
"Escape sequences that match to a specific character now display their hexadecimal code (actually done on January 25, but I forgot to update the changelog)",
"Fixing styling issue with header links (see <a href=\"https://github.com/javallone/regexper-static/issues/5\">GitHub issue</a>)"
]
},
{
"label": "December 30, 2014 Release",
"changes": [
"Fixing bug that prevented rendering empty subexpressions",
"Fixing minor styling bug when permalink is disabled",
"Cleaning up some duplicated styles and JS"
]
},
{
"label": "December 29, 2014 Release",
"changes": [
"Tweaking analytics data to help with addressing issues in deployed code (work will likely continue on this)",
"Added progress bars on the documentation page",
"Removed the loading spinner everywhere",
"Animated the progress bars"
]
},
{
"label": "December 26, 2014 Release",
"changes": [
"Freshened up design",
"Multiline regular expression input field (press Shift-Enter to render)",
"Added a changelog",
"Added documentation",
"All parsing and rendering happens client-side (using <a href=\"http://canopy.jcoglan.com/\">Canopy</a> and <a href=\"http://snapsvg.io/\">Snap.svg</a>)",
"Added Download link (not available in older browsers)",
"Added display of regular expression flags (ignore case, global, multiline)",
"Added indicator of quantifier greedy-ness",
"Various improvements to parsing of regular expression",
"Rendering of a regular expression can be canceled by pressing Escape"
]
}
]

View File

@ -1 +0,0 @@
module.exports = new Date().toISOString();

View File

@ -1,5 +0,0 @@
module.exports.register = function(handlebars) {
handlebars.registerHelper('icon', function(selector, context) {
return new handlebars.SafeString(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 8 8"><use xlink:href="${selector}" /></svg>`);
});
};

View File

@ -1,5 +0,0 @@
var layouts = require('handlebars-layouts');
module.exports.register = function(handlebars) {
layouts.register(handlebars);
};

View File

@ -1,11 +0,0 @@
{{#if gaPropertyId}}
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '{{{gaPropertyId}}}', 'auto');
ga('send', 'pageview');
</script>
{{/if}}

View File

@ -1,68 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>Regexper{{#if file.frontMatter.title}} - {{file.frontMatter.title}}{{/if}}</title>
<meta name="description" content="Regular expression visualizer using railroad diagrams" />
<meta name="viewport" content="width=device-width" />
<meta name="theme-color" content="#bada55" />
{{> "google_analytics"}}
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="author" href="humans.txt" />
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Bangers&text=Regxpr" />
<link rel="stylesheet" href="/css/main.css" />
<!-- Built: {{date}} -->
</head>
<body>
<header>
<div class="logo">
<h1><a href="/">Regexper</a></h1>
<!-- n. One who regexpes -->
<span>You thought you only had two problems&hellip;</span>
</div>
<nav>
<ul>
<li>
<a class="inline-icon" href="/changelog.html">{{icon "#list-rich"}}Changelog</a>
</li>
<li>
<a class="inline-icon" href="/documentation.html">{{icon "#document"}}Documentation</a>
</li>
<li>
<a class="inline-icon" href="https://github.com/javallone/regexper-static">{{icon "#code"}}Source on GitHub</a>
</li>
</ul>
</nav>
</header>
<main id="content">
{{#block "body"}}{{/block}}
</main>
<footer>
{{#block "footer"}}
<ul class="inline-list">
<li>Created by <a href="mailto:jeff.avallone@gmail.com">Jeff Avallone</a></li>
<li>
Generated images licensed:
<a rel="license" href="http://creativecommons.org/licenses/by/3.0/"><img alt="Creative Commons License" src="https://licensebuttons.net/l/by/3.0/80x15.png" /></a>
</li>
</ul>
<script type="text/html" id="svg-container-base">
{{> "svg_template"}}
</script>
{{> "sentry"}}
{{/block}}
</footer>
{{> "open_iconic"}}
</body>
</html>

View File

@ -1,23 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" id="open-iconic">
<!-- These icon are from the Open Iconic project https://useiconic.com/open/ -->
<defs>
<g id="code">
<path d="M5 0l-3 6h1l3-6h-1zm-4 1l-1 2 1 2h1l-1-2 1-2h-1zm5 0l1 2-1 2h1l1-2-1-2h-1z" transform="translate(0 1)" />
</g>
<g id="data-transfer-download">
<path d="M3 0v3h-2l3 3 3-3h-2v-3h-2zm-3 7v1h8v-1h-8z" />
</g>
<g id="document">
<path d="M0 0v8h7v-4h-4v-4h-3zm4 0v3h3l-3-3zm-3 2h1v1h-1v-1zm0 2h1v1h-1v-1zm0 2h4v1h-4v-1z" />
</g>
<g id="link-intact">
<path d="M5.88.03c-.18.01-.36.03-.53.09-.27.1-.53.25-.75.47a.5.5 0 1 0 .69.69c.11-.11.24-.17.38-.22.35-.12.78-.07 1.06.22.39.39.39 1.04 0 1.44l-1.5 1.5c-.44.44-.8.48-1.06.47-.26-.01-.41-.13-.41-.13a.5.5 0 1 0-.5.88s.34.22.84.25c.5.03 1.2-.16 1.81-.78l1.5-1.5c.78-.78.78-2.04 0-2.81-.28-.28-.61-.45-.97-.53-.18-.04-.38-.04-.56-.03zm-2 2.31c-.5-.02-1.19.15-1.78.75l-1.5 1.5c-.78.78-.78 2.04 0 2.81.56.56 1.36.72 2.06.47.27-.1.53-.25.75-.47a.5.5 0 1 0-.69-.69c-.11.11-.24.17-.38.22-.35.12-.78.07-1.06-.22-.39-.39-.39-1.04 0-1.44l1.5-1.5c.4-.4.75-.45 1.03-.44.28.01.47.09.47.09a.5.5 0 1 0 .44-.88s-.34-.2-.84-.22z" />
</g>
<g id="list-rich">
<path d="M0 0v3h3v-3h-3zm4 0v1h4v-1h-4zm0 2v1h3v-1h-3zm-4 2v3h3v-3h-3zm4 0v1h4v-1h-4zm0 2v1h3v-1h-3z" />
</g>
<g id="warning">
<path d="M3.09 0c-.06 0-.1.04-.13.09l-2.94 6.81c-.02.05-.03.13-.03.19v.81c0 .05.04.09.09.09h6.81c.05 0 .09-.04.09-.09v-.81c0-.05-.01-.14-.03-.19l-2.94-6.81c-.02-.05-.07-.09-.13-.09h-.81zm-.09 3h1v2h-1v-2zm0 3h1v1h-1v-1z" />
</g>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,8 +0,0 @@
{{#if sentryKey}}
<script src="https://cdn.ravenjs.com/3.17.0/raven.min.js" crossorigin="anonymous"></script>
<script>
Raven.config('{{{sentryKey}}}', {
whitelistUrls: [/https:\/\/(.*\.)?regexper\.com/]
}).install()
</script>
{{/if}}

View File

@ -1,25 +0,0 @@
<div class="svg">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
version="1.1">
<defs>
<style type="text/css">{{> svg_styles}}</style>
</defs>
<metadata>
<rdf:RDF>
<cc:License rdf:about="http://creativecommons.org/licenses/by/3.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
</svg>
</div>
<div class="progress">
<div style="width:0;"></div>
</div>

View File

@ -10,48 +10,5 @@
"license": "MIT",
"private": true,
"scripts": {
"pretest": "jscs lib/ src/ spec/",
"test": "karma start --single-run",
"build": "gulp build",
"start": "gulp"
},
"devDependencies": {
"babel-core": "^6.17.0",
"babel-loader": "^7.1.1",
"babel-polyfill": "^6.3.14",
"babel-preset-es2015": "^6.16.0",
"babel-runtime": "^6.3.19",
"canopy": "^0.2.0",
"css-loader": "^0.28.4",
"docco": "^0.7.0",
"extract-loader": "^1.0.0",
"file-loader": "^0.11.2",
"folder-toc": "^0.1.0",
"gulp": "^3.8.10",
"gulp-connect": "^5.0.0",
"gulp-docco": "0.0.4",
"gulp-front-matter": "^1.3.0",
"gulp-hb": "^6.0.2",
"gulp-help": "^1.6.1",
"gulp-notify": "^3.0.0",
"gulp-rename": "^1.2.2",
"gulp-util": "^3.0.7",
"handlebars-layouts": "^3.1.2",
"imports-loader": "^0.7.1",
"jasmine-core": "^2.4.1",
"jscs": "^3.0.7",
"karma": "^1.1.2",
"karma-firefox-launcher": "^1.0.0",
"karma-jasmine": "^1.0.2",
"karma-notify-reporter": "^1.0.1",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.4",
"lodash": "^4.6.1",
"node-bourbon": "^4.2.3",
"node-sass": "^4.5.3",
"sass-loader": "^6.0.6",
"snapsvg": "^0.5.1",
"watchify": "^3.7.0",
"webpack": "^3.4.1"
}
}

View File

@ -1,20 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import _ from 'lodash';
describe('parser/javascript/anchor.js', function() {
_.forIn({
'^': {
label: 'Start of line'
},
'$': {
label: 'End of line'
}
}, (content, str) => {
it(`parses "${str}" as an Anchor`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__anchor()).toEqual(jasmine.objectContaining(content));
});
});
});

View File

@ -1,27 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
describe('parser/javascript/any_character.js', function() {
it('parses "." as an AnyCharacter', function() {
var parser = new javascript.Parser('.');
expect(parser.__consume__terminal()).toEqual(jasmine.objectContaining({
type: 'any-character'
}));
});
describe('#_render', function() {
beforeEach(function() {
var parser = new javascript.Parser('.');
this.node = parser.__consume__terminal();
});
it('renders a label', function() {
spyOn(this.node, 'renderLabel').and.returnValue('rendered label');
expect(this.node._render()).toEqual('rendered label');
expect(this.node.renderLabel).toHaveBeenCalledWith('any character');
});
});
});

View File

@ -1,32 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import _ from 'lodash';
import Snap from 'snapsvg';
describe('parser/javascript/charset_escape.js', function() {
_.forIn({
'\\b': { label: 'backspace (0x08)', ordinal: 0x08 },
'\\d': { label: 'digit', ordinal: -1 },
'\\D': { label: 'non-digit', ordinal: -1 },
'\\f': { label: 'form feed (0x0C)', ordinal: 0x0c },
'\\n': { label: 'line feed (0x0A)', ordinal: 0x0a },
'\\r': { label: 'carriage return (0x0D)', ordinal: 0x0d },
'\\s': { label: 'white space', ordinal: -1 },
'\\S': { label: 'non-white space', ordinal: -1 },
'\\t': { label: 'tab (0x09)', ordinal: 0x09 },
'\\v': { label: 'vertical tab (0x0B)', ordinal: 0x0b },
'\\w': { label: 'word', ordinal: -1 },
'\\W': { label: 'non-word', ordinal: -1 },
'\\0': { label: 'null (0x00)', ordinal: 0 },
'\\012': { label: 'octal: 12 (0x0A)', ordinal: 10 },
'\\cx': { label: 'ctrl-X (0x18)', ordinal: 24 },
'\\xab': { label: '0xAB', ordinal: 0xab },
'\\uabcd': { label: 'U+ABCD', ordinal: 0xabcd }
}, (content, str) => {
it(`parses "${str}" as a CharsetEscape`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__charset_terminal()).toEqual(jasmine.objectContaining(content));
});
});
});

View File

@ -1,103 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import util from '../../../src/js/util.js';
import _ from 'lodash';
describe('parser/javascript/charset_range.js', function() {
_.forIn({
'a-z': {
first: jasmine.objectContaining({ textValue: 'a' }),
last: jasmine.objectContaining({ textValue: 'z' })
},
'\\b-z': {
first: jasmine.objectContaining({ textValue: '\\b' }),
last: jasmine.objectContaining({ textValue: 'z' })
},
'\\f-z': {
first: jasmine.objectContaining({ textValue: '\\f' }),
last: jasmine.objectContaining({ textValue: 'z' })
},
'\\n-z': {
first: jasmine.objectContaining({ textValue: '\\n' }),
last: jasmine.objectContaining({ textValue: 'z' })
},
'\\r-z': {
first: jasmine.objectContaining({ textValue: '\\r' }),
last: jasmine.objectContaining({ textValue: 'z' })
},
'\\t-z': {
first: jasmine.objectContaining({ textValue: '\\t' }),
last: jasmine.objectContaining({ textValue: 'z' })
},
'\\v-z': {
first: jasmine.objectContaining({ textValue: '\\v' }),
last: jasmine.objectContaining({ textValue: 'z' })
}
}, (content, str) => {
it(`parses "${str}" as a CharsetRange`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__charset_range()).toEqual(jasmine.objectContaining(content));
});
});
_.each([
'\\d-a',
'\\D-a',
'\\s-a',
'\\S-a',
'\\w-a',
'\\W-a'
], str => {
it(`does not parse "${str}" as a CharsetRange`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__charset_range()).toEqual(null);
});
});
it('throws an exception when the range is out of order', function() {
var parser = new javascript.Parser('z-a');
expect(() => {
parser.__consume__charset_range();
}).toThrow('Range out of order in character class: z-a');
});
describe('#_render', function() {
beforeEach(function() {
var parser = new javascript.Parser('a-z');
this.node = parser.__consume__charset_range();
this.node.container = jasmine.createSpyObj('cotnainer', ['addClass', 'text', 'group']);
this.node.container.text.and.returnValue('hyphen');
this.firstDeferred = this.testablePromise();
this.lastDeferred = this.testablePromise();
spyOn(this.node.first, 'render').and.returnValue(this.firstDeferred.promise);
spyOn(this.node.last, 'render').and.returnValue(this.lastDeferred.promise);
spyOn(util, 'spaceHorizontally');
});
it('renders a hyphen', function() {
this.node._render();
expect(this.node.container.text).toHaveBeenCalledWith(0, 0, '-');
});
it('spaces the items horizontally', function(done) {
this.firstDeferred.resolve();
this.lastDeferred.resolve();
this.node._render()
.then(() => {
expect(util.spaceHorizontally).toHaveBeenCalledWith([
this.node.first,
'hyphen',
this.node.last
], { padding: 5 });
done();
});
});
});
});

View File

@ -1,154 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import Node from '../../../src/js/parser/javascript/node.js';
import util from '../../../src/js/util.js';
import _ from 'lodash';
import Snap from 'snapsvg';
describe('parser/javascript/charset.js', function() {
_.forIn({
'[abc]': {
label: 'One of:',
elements: [
jasmine.objectContaining({ type: 'literal', textValue: 'a' }),
jasmine.objectContaining({ type: 'literal', textValue: 'b' }),
jasmine.objectContaining({ type: 'literal', textValue: 'c' })
]
},
'[^abc]': {
label: 'None of:',
elements: [
jasmine.objectContaining({ type: 'literal', textValue: 'a' }),
jasmine.objectContaining({ type: 'literal', textValue: 'b' }),
jasmine.objectContaining({ type: 'literal', textValue: 'c' })
]
},
'[aaa]': {
label: 'One of:',
elements: [
jasmine.objectContaining({ type: 'literal', textValue: 'a' })
]
},
'[a-z]': {
label: 'One of:',
elements: [
jasmine.objectContaining({ type: 'charset-range', textValue: 'a-z' })
]
},
'[\\b]': {
label: 'One of:',
elements: [
jasmine.objectContaining({ type: 'charset-escape', textValue: '\\b' })
]
}
}, (content, str) => {
it(`parses "${str}" as a Charset`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__charset()).toEqual(jasmine.objectContaining(content));
});
});
it('adds a warning for character sets the contain non-standard escapes', function() {
var node;
Node.state = { warnings: [] };
node = new javascript.Parser('[\\c]').__consume__charset();
expect(node.state.warnings).toEqual(['The character set "[\\c]" contains the \\c escape followed by a character other than A-Z. This can lead to different behavior depending on browser. The representation here is the most common interpretation.']);
});
describe('_anchor property', function() {
it('calculates the anchor based on the partContainer', function() {
var node = new javascript.Parser('[a]').__consume__charset();
node.partContainer = jasmine.createSpyObj('partContainer', ['getBBox']);
node.partContainer.getBBox.and.returnValue({
cy: 20
});
spyOn(node, 'transform').and.returnValue({
localMatrix: Snap.matrix().translate(3, 8)
});
expect(node._anchor).toEqual({
ay: 28
});
});
});
describe('#_render', function() {
beforeEach(function() {
var counter = 0;
this.node = new javascript.Parser('[a]').__consume__charset();
this.node.label = 'example label';
this.node.elements = [
jasmine.createSpyObj('item', ['render']),
jasmine.createSpyObj('item', ['render']),
jasmine.createSpyObj('item', ['render'])
];
this.elementDeferred = [
this.testablePromise(),
this.testablePromise(),
this.testablePromise()
];
this.node.elements[0].render.and.returnValue(this.elementDeferred[0].promise);
this.node.elements[1].render.and.returnValue(this.elementDeferred[1].promise);
this.node.elements[2].render.and.returnValue(this.elementDeferred[2].promise);
this.node.container = Snap(document.createElement('svg')).group();
this.partContainer = this.node.container.group();
spyOn(this.node.container, 'group').and.returnValue(this.partContainer);
spyOn(this.partContainer, 'group').and.callFake(function() {
return `group ${counter++}`;
});
spyOn(this.node, 'renderLabeledBox').and.returnValue('labeled box promise');
spyOn(util, 'spaceVertically');
});
it('creates a cotainer for the parts of the charset', function() {
this.node._render();
expect(this.node.partContainer).toEqual(this.partContainer);
});
it('renders each item', function() {
this.node._render();
expect(this.node.elements[0].render).toHaveBeenCalledWith('group 0');
expect(this.node.elements[1].render).toHaveBeenCalledWith('group 1');
expect(this.node.elements[2].render).toHaveBeenCalledWith('group 2');
});
describe('positioning of the items', function() {
beforeEach(function() {
this.elementDeferred[0].resolve();
this.elementDeferred[1].resolve();
this.elementDeferred[2].resolve();
});
it('spaces the elements vertically', function(done) {
this.node._render()
.then(() => {
expect(util.spaceVertically).toHaveBeenCalledWith(this.node.elements, { padding: 5 });
done();
});
});
it('renders a labeled box', function(done) {
this.node._render()
.then(result => {
expect(this.node.renderLabeledBox).toHaveBeenCalledWith('example label', this.partContainer, { padding: 5 });
expect(result).toEqual('labeled box promise');
done();
});
});
});
});
});

View File

@ -1,72 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import _ from 'lodash';
import Snap from 'snapsvg';
describe('parser/javascript/escape.js', function() {
_.forIn({
'\\b': { label: 'word boundary', ordinal: -1 },
'\\B': { label: 'non-word boundary', ordinal: -1 },
'\\d': { label: 'digit', ordinal: -1 },
'\\D': { label: 'non-digit', ordinal: -1 },
'\\f': { label: 'form feed (0x0C)', ordinal: 0x0c },
'\\n': { label: 'line feed (0x0A)', ordinal: 0x0a },
'\\r': { label: 'carriage return (0x0D)', ordinal: 0x0d },
'\\s': { label: 'white space', ordinal: -1 },
'\\S': { label: 'non-white space', ordinal: -1 },
'\\t': { label: 'tab (0x09)', ordinal: 0x09 },
'\\v': { label: 'vertical tab (0x0B)', ordinal: 0x0b },
'\\w': { label: 'word', ordinal: -1 },
'\\W': { label: 'non-word', ordinal: -1 },
'\\0': { label: 'null (0x00)', ordinal: 0 },
'\\1': { label: 'Back reference (group = 1)', ordinal: -1 },
'\\2': { label: 'Back reference (group = 2)', ordinal: -1 },
'\\3': { label: 'Back reference (group = 3)', ordinal: -1 },
'\\4': { label: 'Back reference (group = 4)', ordinal: -1 },
'\\5': { label: 'Back reference (group = 5)', ordinal: -1 },
'\\6': { label: 'Back reference (group = 6)', ordinal: -1 },
'\\7': { label: 'Back reference (group = 7)', ordinal: -1 },
'\\8': { label: 'Back reference (group = 8)', ordinal: -1 },
'\\9': { label: 'Back reference (group = 9)', ordinal: -1 },
'\\012': { label: 'octal: 12 (0x0A)', ordinal: 10 },
'\\cx': { label: 'ctrl-X (0x18)', ordinal: 24 },
'\\xab': { label: '0xAB', ordinal: 0xab },
'\\uabcd': { label: 'U+ABCD', ordinal: 0xabcd }
}, (content, str) => {
it(`parses "${str}" as an Escape`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__terminal()).toEqual(jasmine.objectContaining(content));
});
});
describe('#_render', function() {
beforeEach(function() {
var parser = new javascript.Parser('\\b');
this.node = parser.__consume__terminal();
this.node.state = {};
this.svg = Snap(document.createElement('svg'));
this.node.container = this.svg.group();
spyOn(this.node, 'renderLabel').and.callThrough();
});
it('renders a label', function() {
this.node._render();
expect(this.node.renderLabel).toHaveBeenCalledWith('word boundary');
});
it('sets the edge radius of the rect', function(done) {
this.node._render()
.then(label => {
expect(label.select('rect').attr()).toEqual(jasmine.objectContaining({
rx: '3',
ry: '3'
}));
done();
});
});
});
});

View File

@ -1,77 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import Snap from 'snapsvg';
describe('parser/javascript/literal.js', function() {
it('parses "x" as a Literal', function() {
var parser = new javascript.Parser('x');
expect(parser.__consume__terminal()).toEqual(jasmine.objectContaining({
type: 'literal',
literal: 'x',
ordinal: 120
}));
});
it('parses "\\x" as a Literal', function() {
var parser = new javascript.Parser('\\x');
expect(parser.__consume__terminal()).toEqual(jasmine.objectContaining({
type: 'literal',
literal: 'x',
ordinal: 120
}));
});
describe('#_render', function() {
beforeEach(function() {
var parser = new javascript.Parser('a');
this.node = parser.__consume__terminal();
this.node.state = {};
this.svg = Snap(document.createElement('svg'));
this.node.container = this.svg.group();
spyOn(this.node, 'renderLabel').and.callThrough();
});
it('renders a label', function() {
this.node._render();
expect(this.node.renderLabel).toHaveBeenCalledWith(['\u201c', 'a', '\u201d']);
});
it('sets the class of the first and third tspan to "quote"', function(done) {
this.node._render()
.then(label => {
expect(label.selectAll('tspan')[0].hasClass('quote')).toBeTruthy();
expect(label.selectAll('tspan')[2].hasClass('quote')).toBeTruthy();
done();
});
});
it('sets the edge radius of the rect', function(done) {
this.node._render()
.then(label => {
expect(label.select('rect').attr()).toEqual(jasmine.objectContaining({
rx: '3',
ry: '3'
}));
done();
});
});
});
describe('#merge', function() {
beforeEach(function() {
var parser = new javascript.Parser('a');
this.node = parser.__consume__terminal();
});
it('appends to the literal value', function() {
this.node.merge({ literal: 'b' });
expect(this.node.literal).toEqual('ab');
});
});
});

View File

@ -1,210 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import _ from 'lodash';
import Snap from 'snapsvg';
describe('parser/javascript/match_fragment.js', function() {
_.forIn({
'a': {
proxy: jasmine.objectContaining({ textValue: 'a' }),
canMerge: true
},
'\\b': {
proxy: jasmine.objectContaining({ textValue: '\\b' }),
canMerge: false
},
'a*': {
content: jasmine.objectContaining({ textValue: 'a' }),
repeat: jasmine.objectContaining({ textValue: '*' }),
canMerge: false
}
}, (content, str) => {
it(`parses "${str}" as a MatchFragment`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__match_fragment()).toEqual(jasmine.objectContaining(content));
});
});
describe('_anchor property', function() {
beforeEach(function() {
this.node = new javascript.Parser('a').__consume__match_fragment();
this.node.content = {
getBBox() {
return {
ax: 1,
ax2: 2,
ay: 3
};
}
};
spyOn(this.node, 'transform').and.returnValue({
localMatrix: Snap.matrix().translate(10, 20)
});
});
it('applies the local transform to the content anchor', function() {
expect(this.node._anchor).toEqual({
ax: 11,
ax2: 12,
ay: 23
});
});
});
describe('#_render', function() {
beforeEach(function() {
this.node = new javascript.Parser('a').__consume__match_fragment();
this.node.container = jasmine.createSpyObj('container', [
'addClass',
'group',
'prepend',
'path'
]);
this.node.container.group.and.returnValue('example group');
this.renderDeferred = this.testablePromise();
this.node.content = jasmine.createSpyObj('content', [
'render',
'transform',
'getBBox'
]);
this.node.content.getBBox.and.returnValue('content bbox');
this.node.content.render.and.returnValue(this.renderDeferred.promise);
this.node.repeat = {
contentPosition: 'example position',
skipPath: jasmine.createSpy('skipPath').and.returnValue('skip path'),
loopPath: jasmine.createSpy('loopPath').and.returnValue('loop path')
};
spyOn(this.node, 'loopLabel');
});
it('renders the content', function() {
this.node._render();
expect(this.node.content.render).toHaveBeenCalledWith('example group');
});
describe('positioning of content', function() {
beforeEach(function() {
this.renderDeferred.resolve();
});
it('moves the content to the correct position', function(done) {
this.node._render()
.then(() => {
expect(this.node.content.transform).toHaveBeenCalledWith('example position');
done();
});
});
it('renders a skip path and loop path', function(done) {
this.node._render()
.then(() => {
expect(this.node.repeat.skipPath).toHaveBeenCalledWith('content bbox');
expect(this.node.repeat.loopPath).toHaveBeenCalledWith('content bbox');
expect(this.node.container.path).toHaveBeenCalledWith('skip pathloop path');
done();
});
});
it('renders a loop label', function(done) {
this.node._render()
.then(() => {
expect(this.node.loopLabel).toHaveBeenCalled();
done();
});
});
});
});
describe('#loopLabel', function() {
beforeEach(function() {
this.node = new javascript.Parser('a').__consume__match_fragment();
this.node.repeat = {};
this.node.container = jasmine.createSpyObj('container', [
'addClass',
'text'
]);
this.text = jasmine.createSpyObj('text', [
'addClass',
'getBBox',
'transform'
]);
this.node.container.text.and.returnValue(this.text);
this.text.addClass.and.returnValue(this.text);
this.text.getBBox.and.returnValue({
width: 11,
height: 22
});
spyOn(this.node, 'getBBox').and.returnValue({
x2: 33,
y2: 44
});
});
describe('when a label is defined', function() {
beforeEach(function() {
this.node.repeat.label = 'example label';
});
it('renders a text element', function() {
this.node.loopLabel();
expect(this.node.container.text).toHaveBeenCalledWith(0, 0, ['example label']);
});
describe('when there is a skip loop', function() {
beforeEach(function() {
this.node.repeat.hasSkip = true;
});
it('positions the text element', function() {
this.node.loopLabel();
expect(this.text.transform).toHaveBeenCalledWith(Snap.matrix()
.translate(17, 66));
});
});
describe('when there is no skip loop', function() {
beforeEach(function() {
this.node.repeat.hasSkip = false;
});
it('positions the text element', function() {
this.node.loopLabel();
expect(this.text.transform).toHaveBeenCalledWith(Snap.matrix()
.translate(22, 66));
});
});
});
describe('when a label is not defined', function() {
it('does not render a text element', function() {
this.node.loopLabel();
expect(this.node.container.text).not.toHaveBeenCalled();
});
});
});
});

View File

@ -1,196 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import util from '../../../src/js/util.js';
import _ from 'lodash';
import Snap from 'snapsvg';
describe('parser/javascript/match.js', function() {
_.forIn({
'example': {
parts: [
jasmine.objectContaining({
content: jasmine.objectContaining({ literal: 'example' })
})
],
proxy: jasmine.objectContaining({
content: jasmine.objectContaining({ literal: 'example' })
})
},
'example*': {
parts: [
jasmine.objectContaining({
content: jasmine.objectContaining({ literal: 'exampl' })
}),
jasmine.objectContaining({
content: jasmine.objectContaining({ literal: 'e' })
})
]
},
'': {
parts: []
}
}, (content, str) => {
it(`parses "${str}" as a Match`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__match()).toEqual(jasmine.objectContaining(content));
});
});
describe('_anchor property', function() {
beforeEach(function() {
this.node = new javascript.Parser('a').__consume__match();
this.node.start = jasmine.createSpyObj('start', ['getBBox']);
this.node.start.getBBox.and.returnValue({
x: 1,
x2: 2,
cy: 3
});
this.node.end = jasmine.createSpyObj('start', ['getBBox']);
this.node.end.getBBox.and.returnValue({
x: 4,
x2: 5,
cy: 6
});
spyOn(this.node, 'transform').and.returnValue({
localMatrix: Snap.matrix().translate(10, 20)
});
});
it('calculates the anchor from the start and end items', function() {
expect(this.node._anchor).toEqual({
ax: 11,
ax2: 15,
ay: 23
});
});
});
describe('#_render', function() {
beforeEach(function() {
this.node = new javascript.Parser('a').__consume__match();
this.node.container = jasmine.createSpyObj('container', [
'addClass',
'group',
'prepend',
'path'
]);
this.node.container.group.and.returnValue('example group');
this.node.parts = [
jasmine.createSpyObj('part 0', ['render']),
jasmine.createSpyObj('part 1', ['render']),
jasmine.createSpyObj('part 2', ['render'])
];
this.partDeferreds = [
this.testablePromise(),
this.testablePromise(),
this.testablePromise()
];
this.node.parts[0].render.and.returnValue(this.partDeferreds[0].promise);
this.node.parts[1].render.and.returnValue(this.partDeferreds[1].promise);
this.node.parts[2].render.and.returnValue(this.partDeferreds[2].promise);
});
it('renders each part', function() {
this.node._render();
expect(this.node.parts[0].render).toHaveBeenCalledWith('example group');
expect(this.node.parts[1].render).toHaveBeenCalledWith('example group');
expect(this.node.parts[2].render).toHaveBeenCalledWith('example group');
});
describe('positioning of items', function() {
beforeEach(function() {
this.partDeferreds[0].resolve('part 0');
this.partDeferreds[1].resolve('part 1');
this.partDeferreds[2].resolve('part 2');
spyOn(util, 'spaceHorizontally');
spyOn(this.node, 'connectorPaths').and.returnValue(['connector paths']);
});
it('sets the start and end properties', function(done) {
this.node._render()
.then(() => {
expect(this.node.start).toEqual('part 0');
expect(this.node.end).toEqual('part 2');
done();
});
});
it('spaces the items horizontally', function(done) {
this.node._render()
.then(() => {
expect(util.spaceHorizontally).toHaveBeenCalledWith([
'part 0',
'part 1',
'part 2'
], { padding: 10 });
done();
});
});
it('renders the connector paths', function(done) {
this.node._render()
.then(() => {
expect(this.node.connectorPaths).toHaveBeenCalledWith([
'part 0',
'part 1',
'part 2'
]);
expect(this.node.container.path).toHaveBeenCalledWith('connector paths');
done();
});
});
});
});
describe('#connectorPaths', function() {
beforeEach(function() {
this.node = new javascript.Parser('a').__consume__match();
this.items = [
jasmine.createSpyObj('item 0', ['getBBox']),
jasmine.createSpyObj('item 1', ['getBBox']),
jasmine.createSpyObj('item 2', ['getBBox'])
];
this.items[0].getBBox.and.returnValue({
x: 10,
x2: 20,
cy: 5
});
this.items[1].getBBox.and.returnValue({
x: 30,
x2: 40,
cy: 5
});
this.items[2].getBBox.and.returnValue({
x: 50,
x2: 60,
cy: 5
});
});
it('returns the connector paths between fragments', function() {
expect(this.node.connectorPaths(this.items)).toEqual([
'M20,5H30',
'M40,5H50'
]);
});
});
});

View File

@ -1,422 +0,0 @@
import Node from '../../../src/js/parser/javascript/node.js';
import Snap from 'snapsvg';
describe('parser/javascript/node.js', function() {
beforeEach(function() {
Node.state = {};
this.node = new Node();
});
it('references the state from Node.state', function() {
Node.state.example = 'example state';
expect(this.node.state.example).toEqual('example state');
});
describe('module setter', function() {
it('extends the node with the module', function() {
this.node.module = { example: 'value' };
expect(this.node.example).toEqual('value');
});
it('calls the module #setup method', function() {
var setup = jasmine.createSpy('setup');
this.node.module = { setup };
expect(setup).toHaveBeenCalled();
});
it('sets up any defined properties and removes \'definedProperties\' field', function() {
this.node.module = {
definedProperties: {
example: {
get: function() {
return 'value';
}
}
}
};
expect(this.node.example).toEqual('value');
expect(this.node.definedProperties).toBeUndefined();
});
});
describe('container setter', function() {
it('adds a class to the container element', function() {
var container = jasmine.createSpyObj('container', ['addClass']);
this.node.type = 'example type';
this.node.container = container;
expect(container.addClass).toHaveBeenCalledWith('example type');
});
});
describe('anchor getter', function() {
describe('when a proxy node is used', function() {
it('returns the anchor from the proxy', function() {
this.node.proxy = { anchor: 'example anchor' };
expect(this.node.anchor).toEqual('example anchor');
});
});
describe('when a proxy node is not used', function() {
it('returns _anchor of the node', function() {
this.node._anchor = { example: 'value' };
expect(this.node.anchor).toEqual({
example: 'value'
});
});
});
});
describe('#getBBox', function() {
it('returns the normalized bbox of the container merged with the anchor', function() {
this.node.proxy = {
anchor: {
anchor: 'example anchor'
}
};
this.node.container = jasmine.createSpyObj('container', ['addClass', 'getBBox']);
this.node.container.getBBox.and.returnValue({
bbox: 'example bbox',
x: 'left',
x2: 'right',
cy: 'center'
});
expect(this.node.getBBox()).toEqual({
bbox: 'example bbox',
anchor: 'example anchor',
x: 'left',
x2: 'right',
cy: 'center',
ax: 'left',
ax2: 'right',
ay: 'center'
});
});
});
describe('#transform', function() {
it('returns the result of calling transform on the container', function() {
this.node.container = jasmine.createSpyObj('container', ['addClass', 'transform']);
this.node.container.transform.and.returnValue('transform result');
expect(this.node.transform('matrix')).toEqual('transform result');
expect(this.node.container.transform).toHaveBeenCalledWith('matrix');
});
});
describe('#deferredStep', function() {
it('resolves the returned promise when the render is not canceled', function(done) {
var resolve = jasmine.createSpy('resolve'),
reject = jasmine.createSpy('reject');
this.node.deferredStep('result')
.then(resolve, reject)
.then(() => {
expect(resolve).toHaveBeenCalledWith('result');
expect(reject).not.toHaveBeenCalled();
done();
});
});
it('rejects the returned promise when the render is canceled', function(done) {
var resolve = jasmine.createSpy('resolve'),
reject = jasmine.createSpy('reject');
this.node.state.cancelRender = true;
this.node.deferredStep('result', 'value')
.then(resolve, reject)
.then(() => {
expect(resolve).not.toHaveBeenCalled();
expect(reject).toHaveBeenCalledWith('Render cancelled');
done();
});
});
});
describe('#renderLabel', function() {
beforeEach(function() {
this.group = jasmine.createSpyObj('group', ['addClass', 'rect', 'text']);
this.group.addClass.and.returnValue(this.group);
this.node.container = jasmine.createSpyObj('container', ['addClass', 'group']);
this.node.container.group.and.returnValue(this.group);
});
it('adds a "label" class to the group', function() {
this.node.renderLabel('example label');
expect(this.group.addClass).toHaveBeenCalledWith('label');
});
it('creates a rect element', function() {
this.node.renderLabel('example label');
expect(this.group.rect).toHaveBeenCalled();
});
it('creates a text element', function() {
this.node.renderLabel('example label');
expect(this.group.text).toHaveBeenCalledWith(0, 0, ['example label']);
});
describe('positioning of label elements', function() {
beforeEach(function() {
this.text = jasmine.createSpyObj('text', ['getBBox', 'transform']);
this.rect = jasmine.createSpyObj('rect', ['attr']);
this.text.getBBox.and.returnValue({
width: 42,
height: 24
});
this.group.text.and.returnValue(this.text);
this.group.rect.and.returnValue(this.rect);
});
it('transforms the text element', function(done) {
this.node.renderLabel('example label')
.then(() => {
expect(this.text.transform).toHaveBeenCalledWith(Snap.matrix()
.translate(5, 22));
done();
});
});
it('sets the dimensions of the rect element', function(done) {
this.node.renderLabel('example label')
.then(() => {
expect(this.rect.attr).toHaveBeenCalledWith({
width: 52,
height: 34
});
done();
});
});
it('resolves with the group element', function(done) {
this.node.renderLabel('example label')
.then(group => {
expect(group).toEqual(this.group);
done();
});
});
});
});
describe('#render', function() {
beforeEach(function() {
this.container = jasmine.createSpyObj('container', ['addClass']);
});
describe('when a proxy node is used', function() {
beforeEach(function() {
this.node.proxy = jasmine.createSpyObj('proxy', ['render']);
this.node.proxy.render.and.returnValue('example proxy result');
});
it('sets the container', function() {
this.node.render(this.container);
expect(this.node.container).toEqual(this.container);
});
it('calls the proxy render method', function() {
expect(this.node.render(this.container)).toEqual('example proxy result');
expect(this.node.proxy.render).toHaveBeenCalledWith(this.container);
});
});
describe('when a proxy node is not used', function() {
beforeEach(function() {
this.deferred = this.testablePromise();
this.node._render = jasmine.createSpy('_render').and.returnValue(this.deferred.promise);
});
it('sets the container', function() {
this.node.render(this.container);
expect(this.node.container).toEqual(this.container);
});
it('increments the renderCounter', function() {
this.node.state.renderCounter = 0;
this.node.render(this.container);
expect(this.node.state.renderCounter).toEqual(1);
});
it('calls #_render', function() {
this.node.render(this.container);
expect(this.node._render).toHaveBeenCalled();
});
describe('when #_render is complete', function() {
it('decrements the renderCounter', function(done) {
this.node.render(this.container)
.then(() => {
expect(this.node.state.renderCounter).toEqual(41);
done();
});
this.node.state.renderCounter = 42;
this.deferred.resolve();
});
it('ultimately resolves with the node instance', function(done) {
this.deferred.resolve();
this.node.render(this.container)
.then(result => {
expect(result).toEqual(this.node);
done();
});
});
});
});
});
describe('#renderLabeledBox', function() {
beforeEach(function() {
var svg = Snap(document.createElement('svg'));
this.text = svg.text();
this.rect = svg.rect();
this.content = svg.rect();
this.node.container = jasmine.createSpyObj('container', ['addClass', 'text', 'rect', 'prepend']);
this.node.container.text.and.returnValue(this.text);
this.node.container.rect.and.returnValue(this.rect);
this.node.type = 'example-type';
});
it('creates a text element', function() {
this.node.renderLabeledBox('example label', this.content, { padding: 5 });
expect(this.node.container.text).toHaveBeenCalledWith(0, 0, ['example label']);
});
it('sets the class on the text element', function() {
spyOn(this.text, 'addClass').and.callThrough();
this.node.renderLabeledBox('example label', this.content, { padding: 5 });
expect(this.text.addClass).toHaveBeenCalledWith('example-type-label');
});
it('creates a rect element', function() {
this.node.renderLabeledBox('example label', this.content, { padding: 5 });
expect(this.node.container.rect).toHaveBeenCalled();
});
it('sets the class on the rect element', function() {
spyOn(this.rect, 'addClass').and.callThrough();
this.node.renderLabeledBox('example label', this.content, { padding: 5 });
expect(this.rect.addClass).toHaveBeenCalledWith('example-type-box');
});
it('sets the corner radius on the rect element', function() {
spyOn(this.rect, 'attr').and.callThrough();
this.node.renderLabeledBox('example label', this.content, { padding: 5 });
expect(this.rect.attr).toHaveBeenCalledWith({
rx: 3,
ry: 3
});
});
describe('positioning of elements', function() {
beforeEach(function() {
spyOn(this.text, 'getBBox').and.returnValue({
width: 100,
height: 20
});
spyOn(this.content, 'getBBox').and.returnValue({
width: 200,
height: 100,
cx: 100
});
});
it('positions the text element', function(done) {
spyOn(this.text, 'transform').and.callThrough();
this.node.renderLabeledBox('example label', this.content, { padding: 5 })
.then(() => {
expect(this.text.transform).toHaveBeenCalledWith(Snap.matrix()
.translate(0, 20));
done();
});
});
it('positions the rect element', function(done) {
spyOn(this.rect, 'transform').and.callThrough();
this.node.renderLabeledBox('example label', this.content, { padding: 5 })
.then(() => {
expect(this.rect.transform).toHaveBeenCalledWith(Snap.matrix()
.translate(0, 20));
done();
});
});
it('sets the dimensions of the rect element', function(done) {
spyOn(this.rect, 'attr').and.callThrough();
this.node.renderLabeledBox('example label', this.content, { padding: 5 })
.then(() => {
expect(this.rect.attr).toHaveBeenCalledWith({
width: 210,
height: 110
});
done();
});
});
it('sets the dimensions of the rect element (based on the text element)', function(done) {
this.content.getBBox.and.returnValue({
width: 50,
height: 100,
cx: 25
});
spyOn(this.rect, 'attr').and.callThrough();
this.node.renderLabeledBox('example label', this.content, { padding: 5 })
.then(() => {
expect(this.rect.attr).toHaveBeenCalledWith({
width: 100,
height: 110
});
done();
});
});
it('positions the content element', function(done) {
spyOn(this.content, 'transform').and.callThrough();
this.node.renderLabeledBox('example label', this.content, { padding: 5 })
.then(() => {
expect(this.content.transform).toHaveBeenCalledWith(Snap.matrix()
.translate(5, 25));
done();
});
});
});
});
});

View File

@ -1,30 +0,0 @@
import ParserState from '../../../src/js/parser/javascript/parser_state.js';
describe('parser/javascript/parser_state.js', function() {
beforeEach(function() {
this.progress = { style: {} };
this.state = new ParserState(this.progress);
});
describe('renderCounter property', function() {
it('sets the width of the progress element to the percent of completed steps', function() {
this.state.renderCounter = 50;
expect(this.progress.style.width).toEqual('0.00%');
this.state.renderCounter = 10;
expect(this.progress.style.width).toEqual('80.00%');
});
it('does not change the width of the progress element when rendering has been cancelled', function() {
this.state.renderCounter = 50;
this.state.renderCounter = 40;
expect(this.progress.style.width).toEqual('20.00%');
this.state.cancelRender = true;
this.state.renderCounter = 10;
expect(this.progress.style.width).toEqual('20.00%');
});
});
});

View File

@ -1,311 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import util from '../../../src/js/util.js';
import _ from 'lodash';
import Snap from 'snapsvg';
describe('parser/javascript/regexp.js', function() {
_.forIn({
'test': {
proxy: jasmine.objectContaining({ textValue: 'test' })
},
'part 1|part 2': {
matches: [
jasmine.objectContaining({ textValue: 'part 1' }),
jasmine.objectContaining({ textValue: 'part 2' })
]
}
}, (content, str) => {
it(`parses "${str}" as a Regexp`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__regexp()).toEqual(jasmine.objectContaining(content));
});
});
describe('#_render', function() {
beforeEach(function() {
var counter = 0;
this.node = new javascript.Parser('a|b').__consume__regexp();
this.node.container = jasmine.createSpyObj('container', [
'addClass',
'group',
'prepend',
'path'
]);
this.group = jasmine.createSpyObj('group', [
'addClass',
'transform',
'group',
'prepend',
'path',
'getBBox'
]);
this.node.container.group.and.returnValue(this.group);
this.group.addClass.and.returnValue(this.group);
this.group.transform.and.returnValue(this.group);
this.group.getBBox.and.returnValue('group bbox');
this.group.group.and.callFake(function() {
return `group ${counter++}`;
});
this.node.matches = [
jasmine.createSpyObj('match', ['render']),
jasmine.createSpyObj('match', ['render']),
jasmine.createSpyObj('match', ['render'])
];
this.matchDeferred = [
this.testablePromise(),
this.testablePromise(),
this.testablePromise()
];
this.node.matches[0].render.and.returnValue(this.matchDeferred[0].promise);
this.node.matches[1].render.and.returnValue(this.matchDeferred[1].promise);
this.node.matches[2].render.and.returnValue(this.matchDeferred[2].promise);
spyOn(this.node, 'getBBox').and.returnValue('container bbox');
spyOn(this.node, 'makeCurve').and.returnValue('curve');
spyOn(this.node, 'makeSide').and.returnValue('side');
spyOn(this.node, 'makeConnector').and.returnValue('connector');
spyOn(util, 'spaceVertically');
});
it('creates a container for the match nodes', function() {
this.node._render();
expect(this.node.container.group).toHaveBeenCalled();
expect(this.group.addClass).toHaveBeenCalledWith('regexp-matches');
expect(this.group.transform).toHaveBeenCalledWith(Snap.matrix()
.translate(20, 0));
});
it('renders each match node', function() {
this.node._render();
expect(this.node.matches[0].render).toHaveBeenCalledWith('group 0');
expect(this.node.matches[1].render).toHaveBeenCalledWith('group 1');
expect(this.node.matches[2].render).toHaveBeenCalledWith('group 2');
});
describe('positioning of the match nodes', function() {
beforeEach(function() {
this.matchDeferred[0].resolve();
this.matchDeferred[1].resolve();
this.matchDeferred[2].resolve();
});
it('spaces the nodes vertically', function(done) {
this.node._render()
.then(() => {
expect(util.spaceVertically).toHaveBeenCalledWith(this.node.matches, { padding: 5 });
done();
});
});
it('renders the sides and curves into the container', function(done) {
this.node._render()
.then(() => {
expect(this.node.makeCurve).toHaveBeenCalledWith('container bbox', this.node.matches[0]);
expect(this.node.makeCurve).toHaveBeenCalledWith('container bbox', this.node.matches[1]);
expect(this.node.makeCurve).toHaveBeenCalledWith('container bbox', this.node.matches[2]);
expect(this.node.makeSide).toHaveBeenCalledWith('container bbox', this.node.matches[0]);
expect(this.node.makeSide).toHaveBeenCalledWith('container bbox', this.node.matches[2]);
expect(this.node.container.path).toHaveBeenCalledWith('curvecurvecurvesideside');
done();
});
});
it('renders the connectors into the match container', function(done) {
this.node._render()
.then(() => {
expect(this.node.makeConnector).toHaveBeenCalledWith('group bbox', this.node.matches[0]);
expect(this.node.makeConnector).toHaveBeenCalledWith('group bbox', this.node.matches[1]);
expect(this.node.makeConnector).toHaveBeenCalledWith('group bbox', this.node.matches[2]);
expect(this.group.path).toHaveBeenCalledWith('connectorconnectorconnector');
done();
});
});
});
});
describe('#madeSide', function() {
beforeEach(function() {
this.node = new javascript.Parser('a|b').__consume__regexp();
this.containerBox = {
cy: 50,
width: 30
};
this.matchBox = {
};
this.match = jasmine.createSpyObj('match', ['getBBox']);
this.match.getBBox.and.returnValue(this.matchBox);
});
describe('when the match node is 15px or more from the centerline', function() {
describe('when the match node is above the centerline', function() {
beforeEach(function() {
this.matchBox.ay = 22;
});
it('returns the vertical sideline to the match node', function() {
expect(this.node.makeSide(this.containerBox, this.match)).toEqual([
'M0,50q10,0 10,-10V32',
'M70,50q-10,0 -10,-10V32'
]);
});
});
describe('when the match node is below the centerline', function() {
beforeEach(function() {
this.matchBox.ay = 88;
});
it('returns the vertical sideline to the match node', function() {
expect(this.node.makeSide(this.containerBox, this.match)).toEqual([
'M0,50q10,0 10,10V78',
'M70,50q-10,0 -10,10V78'
]);
});
});
});
describe('when the match node is less than 15px from the centerline', function() {
beforeEach(function() {
this.matchBox.ay = 44;
});
it('returns nothing', function() {
expect(this.node.makeSide(this.containerBox, this.match)).toBeUndefined();
});
});
});
describe('#makeCurve', function() {
beforeEach(function() {
this.node = new javascript.Parser('a|b').__consume__regexp();
this.containerBox = {
cy: 50,
width: 30
};
this.matchBox = {};
this.match = jasmine.createSpyObj('match', ['getBBox']);
this.match.getBBox.and.returnValue(this.matchBox);
});
describe('when the match node is 15px or more from the centerline', function() {
describe('when the match node is above the centerline', function() {
beforeEach(function() {
this.matchBox.ay = 22;
});
it('returns the curve to the match node', function() {
expect(this.node.makeCurve(this.containerBox, this.match)).toEqual([
'M10,32q0,-10 10,-10',
'M60,32q0,-10 -10,-10'
]);
});
});
describe('when the match node is below the centerline', function() {
beforeEach(function() {
this.matchBox.ay = 88;
});
it('returns the curve to the match node', function() {
expect(this.node.makeCurve(this.containerBox, this.match)).toEqual([
'M10,78q0,10 10,10',
'M60,78q0,10 -10,10'
]);
});
});
});
describe('when the match node is less than 15px from the centerline', function() {
describe('when the match node is above the centerline', function() {
beforeEach(function() {
this.matchBox.ay = 44;
});
it('returns the curve to the match node', function() {
expect(this.node.makeCurve(this.containerBox, this.match)).toEqual([
'M0,50c10,0 10,-6 20,-6',
'M70,50c-10,0 -10,-6 -20,-6'
]);
});
});
describe('when the match node is below the centerline', function() {
beforeEach(function() {
this.matchBox.ay = 55;
});
it('returns the curve to the match node', function() {
expect(this.node.makeCurve(this.containerBox, this.match)).toEqual([
'M0,50c10,0 10,5 20,5',
'M70,50c-10,0 -10,5 -20,5'
]);
});
});
});
});
describe('#makeConnector', function() {
beforeEach(function() {
this.node = new javascript.Parser('a|b').__consume__regexp();
this.containerBox = {
width: 4
};
this.matchBox = {
ay: 1,
ax: 2,
ax2: 3
};
this.match = jasmine.createSpyObj('match', ['getBBox']);
this.match.getBBox.and.returnValue(this.matchBox);
});
it('returns a line from the curve to the match node', function() {
expect(this.node.makeConnector(this.containerBox, this.match)).toEqual('M0,1h2M3,1H4');
});
});
});

View File

@ -1,13 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
describe('parser/javascript/repeat_any.js', function() {
it('parses "*" as a RepeatAny', function() {
var parser = new javascript.Parser('*');
expect(parser.__consume__repeat_any()).toEqual(jasmine.objectContaining({
minimum: 0,
maximum: -1
}));
});
});

View File

@ -1,13 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
describe('parser/javascript/repeat_optional.js', function() {
it('parses "?" as a RepeatOptional', function() {
var parser = new javascript.Parser('?');
expect(parser.__consume__repeat_optional()).toEqual(jasmine.objectContaining({
minimum: 0,
maximum: 1
}));
});
});

View File

@ -1,13 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
describe('parser/javascript/repeat_required.js', function() {
it('parses "+" as a RepeatRequired', function() {
var parser = new javascript.Parser('+');
expect(parser.__consume__repeat_required()).toEqual(jasmine.objectContaining({
minimum: 1,
maximum: -1
}));
});
});

View File

@ -1,410 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import _ from 'lodash';
import Snap from 'snapsvg';
describe('parser/javascript/repeat.js', function() {
_.forIn({
'*': {
minimum: 0,
maximum: -1,
greedy: true,
hasSkip: true,
hasLoop: true
},
'*?': {
minimum: 0,
maximum: -1,
greedy: false,
hasSkip: true,
hasLoop: true
},
'+': {
minimum: 1,
maximum: -1,
greedy: true,
hasSkip: false,
hasLoop: true
},
'+?': {
minimum: 1,
maximum: -1,
greedy: false,
hasSkip: false,
hasLoop: true
},
'?': {
minimum: 0,
maximum: 1,
greedy: true,
hasSkip: true,
hasLoop: false
},
'??': {
minimum: 0,
maximum: 1,
greedy: false,
hasSkip: true,
hasLoop: false
},
'{1}': {
minimum: 1,
maximum: 1,
greedy: true,
hasSkip: false,
hasLoop: false
},
'{0}': {
minimum: 0,
maximum: 0,
greedy: true,
hasSkip: true,
hasLoop: false
},
'{1}?': {
minimum: 1,
maximum: 1,
greedy: false,
hasSkip: false,
hasLoop: false
},
'{2}': {
minimum: 2,
maximum: 2,
greedy: true,
hasSkip: false,
hasLoop: true
},
'{2}?': {
minimum: 2,
maximum: 2,
greedy: false,
hasSkip: false,
hasLoop: true
},
'{0,}': {
minimum: 0,
maximum: -1,
greedy: true,
hasSkip: true,
hasLoop: true
},
'{0,}?': {
minimum: 0,
maximum: -1,
greedy: false,
hasSkip: true,
hasLoop: true
},
'{1,}': {
minimum: 1,
maximum: -1,
greedy: true,
hasSkip: false,
hasLoop: true
},
'{1,}?': {
minimum: 1,
maximum: -1,
greedy: false,
hasSkip: false,
hasLoop: true
},
'{0,1}': {
minimum: 0,
maximum: 1,
greedy: true,
hasSkip: true,
hasLoop: false
},
'{0,1}?': {
minimum: 0,
maximum: 1,
greedy: false,
hasSkip: true,
hasLoop: false
},
'{0,2}': {
minimum: 0,
maximum: 2,
greedy: true,
hasSkip: true,
hasLoop: true
},
'{0,2}?': {
minimum: 0,
maximum: 2,
greedy: false,
hasSkip: true,
hasLoop: true
},
'{1,2}': {
minimum: 1,
maximum: 2,
greedy: true,
hasSkip: false,
hasLoop: true
},
'{1,2}?': {
minimum: 1,
maximum: 2,
greedy: false,
hasSkip: false,
hasLoop: true
}
}, (content, str) => {
it(`parses "${str}" as a Repeat`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__repeat()).toEqual(jasmine.objectContaining(content));
});
});
describe('contentPosition property', function() {
beforeEach(function() {
this.node = new javascript.Parser('*').__consume__repeat();
});
_.each([
{
hasLoop: false,
hasSkip: false,
translate: { x: 0, y: 0 }
},
{
hasLoop: true,
hasSkip: false,
translate: { x: 10, y: 0 }
},
{
hasLoop: false,
hasSkip: true,
translate: { x: 15, y: 10 }
},
{
hasLoop: true,
hasSkip: true,
translate: { x: 15, y: 10 }
}
], t => {
it(`translates to [${t.translate.x}, ${t.translate.y}] when hasLoop is ${t.hasLoop} and hasSkip is ${t.hasSkip}`, function() {
this.node.hasLoop = t.hasLoop;
this.node.hasSkip = t.hasSkip;
expect(this.node.contentPosition).toEqual(Snap.matrix()
.translate(t.translate.x, t.translate.y));
});
});
});
describe('label property', function() {
beforeEach(function() {
this.node = new javascript.Parser('*').__consume__repeat();
});
_.each([
{
minimum: 1,
maximum: -1,
label: undefined
},
{
minimum: 0,
maximum: 0,
label: undefined
},
{
minimum: 2,
maximum: -1,
label: '1+ times'
},
{
minimum: 3,
maximum: -1,
label: '2+ times'
},
{
minimum: 0,
maximum: 2,
label: 'at most once'
},
{
minimum: 0,
maximum: 3,
label: 'at most 2 times'
},
{
minimum: 2,
maximum: 2,
label: 'once'
},
{
minimum: 3,
maximum: 3,
label: '2 times'
},
{
minimum: 2,
maximum: 3,
label: '1\u20262 times'
},
{
minimum: 3,
maximum: 4,
label: '2\u20263 times'
}
], t => {
it(`is "${t.label}" when minimum=${t.minimum} and maximum=${t.maximum}`, function() {
this.node.minimum = t.minimum;
this.node.maximum = t.maximum;
expect(this.node.label).toEqual(t.label);
});
});
});
describe('tooltip property', function() {
beforeEach(function() {
this.node = new javascript.Parser('*').__consume__repeat();
});
_.each([
{
minimum: 1,
maximum: -1,
tooltip: undefined
},
{
minimum: 0,
maximum: 0,
tooltip: undefined
},
{
minimum: 2,
maximum: -1,
tooltip: 'repeats 2+ times in total'
},
{
minimum: 3,
maximum: -1,
tooltip: 'repeats 3+ times in total'
},
{
minimum: 0,
maximum: 2,
tooltip: 'repeats at most 2 times in total'
},
{
minimum: 0,
maximum: 3,
tooltip: 'repeats at most 3 times in total'
},
{
minimum: 2,
maximum: 2,
tooltip: 'repeats 2 times in total'
},
{
minimum: 3,
maximum: 3,
tooltip: 'repeats 3 times in total'
},
{
minimum: 2,
maximum: 3,
tooltip: 'repeats 2\u20263 times in total'
},
{
minimum: 3,
maximum: 4,
tooltip: 'repeats 3\u20264 times in total'
}
], t => {
it(`is "${t.tooltip}" when minimum=${t.minimum} and maximum=${t.maximum}`, function() {
this.node.minimum = t.minimum;
this.node.maximum = t.maximum;
expect(this.node.tooltip).toEqual(t.tooltip);
});
});
});
describe('#skipPath', function() {
beforeEach(function() {
this.node = new javascript.Parser('*').__consume__repeat();
this.box = {
y: 11,
ay: 22,
width: 33
};
});
it('returns nothing when there is no skip', function() {
this.node.hasSkip = false;
expect(this.node.skipPath(this.box)).toEqual([]);
});
it('returns a path when there is a skip', function() {
this.node.hasSkip = true;
this.node.greedy = true;
expect(this.node.skipPath(this.box)).toEqual([
'M0,22q10,0 10,-10v-1q0,-10 10,-10h23q10,0 10,10v1q0,10 10,10'
]);
});
it('returns a path with arrow when there is a non-greedy skip', function() {
this.node.hasSkip = true;
this.node.greedy = false;
expect(this.node.skipPath(this.box)).toEqual([
'M0,22q10,0 10,-10v-1q0,-10 10,-10h23q10,0 10,10v1q0,10 10,10',
'M10,7l5,5m-5,-5l-5,5'
]);
});
});
describe('#loopPath', function() {
beforeEach(function() {
this.node = new javascript.Parser('*').__consume__repeat();
this.box = {
x: 11,
x2: 22,
ay: 33,
y2: 44,
width: 55
};
});
it('returns nothing when there is no loop', function() {
this.node.hasLoop = false;
expect(this.node.loopPath(this.box)).toEqual([]);
});
it('returns a path when there is a loop', function() {
this.node.hasLoop = true;
this.node.greedy = false;
expect(this.node.loopPath(this.box)).toEqual([
'M11,33q-10,0 -10,10v1q0,10 10,10h55q10,0 10,-10v-1q0,-10 -10,-10'
]);
});
it('returns a path with arrow when there is a greedy loop', function() {
this.node.hasLoop = true;
this.node.greedy = true;
expect(this.node.loopPath(this.box)).toEqual([
'M11,33q-10,0 -10,10v1q0,10 10,10h55q10,0 10,-10v-1q0,-10 -10,-10',
'M32,48l5,-5m-5,5l-5,-5'
]);
});
});
});

View File

@ -1,41 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
describe('parser/javascript/repeat_spec.js', function() {
it('parses "{n,m}" as a RepeatSpec (with minimum and maximum values)', function() {
var parser = new javascript.Parser('{24,42}');
expect(parser.__consume__repeat_spec()).toEqual(jasmine.objectContaining({
minimum: 24,
maximum: 42
}));
});
it('parses "{n,}" as a RepeatSpec (with only minimum value)', function() {
var parser = new javascript.Parser('{24,}');
expect(parser.__consume__repeat_spec()).toEqual(jasmine.objectContaining({
minimum: 24,
maximum: -1
}));
});
it('parses "{n}" as a RepeatSpec (with an exact count)', function() {
var parser = new javascript.Parser('{24}');
expect(parser.__consume__repeat_spec()).toEqual(jasmine.objectContaining({
minimum: 24,
maximum: 24
}));
});
it('does not parse "{,m}" as a RepeatSpec', function() {
var parser = new javascript.Parser('{,42}');
expect(parser.__consume__repeat_spec()).toEqual(null);
});
it('throws an exception when the numbers are out of order', function() {
var parser = new javascript.Parser('{42,24}');
expect(() => {
parser.__consume__repeat_spec();
}).toThrow('Numbers out of order: {42,24}');
});
});

View File

@ -1,175 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import Snap from 'snapsvg';
import _ from 'lodash';
describe('parser/javascript/root.js', function() {
_.forIn({
'test': {
flags: [],
regexp: jasmine.objectContaining({ textValue: 'test' })
},
'/test/': {
flags: [],
regexp: jasmine.objectContaining({ textValue: 'test' })
},
'/test/i': {
flags: ['Ignore Case'],
regexp: jasmine.objectContaining({ textValue: 'test' })
},
'/test/g': {
flags: ['Global'],
regexp: jasmine.objectContaining({ textValue: 'test' })
},
'/test/m': {
flags: ['Multiline'],
regexp: jasmine.objectContaining({ textValue: 'test' })
},
'/test/y': {
flags: ['Sticky'],
regexp: jasmine.objectContaining({ textValue: 'test' })
},
'/test/u': {
flags: ['Unicode'],
regexp: jasmine.objectContaining({ textValue: 'test' })
},
'/test/mgi': {
flags: ['Global', 'Ignore Case', 'Multiline'],
regexp: jasmine.objectContaining({ textValue: 'test' })
}
}, (content, str) => {
it(`parses "${str}" as a Root`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__root()).toEqual(jasmine.objectContaining(content));
});
});
describe('#_render', function() {
beforeEach(function() {
this.textElement = jasmine.createSpyObj('text', ['getBBox']);
this.textElement.getBBox.and.returnValue({
height: 20
});
this.node = new javascript.Parser('test').__consume__root();
this.node.container = jasmine.createSpyObj('container', [
'addClass',
'text',
'group',
'path',
'circle'
]);
this.node.container.text.and.returnValue(this.textElement);
this.node.container.group.and.returnValue('group element');
this.node.regexp = jasmine.createSpyObj('regexp', [
'render',
'transform',
'getBBox'
]);
this.renderDeferred = this.testablePromise();
this.node.regexp.render.and.returnValue(this.renderDeferred.promise);
});
it('renders the regexp', function() {
this.node._render();
expect(this.node.regexp.render).toHaveBeenCalledWith('group element');
});
describe('when there are flags', function() {
beforeEach(function() {
this.node.flags = ['example', 'flags'];
});
it('renders a text element', function() {
this.node._render();
expect(this.node.container.text).toHaveBeenCalledWith(0, 0, 'Flags: example, flags');
});
});
describe('when there are no flags', function() {
beforeEach(function() {
this.node.flags = [];
});
it('does not render a text element', function() {
this.node._render();
expect(this.node.container.text).not.toHaveBeenCalled();
});
});
describe('positioning of elements', function() {
beforeEach(function() {
this.renderDeferred.resolve();
this.node.regexp.getBBox.and.returnValue({
ax: 1,
ay: 2,
ax2: 3,
x2: 4
});
});
it('renders a path element to lead in and out of the regexp', function(done) {
this.node._render()
.then(() => {
expect(this.node.container.path).toHaveBeenCalledWith('M1,2H0M3,2H14');
done();
});
});
it('renders circle elements before and after the regexp', function(done) {
this.node._render()
.then(() => {
expect(this.node.container.circle).toHaveBeenCalledWith(0, 2, 5);
expect(this.node.container.circle).toHaveBeenCalledWith(14, 2, 5);
done();
});
});
describe('when there are flags', function() {
beforeEach(function() {
this.node.flags = ['example'];
});
it('moves the regexp below the flag text', function(done) {
this.node._render()
.then(() => {
expect(this.node.regexp.transform).toHaveBeenCalledWith(Snap.matrix()
.translate(10, 20));
done();
});
});
});
describe('when there are no flags', function() {
beforeEach(function() {
this.node.flags = [];
});
it('positions the regexp', function(done) {
this.node._render()
.then(() => {
expect(this.node.regexp.transform).toHaveBeenCalledWith(Snap.matrix()
.translate(10, 0));
done();
});
});
});
});
});
});

View File

@ -1,120 +0,0 @@
import javascript from '../../../src/js/parser/javascript/parser.js';
import Node from '../../../src/js/parser/javascript/node.js';
import _ from 'lodash';
import Snap from 'snapsvg';
describe('parser/javascript/subexp.js', function() {
beforeEach(function() {
Node.state = { groupCounter: 1 };
});
_.forIn({
'(test)': {
regexp: jasmine.objectContaining({ textValue: 'test' })
},
'(?=test)': {
regexp: jasmine.objectContaining({ textValue: 'test' })
},
'(?!test)': {
regexp: jasmine.objectContaining({ textValue: 'test' })
},
'(?:test)': {
regexp: jasmine.objectContaining({ textValue: 'test' }),
proxy: jasmine.objectContaining({ textValue: 'test' })
}
}, (content, str) => {
it(`parses "${str}" as a Subexp`, function() {
var parser = new javascript.Parser(str);
expect(parser.__consume__subexp()).toEqual(jasmine.objectContaining(content));
});
});
describe('_anchor property', function() {
it('applies the local transform matrix to the anchor from the regexp', function() {
var node = new javascript.Parser('(test)').__consume__subexp();
node.regexp = {
getBBox() {
return {
ax: 10,
ax2: 15,
ay: 20
};
}
};
spyOn(node, 'transform').and.returnValue({
localMatrix: Snap.matrix().translate(3, 8)
});
expect(node._anchor).toEqual({
ax: 13,
ax2: 18,
ay: 28
});
});
});
describe('#_render', function() {
beforeEach(function() {
this.renderDeferred = this.testablePromise();
this.node = new javascript.Parser('(test)').__consume__subexp();
this.node.regexp = jasmine.createSpyObj('regexp', ['render']);
this.node.container = jasmine.createSpyObj('container', ['addClass', 'group']);
spyOn(this.node, 'label').and.returnValue('example label')
this.node.regexp.render.and.returnValue(this.renderDeferred.promise);
});
it('renders the regexp', function() {
this.node._render();
expect(this.node.regexp.render).toHaveBeenCalled();
});
it('renders a labeled box', function(done) {
spyOn(this.node, 'renderLabeledBox');
this.renderDeferred.resolve();
this.node._render()
.then(() => {
expect(this.node.renderLabeledBox).toHaveBeenCalledWith('example label', this.node.regexp, { padding: 10 });
done();
});
});
});
describe('#label', function() {
_.forIn({
'(test)': {
label: 'group #1',
groupCounter: 2
},
'(?=test)': {
label: 'positive lookahead',
groupCounter: 1
},
'(?!test)': {
label: 'negative lookahead',
groupCounter: 1
},
'(?:test)': {
label: '',
groupCounter: 1
}
}, (data, str) => {
it(`generates the correct label for "${str}"`, function() {
var node = new javascript.Parser(str).__consume__subexp();
expect(node.label()).toEqual(data.label);
expect(node.state.groupCounter).toEqual(data.groupCounter);
});
});
});
});

View File

@ -1,173 +0,0 @@
import Parser from '../../src/js/parser/javascript.js';
import regexpParser from '../../src/js/parser/javascript/grammar.peg';
import Snap from 'snapsvg';
describe('parser/javascript.js', function() {
beforeEach(function() {
this.container = document.createElement('div');
this.parser = new Parser(this.container);
});
describe('container property', function() {
it('sets the content of the element', function() {
var element = document.createElement('div');
this.parser.container = element;
expect(element.innerHTML).not.toEqual('');
});
it('keeps the original content if the keepContent option is set', function() {
var element = document.createElement('div');
element.innerHTML = 'example content';
this.parser.options.keepContent = true;
this.parser.container = element;
expect(element.innerHTML).toContain('example content');
expect(element.innerHTML).not.toEqual('example content');
});
it('adds the "svg-container" class', function() {
spyOn(this.parser, '_addClass');
this.parser.container = document.createElement('div');
expect(this.parser._addClass).toHaveBeenCalledWith('svg-container');
});
});
describe('#parse', function() {
beforeEach(function() {
spyOn(regexpParser, 'parse');
});
it('adds the "loading" class', function() {
spyOn(this.parser, '_addClass');
this.parser.parse('example expression');
expect(this.parser._addClass).toHaveBeenCalledWith('loading');
});
it('parses the expression', function(done) {
this.parser.parse('example expression')
.then(() => {
expect(regexpParser.parse).toHaveBeenCalledWith('example expression');
done();
});
});
it('replaces newlines with "\\n"', function(done) {
this.parser.parse('multiline\nexpression')
.then(() => {
expect(regexpParser.parse).toHaveBeenCalledWith('multiline\\nexpression');
done();
});
});
it('resolves the returned promise with the parser instance', function(done) {
this.parser.parse('example expression')
.then(result => {
expect(result).toEqual(this.parser);
done();
});
});
it('rejects the returned promise with the exception thrown', function(done) {
regexpParser.parse.and.throwError('fail');
this.parser.parse('(example')
.then(null, result => {
expect(result).toBeDefined();
done();
});
});
});
describe('#render', function() {
beforeEach(function() {
this.renderPromise = this.testablePromise();
this.parser.parsed = jasmine.createSpyObj('parsed', ['render']);
this.parser.parsed.render.and.returnValue(this.renderPromise.promise);
});
it('render the parsed expression', function() {
this.parser.render();
expect(this.parser.parsed.render).toHaveBeenCalled();
});
describe('when rendering is complete', function() {
beforeEach(function() {
this.result = jasmine.createSpyObj('result', ['getBBox', 'transform']);
this.result.getBBox.and.returnValue({
x: 4,
y: 2,
width: 42,
height: 24
});
this.renderPromise.resolve(this.result);
});
it('positions the renderd expression', function(done) {
this.parser.render()
.then(() => {
expect(this.result.transform).toHaveBeenCalledWith(Snap.matrix()
.translate(6, 8));
done();
});
});
it('sets the dimensions of the image', function(done) {
this.parser.render()
.then(() => {
let svg = this.container.querySelector('svg');
expect(svg.getAttribute('width')).toEqual('62');
expect(svg.getAttribute('height')).toEqual('44');
done();
});
});
it('removes the "loading" class', function(done) {
spyOn(this.parser, '_removeClass');
this.parser.render()
.then(() => {
expect(this.parser._removeClass).toHaveBeenCalledWith('loading');
done();
});
});
it('removes the progress element', function(done) {
this.parser.render()
.then(() => {
expect(this.container.querySelector('.loading')).toBeNull();
done();
});
});
});
});
describe('#cancel', function() {
it('sets the cancelRender state to true', function() {
this.parser.cancel();
expect(this.parser.state.cancelRender).toEqual(true);
});
});
describe('warnings property', function() {
it('returns the content of the warnings state variable', function() {
this.parser.state.warnings.push('example');
expect(this.parser.warnings).toEqual(['example']);
});
});
});

View File

@ -1,569 +0,0 @@
import util from '../src/js/util.js';
import Regexper from '../src/js/regexper.js';
import Parser from '../src/js/parser/javascript.js';
import Snap from 'snapsvg';
describe('regexper.js', function() {
beforeEach(function() {
this.root = document.createElement('div');
this.root.innerHTML = [
'<form id="regexp-form" action="/">',
'<input type="text" id="regexp-input">',
'<ul class="example">',
'<ul><a href="#" data-action="permalink"></a></ul>',
'<ul><a href="#" data-action="download-svg"></a></ul>',
'<ul><a href="#" data-action="download-png"></a></ul>',
'</ul>',
'</form>',
'<div id="error"></div>',
'<ul id="warnings"></ul>',
'<div id="regexp-render"></div>'
].join('');
this.regexper = new Regexper(this.root);
spyOn(this.regexper, '_setHash');
spyOn(this.regexper, '_getHash');
});
describe('#keypressListener', function() {
beforeEach(function() {
this.event = util.customEvent('keypress');
spyOn(this.event, 'preventDefault');
spyOn(this.regexper.form, 'dispatchEvent');
});
describe('when the shift key is not depressed', function() {
beforeEach(function() {
this.event.shiftKey = false;
this.event.keyCode = 13;
});
it('does not prevent the default action', function() {
this.regexper.keypressListener(this.event);
expect(this.event.returnValue).not.toEqual(false);
expect(this.event.preventDefault).not.toHaveBeenCalled();
});
it('does not trigger a submit event', function() {
this.regexper.keypressListener(this.event);
expect(this.regexper.form.dispatchEvent).not.toHaveBeenCalled();
});
});
describe('when the keyCode is not 13 (Enter)', function() {
beforeEach(function() {
this.event.shiftKey = true;
this.event.keyCode = 42;
});
it('does not prevent the default action', function() {
this.regexper.keypressListener(this.event);
expect(this.event.returnValue).not.toEqual(false);
expect(this.event.preventDefault).not.toHaveBeenCalled();
});
it('does not trigger a submit event', function() {
this.regexper.keypressListener(this.event);
expect(this.regexper.form.dispatchEvent).not.toHaveBeenCalled();
});
});
describe('when the shift key is depressed and the keyCode is 13 (Enter)', function() {
beforeEach(function() {
this.event.shiftKey = true;
this.event.keyCode = 13;
});
it('prevents the default action', function() {
this.regexper.keypressListener(this.event);
expect(this.event.returnValue).not.toEqual(true);
expect(this.event.preventDefault).toHaveBeenCalled();
});
it('triggers a submit event', function() {
var event;
this.regexper.keypressListener(this.event);
expect(this.regexper.form.dispatchEvent).toHaveBeenCalled();
event = this.regexper.form.dispatchEvent.calls.mostRecent().args[0];
expect(event.type).toEqual('submit');
});
});
});
describe('#documentKeypressListener', function() {
beforeEach(function() {
this.event = util.customEvent('keyup');
this.regexper.running = jasmine.createSpyObj('parser', ['cancel']);
});
describe('when the keyCode is not 27 (Escape)', function() {
beforeEach(function() {
this.event.keyCode = 42;
});
it('does not cancel the parser', function() {
this.regexper.documentKeypressListener(this.event);
expect(this.regexper.running.cancel).not.toHaveBeenCalled();
});
});
describe('when the keyCode is 27 (Escape)', function() {
beforeEach(function() {
this.event.keyCode = 27;
});
it('cancels the parser', function() {
this.regexper.documentKeypressListener(this.event);
expect(this.regexper.running.cancel).toHaveBeenCalled();
});
});
});
describe('#submitListener', function() {
beforeEach(function() {
this.event = util.customEvent('submit');
spyOn(this.event, 'preventDefault');
this.regexper.field.value = 'example value';
});
it('prevents the default action', function() {
this.regexper.submitListener(this.event);
expect(this.event.returnValue).not.toEqual(true);
expect(this.event.preventDefault).toHaveBeenCalled();
});
it('sets the location.hash', function() {
this.regexper.submitListener(this.event);
expect(this.regexper._setHash).toHaveBeenCalledWith('example value');
});
describe('when setting location.hash fails', function() {
beforeEach(function() {
this.regexper._setHash.and.throwError('hash failure');
});
it('disables the permalink', function() {
this.regexper.submitListener(this.event);
expect(this.regexper.permalinkEnabled).toEqual(false);
});
it('shows the expression directly', function() {
spyOn(this.regexper, 'showExpression');
this.regexper.submitListener(this.event);
expect(this.regexper.showExpression).toHaveBeenCalledWith('example value');
});
});
});
describe('#hashchangeListener', function() {
describe('when the URL is invalid', function() {
beforeEach(function() {
this.regexper._getHash.and.returnValue(new Error('example error'));
});
it('displays an error message', function() {
this.regexper.hashchangeListener();
expect(this.regexper.state).toEqual('has-error');
expect(this.regexper.error.innerHTML).toEqual('Malformed expression in URL');
});
it('tracks the event', function() {
this.regexper.hashchangeListener();
expect(util.track).toHaveBeenCalledWith('send', 'event', 'visualization', 'malformed URL');
});
});
describe('when the URL is valid', function() {
beforeEach(function() {
this.regexper._getHash.and.returnValue('example hash value');
});
it('enables the permalink', function() {
this.regexper.hashchangeListener();
expect(this.regexper.permalinkEnabled).toEqual(true);
});
it('shows the expression from the hash', function() {
spyOn(this.regexper, 'showExpression');
this.regexper.hashchangeListener();
expect(this.regexper.showExpression).toHaveBeenCalledWith('example hash value');
});
});
});
describe('#bindListeners', function() {
beforeEach(function() {
spyOn(this.regexper, 'keypressListener');
spyOn(this.regexper, 'submitListener');
spyOn(this.regexper, 'documentKeypressListener');
spyOn(this.regexper, 'hashchangeListener');
});
it('binds #keypressListener to keypress on the text field', function() {
spyOn(this.regexper.field, 'addEventListener');
this.regexper.bindListeners();
expect(this.regexper.field.addEventListener).toHaveBeenCalledWith('keypress', jasmine.any(Function));
this.regexper.field.addEventListener.calls.mostRecent().args[1]();
expect(this.regexper.keypressListener).toHaveBeenCalled();
});
it('binds #submitListener to submit on the form', function() {
spyOn(this.regexper.form, 'addEventListener');
this.regexper.bindListeners();
expect(this.regexper.form.addEventListener).toHaveBeenCalledWith('submit', jasmine.any(Function));
this.regexper.form.addEventListener.calls.mostRecent().args[1]();
expect(this.regexper.submitListener).toHaveBeenCalled();
});
it('binds #documentKeypressListener to keyup on the root', function() {
spyOn(this.regexper.root, 'addEventListener');
this.regexper.bindListeners();
expect(this.regexper.root.addEventListener).toHaveBeenCalledWith('keyup', jasmine.any(Function));
this.regexper.root.addEventListener.calls.mostRecent().args[1]();
expect(this.regexper.documentKeypressListener).toHaveBeenCalled();
});
it('binds #hashchangeListener to hashchange on the window', function() {
spyOn(window, 'addEventListener');
this.regexper.bindListeners();
expect(window.addEventListener).toHaveBeenCalledWith('hashchange', jasmine.any(Function));
window.addEventListener.calls.mostRecent().args[1]();
expect(this.regexper.hashchangeListener).toHaveBeenCalled();
});
});
describe('#showExpression', function() {
beforeEach(function() {
spyOn(this.regexper, 'renderRegexp').and.returnValue(jasmine.createSpyObj('renderRegexp', ['catch']));
});
it('sets the text field value', function() {
this.regexper.showExpression('example expression');
expect(this.regexper.field.value).toEqual('example expression');
});
it('clears the state', function() {
this.regexper.showExpression('');
expect(this.regexper.state).toEqual('');
});
describe('when the expression is not blank', function() {
it('renders the expression', function() {
this.regexper.showExpression('example expression');
expect(this.regexper.renderRegexp).toHaveBeenCalledWith('example expression');
});
});
});
describe('#updateLinks', function() {
beforeEach(function() {
spyOn(this.regexper, 'buildBlobURL');
this.regexper.svgContainer.innerHTML = '<div class="svg">example image</div>';
});
it('builds the blob URL from the SVG image', function() {
this.regexper.updateLinks();
expect(this.regexper.buildBlobURL).toHaveBeenCalledWith('example image');
});
describe('when blob URLs are supported', function() {
beforeEach(function() {
this.regexper.buildBlobURL.and.returnValue('http://example.com/blob');
});
it('sets the download link href', function() {
this.regexper.updateLinks();
expect(this.regexper.downloadSvg.href).toEqual('http://example.com/blob');
});
});
describe('when blob URLs are not supported', function() {
beforeEach(function() {
this.regexper.buildBlobURL.and.throwError('blob failure');
});
it('hides the download link', function() {
this.regexper.updateLinks();
expect(this.regexper.links.className).toMatch(/\bexample\b/);
expect(this.regexper.links.className).toMatch(/\bhide-download\b/);
});
});
describe('when the permalink is enabled', function() {
beforeEach(function() {
this.regexper.permalinkEnabled = true;
});
it('sets the permalink href', function() {
this.regexper.updateLinks();
expect(this.regexper.permalink.href).toEqual(location.toString());
});
});
describe('when the permalink is disabled', function() {
beforeEach(function() {
this.regexper.permalinkEnabled = false;
});
it('hides the permalink', function() {
this.regexper.updateLinks();
expect(this.regexper.links.className).toMatch(/\bexample\b/);
expect(this.regexper.links.className).toMatch(/\bhide-permalink\b/);
});
});
});
describe('#displayWarnings', function() {
it('adds a list item for each warning', function() {
spyOn(util, 'icon').and.returnValue('(icon-markup)');
this.regexper.displayWarnings(['warning 1', 'warning 2']);
expect(this.regexper.warnings.innerHTML).toEqual('<li class="inline-icon">(icon-markup)warning 1</li><li class="inline-icon">(icon-markup)warning 2</li>');
});
});
describe('#renderRegexp', function() {
beforeEach(function() {
this.parsePromise = this.testablePromise();
this.renderPromise = this.testablePromise();
spyOn(Parser.prototype, 'parse').and.returnValue(this.parsePromise.promise);
spyOn(Parser.prototype, 'render').and.returnValue(this.renderPromise.promise);
spyOn(Parser.prototype, 'cancel');
spyOn(this.regexper, 'updateLinks');
spyOn(this.regexper, 'displayWarnings');
});
it('sets the state to "is-loading"', function() {
this.regexper.renderRegexp('example expression');
expect(this.regexper.state).toEqual('is-loading');
});
it('tracks the beginning of the render', function() {
this.regexper.renderRegexp('example expression');
expect(util.track).toHaveBeenCalledWith('send', 'event', 'visualization', 'start');
});
it('keeps a copy of the running property parser', function() {
this.regexper.renderRegexp('example expression');
expect(this.regexper.running).toBeTruthy();
});
it('parses the expression', function() {
this.regexper.renderRegexp('example expression');
expect(this.regexper.running.parse).toHaveBeenCalledWith('example expression');
});
describe('when parsing fails', function() {
beforeEach(function() {
this.parsePromise.reject(new Error('example parse error'));
});
it('sets the state to be "has-error"', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.regexper.state).toEqual('has-error');
done();
});
});
it('displays the error message', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.regexper.error.innerHTML).toEqual('Error: example parse error');
done();
});
});
it('tracks the parse error', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(util.track).toHaveBeenCalledWith('send', 'event', 'visualization', 'parse error');
done();
});
});
});
describe('when parsing succeeds', function() {
beforeEach(function() {
this.parser = new Parser(this.regexper.svgContainer);
this.parsePromise.resolve(this.parser);
this.renderPromise.resolve();
});
it('renders the expression', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.parser.render).toHaveBeenCalled();
done();
});
});
});
describe('when rendering is complete', function() {
beforeEach(function() {
this.parser = new Parser(this.regexper.svgContainer);
this.parsePromise.resolve(this.parser);
this.renderPromise.resolve();
});
it('sets the state to "has-results"', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.regexper.state).toEqual('has-results');
done();
});
});
it('updates the links', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.regexper.updateLinks).toHaveBeenCalled();
done();
});
});
it('displays the warnings', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.regexper.displayWarnings).toHaveBeenCalled();
done();
});
});
it('tracks the complete render', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(util.track).toHaveBeenCalledWith('send', 'event', 'visualization', 'complete');
done();
});
});
it('sets the running property to false', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.regexper.running).toBeFalsy();
done();
});
});
it('tracks the total rendering time', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(util.track).toHaveBeenCalledWith('send', 'timing', 'visualization', 'total time', jasmine.any(Number));
done();
});
});
});
describe('when the rendering is cancelled', function() {
beforeEach(function() {
this.parser = new Parser(this.regexper.svgContainer);
this.parsePromise.resolve(this.parser);
this.renderPromise.reject('Render cancelled');
});
it('clears the state', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.regexper.state).toEqual('');
done();
});
});
it('tracks the cancelled render', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(util.track).toHaveBeenCalledWith('send', 'event', 'visualization', 'cancelled');
done();
});
});
it('sets the running property to false', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.regexper.running).toBeFalsy();
done();
});
});
});
describe('when the rendering fails', function() {
beforeEach(function() {
this.parser = new Parser(this.regexper.svgContainer);
this.parsePromise.resolve(this.parser);
this.renderPromise.reject('example render failure');
});
it('sets the running property to false', function(done) {
this.regexper.renderRegexp('example expression')
.then(fail, () => {
expect(this.regexper.running).toBeFalsy();
done();
});
});
});
});
});

View File

@ -1,33 +0,0 @@
import util from '../src/js/util.js';
// Setup (and teardown) SVG container template
beforeEach(function() {
var template = document.createElement('script');
template.setAttribute('type', 'text/html');
template.setAttribute('id', 'svg-container-base');
template.innerHTML = [
'<div class="svg"><svg></svg></div>',
'<div class="progress"><div></div></div>'
].join('');
document.body.appendChild(template);
this.testablePromise = function() {
var result = {};
result.promise = new Promise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
});
afterEach(function() {
document.body.removeChild(document.body.querySelector('#svg-container-base'));
});
// Spy on util.track to prevent unnecessary logging
beforeEach(function() {
spyOn(util, 'track');
});

View File

@ -1,2 +0,0 @@
var testsContext = require.context(".", true, /_spec$/);
testsContext.keys().forEach(testsContext);

View File

@ -1,97 +0,0 @@
import util from '../src/js/util.js';
describe('util.js', function() {
describe('customEvent', function() {
it('sets the event type', function() {
var event = util.customEvent('example');
expect(event.type).toEqual('example');
});
it('sets the event detail', function() {
var event = util.customEvent('example', 'detail');
expect(event.detail).toEqual('detail');
});
});
describe('normalizeBBox', function() {
it('defaults the anchor keys to values from the bbox', function() {
expect(util.normalizeBBox({
x: 'bbox x',
x2: 'bbox x2',
cy: 'bbox cy',
ay: 'bbox ay'
})).toEqual({
x: 'bbox x',
x2: 'bbox x2',
cy: 'bbox cy',
ax: 'bbox x',
ax2: 'bbox x2',
ay: 'bbox ay'
});
});
});
describe('spaceHorizontally', function() {
it('positions each item', function() {
var svg = Snap(document.createElement('svg')),
items = [
svg.group(),
svg.group(),
svg.group()
];
spyOn(items[0], 'getBBox').and.returnValue({ ay: 5, width: 10 });
spyOn(items[1], 'getBBox').and.returnValue({ ay: 15, width: 30 });
spyOn(items[2], 'getBBox').and.returnValue({ ay: 10, width: 20 });
spyOn(items[0], 'transform').and.callThrough();
spyOn(items[1], 'transform').and.callThrough();
spyOn(items[2], 'transform').and.callThrough();
util.spaceHorizontally(items, { padding: 5 });
expect(items[0].transform).toHaveBeenCalledWith(Snap.matrix()
.translate(0, 10));
expect(items[1].transform).toHaveBeenCalledWith(Snap.matrix()
.translate(15, 0));
expect(items[2].transform).toHaveBeenCalledWith(Snap.matrix()
.translate(50, 5));
});
});
describe('spaceVertically', function() {
it('positions each item', function() {
var svg = Snap(document.createElement('svg')),
items = [
svg.group(),
svg.group(),
svg.group()
];
spyOn(items[0], 'getBBox').and.returnValue({ cx: 5, height: 10 });
spyOn(items[1], 'getBBox').and.returnValue({ cx: 15, height: 30 });
spyOn(items[2], 'getBBox').and.returnValue({ cx: 10, height: 20 });
spyOn(items[0], 'transform').and.callThrough();
spyOn(items[1], 'transform').and.callThrough();
spyOn(items[2], 'transform').and.callThrough();
util.spaceVertically(items, { padding: 5 });
expect(items[0].transform).toHaveBeenCalledWith(Snap.matrix()
.translate(10, 0));
expect(items[1].transform).toHaveBeenCalledWith(Snap.matrix()
.translate(0, 15));
expect(items[2].transform).toHaveBeenCalledWith(Snap.matrix()
.translate(5, 50));
});
});
});

View File

@ -1,17 +0,0 @@
---
title: Page Not Found
---
{{#extend "layout"}}
{{#content "body"}}
<div class="error copy">
<h1>404: Not Found</h1>
<blockquote>
Some people, when confronted with a problem, think<br/>
&ldquo;I know, I'll use regular expressions.&rdquo; Now they have two problems.
</blockquote>
<p>Apparently, you have three problems&hellip;because the page you requested cannot be found.</p>
</div>
{{/content}}
{{/extend}}

View File

@ -1,17 +0,0 @@
---
title: Changelog
---
{{#extend "layout"}}
{{#content "body"}}
<div class="copy changelog">
<dl>
{{#each changelog}}
<dt>{{label}}</dt>
{{#each changes}}
<dd>{{{this}}}</dd>
{{/each}}
{{/each}}
</dl>
</div>
{{/content}}
{{/extend}}

View File

@ -1,138 +0,0 @@
---
title: Documentation
---
{{#extend "layout"}}
{{#content "body"}}
<div class="copy documentation">
<section>
<h1>Reading Railroad Diagrams</h1>
<p>The images generated by Regexper are commonly referred to as "Railroad Diagrams". These diagram are a straight-forward way to illustrate what can sometimes become very complicated processing in a regular expression, with nested looping and optional elements. The easiest way to read these diagrams to to start at the left and follow the lines to the right. If you encounter a branch, then there is the option of following one of multiple paths (and those paths can loop back to earlier parts of the diagram). In order for a string to successfully match the regular expression in a diagram, you must be able to fulfill each part of the diagram as you move from left to right and proceed through the entire diagram to the end.</p>
<figure class="shift-right" data-expr="Lions(?: and|,) tigers,? and bears\. Oh my!"></figure>
<p>As an example, this expression will match "Lions and tigers and bears. Oh my!" or the more grammatically correct "Lions, tigers, and bears. Oh my!" (with or without an Oxford comma). The diagram first matches the string "Lions"; you cannot proceed without that in your input. Then there is a choice between a comma or the string " and". No matter what choice you make, the input string must then contain " tigers" followed by an optional comma (your path can either go through the comma or around it). Finally the string must end with " and bears. Oh my!".</p>
<section>
<h2>Basic parts of these diagrams</h2>
<p>The simplest pieces of these diagrams to understand are the parts that match some specific bit of text without an options. They are: Literals, Escape sequences, and "Any charater".</p>
<div class="section">
<h3>Literals</h3>
<figure class="shift-left" data-expr="A literal example"></figure>
<p>Literals match an exact string of text. They're displayed in a light blue box, and the contents are quoted (to make it easier to see any leading or trailing whitespace).</p>
</div>
<div class="section">
<h3>Escape sequences</h3>
<figure class="shift-left" data-expr="\w\x7f\u00bb\1\0"></figure>
<p>Escape sequences are displayed in a green box and contain a description of the type of character(s) they will match.</p>
</div>
<div class="section">
<h3>"Any character"</h3>
<figure class="shift-left" data-expr="."></figure>
<p>"Any character" is similar to an escape sequence. It matches any single character.</p>
</div>
</section>
<section>
<h2>Character Sets</h2>
<figure class="shift-left" data-expr="[#a-z\n][^$0-9\b]"></figure>
<p>Character sets will match or not match a collection of individual characters. They are shown as a box containing literals and escape sequences. The label at the top indicates that the character set will match "One of" the contained items or "None of" the contained items.</p>
</section>
<section>
<h2>Subexpressions</h2>
<figure class="shift-left" data-expr="(example\s)(?=content)"></figure>
<p>Subexpressions are indicated by a dotted outline around the items that are in the expression. Captured subexpressions are labeled with the group number they will be captured under. Positive and negative lookahead are labeled as such.</p>
</section>
<section>
<h2>Alternation</h2>
<figure class="shift-left" data-expr="one\s|two\W|three\t|four\n"></figure>
<p>Alternation provides choices for the regular experssion. It is indicated by the path for the expression fanning out into a number of choices.</p>
</section>
<section>
<h2>Quantifiers</h2>
<p>Quantifiers indicate if part of the expression should be repeated or optional. They are displayed similarly to Alternation, by the path through the diagram branching (and possibly looping back on itself). Unless indicated by an arrow on the path, the preferred path is to continue going straight.</p>
<div class="section">
<h3>Zero-or-more</h3>
<figure class="shift-left" data-expr="(?:greedy)*">
<figcaption>Greedy quantifier</figcaption>
</figure>
<figure class="shift-left" data-expr="(?:non-greedy)*?">
<figcaption>Non-greedy quantifier</figcaption>
</figure>
<p>The zero-or-more quantifier matches any number of repetitions of the pattern.</p>
</div>
<div class="section">
<h3>Required</h3>
<figure class="shift-left" data-expr="(?:greedy)+">
<figcaption>Greedy quantifier</figcaption>
</figure>
<figure class="shift-left" data-expr="(?:non-greedy)+?">
<figcaption>Non-greedy quantifier</figcaption>
</figure>
<p>The required quantifier matches one or more repetitions of the pattern. Note that it does not have the path that allows the pattern to be skipped like the zero-or-more quantifier.</p>
</div>
<div class="section">
<h3>Optional</h3>
<figure class="shift-left" data-expr="(?:greedy)?">
<figcaption>Greedy quantifier</figcaption>
</figure>
<figure class="shift-left" data-expr="(?:non-greedy)??">
<figcaption>Non-greedy quantifier</figcaption>
</figure>
<p>The optional quantifier matches the pattern at most once. Note that it does not have the path that allows the pattern to loop back on itself like the zero-or-more or required quantifiers.</p>
</div>
<div class="section">
<h3>Range</h3>
<figure class="shift-left" data-expr="(?:greedy){5,10}">
<figcaption>Greedy quantifier</figcaption>
</figure>
<figure class="shift-left" data-expr="(?:non-greedy){5,10}?">
<figcaption>Non-greedy quantifier</figcaption>
</figure>
<p>The ranged quantifier specifies a number of times the pattern may be repeated. The two examples provided here both have a range of "{5,10}", the label for the looping branch indicates the number of times that branch may be followed. The values are one less than specified in the expression since the pattern would have to be matched once before repeating it is an option. So, for these examples, the pattern would be matched once and then the loop would be followed 4 to 9 times, for a total of 5 to 10 matches of the pattern.</p>
</div>
</section>
</section>
</div>
{{/content}}
{{#content "footer" mode="append"}}
<script src="/js/main.js" async defer></script>
{{/content}}
{{/extend}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -1,16 +0,0 @@
# humanstxt.org/
# The humans responsible & technology colophon
# TEAM
Creator: Jeff Avallone
Site: http://github.com/javallone
Twitter: @javallone
# THANKS
strfriend.com for the idea, whatever happened to you?
# TECHNOLOGY COLOPHON
HTML5, CSS3, SVG, Sass, Open Iconic

View File

@ -1,35 +0,0 @@
{{#extend "layout"}}
{{#content "body"}}
<div class="application">
<form id="regexp-form">
<textarea id="regexp-input" autofocus="autofocus" placeholder="Enter JavaScript-style regular expression to display"></textarea>
<button type="submit">Display</button>
<ul class="inline-list">
<li class="download-svg">
<a href="#" class="inline-icon" data-action="download-svg" download="image.svg" type="image/svg+xml">{{icon "#data-transfer-download"}}Download SVG</a>
</li>
<li class="download-png">
<a href="#" class="inline-icon" data-action="download-png" download="image.png" type="image/png">{{icon "#data-transfer-download"}}Download PNG</a>
</li>
<li class="permalink">
<a href="#" class="inline-icon" data-action="permalink">{{icon "#link-intact"}}Permalink</a>
</li>
</ul>
</form>
</div>
<div class="results">
<div id="error"></div>
<ul id="warnings"></ul>
<div id="regexp-render"></div>
</div>
{{/content}}
{{#content "footer" mode="append"}}
<script src="/js/main.js" async defer></script>
{{/content}}
{{/extend}}

View File

View File

@ -1,38 +0,0 @@
// This file contains code to start up pages on the site, and other code that
// is not directly related to parsing and display of regular expressions.
//
// Since the code in this is executed immediately, it is all but impossible to
// test. Therefore, this code is kept as simple as possible to reduce the need
// to run it through automated tests.
import util from './util.js';
import Regexper from './regexper.js';
import Parser from './parser/javascript.js';
import _ from 'lodash';
(function() {
// Initialize the main page of the site. Functionality is kept in the
// [Regexper class](./regexper.html).
if (document.body.querySelector('#content .application')) {
let regexper = new Regexper(document.body);
regexper.detectBuggyHash();
regexper.bindListeners();
util.tick().then(() => {
window.dispatchEvent(util.customEvent('hashchange'));
});
}
// Initialize other pages on the site (specifically the documentation page).
// Any element with a `data-expr` attribute will contain a rendering of the
// provided regular expression.
_.each(document.querySelectorAll('[data-expr]'), element => {
new Parser(element, { keepContent: true })
.parse(element.getAttribute('data-expr'))
.then(parser => {
parser.render();
})
.catch(util.exposeError);
});
}());

View File

@ -1,112 +0,0 @@
// Entry point for the JavaScript-flavor regular expression parsing and
// rendering. Actual parsing code is in
// [parser.js](./javascript/parser.html) and the grammar file. Rendering code
// is contained in the various subclasses of
// [Node](./javascript/node.html)
import Snap from 'snapsvg';
import _ from 'lodash';
import util from '../util.js';
import javascript from './javascript/parser.js';
import ParserState from './javascript/parser_state.js';
export default class Parser {
// - __container__ - DOM node that will contain the rendered expression
// - __options.keepContent__ - Boolean indicating if content of the container
// should be preserved after rendering. Defaults to false (don't keep
// contents)
constructor(container, options) {
this.options = options || {};
_.defaults(this.options, {
keepContent: false
});
this.container = container;
// The [ParserState](./javascript/parser_state.html) instance is used to
// communicate between the parser and a running render, and to update the
// progress bar for the running render.
this.state = new ParserState(this.container.querySelector('.progress div'));
}
// DOM node that will contain the rendered expression. Setting this will add
// the base markup necessary for rendering the expression, and set the
// `svg-container` class
set container(cont) {
this._container = cont;
this._container.innerHTML = [
document.querySelector('#svg-container-base').innerHTML,
this.options.keepContent ? this.container.innerHTML : ''
].join('');
this._addClass('svg-container');
}
get container() {
return this._container;
}
// Helper method to simplify adding classes to the container.
_addClass(className) {
this.container.className = _(this.container.className.split(' '))
.union([className])
.join(' ');
}
// Helper method to simplify removing classes from the container.
_removeClass(className) {
this.container.className = _(this.container.className.split(' '))
.without(className)
.join(' ');
}
// Parse a regular expression into a tree of
// [Nodes](./javascript/node.html) that can then be used to render an SVG.
// - __expression__ - Regular expression to parse.
parse(expression) {
this._addClass('loading');
// Allow the browser to repaint before parsing so that the loading bar is
// displayed before the (possibly lengthy) parsing begins.
return util.tick().then(() => {
javascript.Parser.SyntaxNode.state = this.state;
this.parsed = javascript.parse(expression.replace(/\n/g, '\\n'));
return this;
});
}
// Render the parsed expression to an SVG.
render() {
let svg = Snap(this.container.querySelector('svg'));
return this.parsed.render(svg.group())
// Once rendering is complete, the rendered expression is positioned and
// the SVG resized to create some padding around the image contents.
.then(result => {
let box = result.getBBox();
result.transform(Snap.matrix()
.translate(10 - box.x, 10 - box.y));
svg.attr({
width: box.width + 20,
height: box.height + 20
});
})
// Stop and remove loading indicator after render is totally complete.
.then(() => {
this._removeClass('loading');
this.container.removeChild(this.container.querySelector('.progress'));
});
}
// Cancels any currently in-progress render.
cancel() {
this.state.cancelRender = true;
}
// Returns any warnings that may have been set during the rendering process.
get warnings() {
return this.state.warnings;
}
}

View File

@ -1,13 +0,0 @@
export default {
_render() {
return this.renderLabel(this.label).then(label => label.addClass('anchor'));
},
setup() {
if (this.textValue === '^') {
this.label = 'Start of line';
} else {
this.label = 'End of line';
}
}
};

View File

@ -1,12 +0,0 @@
// AnyCharacter nodes are for `*` regular expression syntax. They are rendered
// as just an "any character" label.
import _ from 'lodash';
export default {
type: 'any-character',
_render() {
return this.renderLabel('any character');
}
};

View File

@ -1,69 +0,0 @@
// Charset nodes are used for `[abc1-9]` regular expression syntax. It is
// rendered as a labeled box with each literal, escape, and range rendering
// handled by the nested node(s).
import util from '../../util.js';
import _ from 'lodash';
export default {
type: 'charset',
definedProperties: {
// Default anchor is overridden to move it down so that it connects at the
// middle of the box that wraps all of the charset parts, instead of the
// middle of the container, which would take the label into account.
_anchor: {
get: function() {
var matrix = this.transform().localMatrix;
return {
ay: matrix.y(0, this.partContainer.getBBox().cy)
};
}
}
},
// Renders the charset into the currently set container.
_render() {
this.partContainer = this.container.group();
// Renders each part of the charset into the part container.
return Promise.all(_.map(this.elements,
part => part.render(this.partContainer.group())
))
.then(() => {
// Space the parts of the charset vertically in the part container.
util.spaceVertically(this.elements, {
padding: 5
});
// Label the part container.
return this.renderLabeledBox(this.label, this.partContainer, {
padding: 5
});
});
},
setup() {
// The label for the charset will be:
// - "One of:" for charsets of the form: `[abc]`.
// - "None of:" for charsets of the form: `[^abc]`.
this.label = (this.properties.invert.textValue === '^') ? 'None of:' : 'One of:';
// Removes any duplicate parts from the charset. This is based on the type
// and text value of the part, so `[aa]` will have only one item, but
// `[a\x61]` will contain two since the first matches "a" and the second
// matches 0x61 (even though both are an "a").
this.elements = _.uniqBy(this.properties.parts.elements,
part => `${part.type}:${part.textValue}`);
// Include a warning for charsets that attempt to match `\c` followed by
// any character other than A-Z (case insensitive). Charsets like `[\c@]`
// behave differently in different browsers. Some match the character
// reference by the control charater escape, others match "\", "c", or "@",
// and some do not appear to match anything.
if (this.textValue.match(/\\c[^a-zA-Z]/)) {
this.state.warnings.push(`The character set "${this.textValue}" contains the \\c escape followed by a character other than A-Z. This can lead to different behavior depending on browser. The representation here is the most common interpretation.`);
}
}
}

View File

@ -1,12 +0,0 @@
// CharsetEscape nodes are for escape sequences inside of character sets. They
// differ from other [Escape](./escape.html) nodes in that `\b` matches a
// backspace character instead of a word boundary.
import _ from 'lodash';
import Escape from './escape.js';
export default _.extend({}, Escape, {
type: 'charset-escape',
b: ['backspace', 0x08, true]
});

View File

@ -1,42 +0,0 @@
// CharsetRange nodes are used for `[a-z]` regular expression syntax. The two
// literal or escape nodes are rendered with a hyphen between them.
import util from '../../util.js';
import _ from 'lodash';
export default {
type: 'charset-range',
// Renders the charset range into the currently set container
_render() {
let contents = [
this.first,
this.container.text(0, 0, '-'),
this.last
];
// Render the nodes of the range.
return Promise.all([
this.first.render(this.container.group()),
this.last.render(this.container.group())
])
.then(() => {
// Space the nodes and hyphen horizontally.
util.spaceHorizontally(contents, {
padding: 5
});
});
},
setup() {
// The two nodes for the range. In `[a-z]` these would be
// [Literal](./literal.html) nodes for "a" and "z".
this.first = this.properties.first;
this.last = this.properties.last;
// Report invalid expression when extents of the range are out of order.
if (this.first.ordinal > this.last.ordinal) {
throw `Range out of order in character class: ${this.textValue}`;
}
}
};

View File

@ -1,88 +0,0 @@
// Escape nodes are used for escape sequences. It is rendered as a label with
// the description of the escape and the numeric code it matches when
// appropriate.
import _ from 'lodash';
function hex(value) {
var str = value.toString(16).toUpperCase();
if (str.length < 2) {
str = '0' + str;
}
return `(0x${str})`;
}
export default {
type: 'escape',
// Renders the escape into the currently set container.
_render() {
return this.renderLabel(this.label)
.then(label => {
label.select('rect').attr({
rx: 3,
ry: 3
});
return label;
});
},
setup() {
let addHex;
// The escape code. For an escape such as `\b` it would be "b".
this.code = this.properties.esc.properties.code.textValue;
// The argument. For an escape such as `\xab` it would be "ab".
this.arg = this.properties.esc.properties.arg.textValue;
// Retrieves the label, ordinal value, an flag to control adding hex value
// from the escape code mappings
[this.label, this.ordinal, addHex] = _.result(this, this.code);
// When requested, add hex code to the label.
if (addHex) {
this.label = `${this.label} ${hex(this.ordinal)}`;
}
},
// Escape code mappings
b: ['word boundary', -1, false],
B: ['non-word boundary', -1, false],
d: ['digit', -1, false],
D: ['non-digit', -1, false],
f: ['form feed', 0x0c, true],
n: ['line feed', 0x0a, true],
r: ['carriage return', 0x0d, true],
s: ['white space', -1, false],
S: ['non-white space', -1, false],
t: ['tab', 0x09, true],
v: ['vertical tab', 0x0b, true],
w: ['word', -1, false],
W: ['non-word', -1, false],
1: ['Back reference (group = 1)', -1, false],
2: ['Back reference (group = 2)', -1, false],
3: ['Back reference (group = 3)', -1, false],
4: ['Back reference (group = 4)', -1, false],
5: ['Back reference (group = 5)', -1, false],
6: ['Back reference (group = 6)', -1, false],
7: ['Back reference (group = 7)', -1, false],
8: ['Back reference (group = 8)', -1, false],
9: ['Back reference (group = 9)', -1, false],
0: function() {
if (this.arg) {
return [`octal: ${this.arg}`, parseInt(this.arg, 8), true];
} else {
return ['null', 0, true];
}
},
c() {
return [`ctrl-${this.arg.toUpperCase()}`, this.arg.toUpperCase().charCodeAt(0) - 64, true];
},
x() {
return [`0x${this.arg.toUpperCase()}`, parseInt(this.arg, 16), false];
},
u() {
return [`U+${this.arg.toUpperCase()}`, parseInt(this.arg, 16), false];
}
};

View File

@ -1,56 +0,0 @@
grammar JavascriptRegexp
root <- ( ( "/" regexp "/" flags:[yigmu]* ) / regexp flags:""? ) <Root>
regexp <- match:match alternates:( "|" match )* <Regexp>
match <- (!repeat) parts:match_fragment* <Match>
anchor <- ( "^" / "$" ) <Anchor>
match_fragment <- content:( anchor / subexp / charset / terminal ) repeat:repeat? <MatchFragment>
repeat <- spec:( repeat_any / repeat_required / repeat_optional / repeat_spec ) greedy:"?"? <Repeat>
repeat_any <- "*" <RepeatAny>
repeat_required <- "+" <RepeatRequired>
repeat_optional <- "?" <RepeatOptional>
repeat_spec <- ( "{" min:[0-9]+ "," max:[0-9]+ "}"
/ "{" min:[0-9]+ ",}"
/ "{" exact:[0-9]+ "}" ) <RepeatSpec>
subexp <- "(" capture:( "?:" / "?=" / "?!" )? regexp ")" <Subexp>
charset <- "[" invert:"^"? parts:( charset_range / charset_terminal )* "]" <Charset>
charset_range <- first:charset_range_terminal "-" last:charset_range_terminal <CharsetRange>
charset_terminal <- charset_escape <CharsetEscape>
/ charset_literal <Literal>
charset_range_terminal <- charset_range_escape <CharsetEscape>
/ charset_literal <Literal>
charset_escape <- "\\" esc:(
code:[bdDfnrsStvwW] arg:""?
/ control_escape
/ octal_escape
/ hex_escape
/ unicode_escape
/ null_escape )
charset_range_escape <- "\\" esc:(
code:[bfnrtv] arg:""?
/ control_escape
/ octal_escape
/ hex_escape
/ unicode_escape
/ null_escape )
charset_literal <- ( ""? literal:[^\\\]] )
/ ( literal:"\\" &"c" )
/ ( "\\" literal:[^bdDfnrsStvwW] )
terminal <- "." <AnyCharacter>
/ escape <Escape>
/ literal <Literal>
escape <- "\\" esc:(
code:[bBdDfnrsStvwW1-9] arg:""?
/ control_escape
/ octal_escape
/ hex_escape
/ unicode_escape
/ null_escape )
literal <- ( ""? literal:[^|\\/.\[\(\)?+*$^] )
/ ( literal:"\\" &"c" )
/ ( "\\" literal:. )
control_escape <- code:"c" arg:[a-zA-Z]
octal_escape <- code:"0" arg:[0-7]+
hex_escape <- code:"x" arg:( [0-9a-fA-F] [0-9a-fA-F] )
unicode_escape <- code:"u" arg:( [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] )
null_escape <- code:"0" arg:""?

View File

@ -1,43 +0,0 @@
// Literal nodes are for plain strings in the regular expression. They are
// rendered as labels with the value of the literal quoted.
import _ from 'lodash';
export default {
type: 'literal',
// Renders the literal into the currently set container.
_render() {
return this.renderLabel(['\u201c', this.literal, '\u201d'])
.then(label => {
let spans = label.selectAll('tspan');
// The quote marks get some styling to lighten their color so they are
// distinct from the actual literal value.
spans[0].addClass('quote');
spans[2].addClass('quote');
label.select('rect').attr({
rx: 3,
ry: 3
});
return label;
});
},
// Merges this literal with another. Literals come back as single characters
// during parsing, and must be post-processed into multi-character literals
// for rendering. This processing is done in [Match](./match.html).
merge(other) {
this.literal += other.literal;
},
setup() {
// Value of the literal.
this.literal = this.properties.literal.textValue;
// Ordinal value of the literal for use in
// [CharsetRange](./charset_range.html).
this.ordinal = this.literal.charCodeAt(0);
}
};

View File

@ -1,102 +0,0 @@
// Match nodes are used for the parts of a regular expression between `|`
// symbols. They consist of a series of [MatchFragment](./match_fragment.html)
// nodes. Optional `^` and `$` symbols are also allowed at the beginning and
// end of the Match.
import util from '../../util.js';
import _ from 'lodash';
export default {
type: 'match',
definedProperties: {
// Default anchor is overridden to attach the left point of the anchor to
// the first element, and the right point to the last element.
_anchor: {
get: function() {
var start = util.normalizeBBox(this.start.getBBox()),
end = util.normalizeBBox(this.end.getBBox()),
matrix = this.transform().localMatrix;
return {
ax: matrix.x(start.ax, start.ay),
ax2: matrix.x(end.ax2, end.ay),
ay: matrix.y(start.ax, start.ay)
};
}
}
},
// Renders the match into the currently set container.
_render() {
// Render each of the match fragments.
let partPromises = _.map(this.parts, part => part.render(this.container.group())),
items = _(partPromises).compact().value();
// Handle the situation where a regular expression of `()` is rendered.
// This leads to a Match node with no fragments. Something must be rendered
// so that the anchor can be calculated based on it.
//
// Furthermore, the content rendered must have height and width or else the
// anchor calculations fail.
if (items.length === 0) {
items = [this.container.group().path('M0,0h10')];
}
return Promise.all(items)
.then(items => {
// Find SVG elements to be used when calculating the anchor.
this.start = _.first(items);
this.end = _.last(items);
util.spaceHorizontally(items, {
padding: 10
});
// Add lines between each item.
this.container.prepend(
this.container.path(this.connectorPaths(items).join('')));
});
},
// Returns an array of SVG path strings between each item.
// - __items__ - Array of SVG elements or nodes.
connectorPaths(items) {
let prev, next;
prev = util.normalizeBBox(_.first(items).getBBox());
return _.map(items.slice(1), item => {
try {
next = util.normalizeBBox(item.getBBox());
return `M${prev.ax2},${prev.ay}H${next.ax}`;
}
finally {
prev = next;
}
});
},
setup() {
// Merged list of MatchFragments to be rendered.
this.parts = _.reduce(this.properties.parts.elements, function(result, node) {
var last = _.last(result);
if (last && node.canMerge && last.canMerge) {
// Merged the content of `node` into `last` when possible. This also
// discards `node` in the process since `result` has not been changed.
last.content.merge(node.content);
} else {
// `node` cannot be merged with the previous node, so it is added to
// the list of parts.
result.push(node);
}
return result;
}, []);
// When there is only one part, then proxy to the part.
if (this.parts.length === 1) {
this.proxy = this.parts[0];
}
}
};

View File

@ -1,93 +0,0 @@
// MatchFragment nodes are part of a [Match](./match.html) followed by an
// optional [Repeat](./repeat.html) node. If no repeat is applied, then
// rendering is proxied to the content node.
import _ from 'lodash';
export default {
type: 'match-fragment',
definedProperties: {
// Default anchor is overridden to apply an transforms from the fragment
// to its content's anchor. Essentially, the fragment inherits the anchor
// of its content.
_anchor: {
get: function() {
var anchor = this.content.getBBox(),
matrix = this.transform().localMatrix;
return {
ax: matrix.x(anchor.ax, anchor.ay),
ax2: matrix.x(anchor.ax2, anchor.ay),
ay: matrix.y(anchor.ax, anchor.ay)
};
}
}
},
// Renders the fragment into the currently set container.
_render() {
return this.content.render(this.container.group())
.then(() => {
let box, paths;
// Contents must be transformed based on the repeat that is applied.
this.content.transform(this.repeat.contentPosition);
box = this.content.getBBox();
// Add skip or repeat paths to the container.
paths = _.flatten([
this.repeat.skipPath(box),
this.repeat.loopPath(box)
]);
this.container.prepend(
this.container.path(paths.join('')));
this.loopLabel();
});
},
// Renders label for the loop path indicating how many times the content may
// be matched.
loopLabel() {
let labelStr = this.repeat.label,
tooltipStr = this.repeat.tooltip;
if (labelStr) {
let label = this.container.text(0, 0, [labelStr])
.addClass('repeat-label'),
labelBox = label.getBBox(),
box = this.getBBox();
if (tooltipStr) {
let tooltip = this.container.el('title')
.append(this.container.text(0, 0, tooltipStr));
label.append(tooltip);
}
label.transform(Snap.matrix().translate(
box.x2 - labelBox.width - (this.repeat.hasSkip ? 5 : 0),
box.y2 + labelBox.height));
}
},
setup() {
// Then content of the fragment.
this.content = this.properties.content;
// The repetition rule for the fragment.
this.repeat = this.properties.repeat;
if (!this.repeat.hasLoop && !this.repeat.hasSkip) {
// For fragments without a skip or loop, rendering is proxied to the
// content. Also set flag indicating that contents can be merged if the
// content is a literal node.
this.canMerge = (this.content.type === 'literal');
this.proxy = this.content;
} else {
// Fragments that have skip or loop lines cannot be merged with others.
this.canMerge = false;
}
}
};

View File

@ -1,186 +0,0 @@
// Base class for all nodes in the parse tree. An instance of this class is
// created for each parsed node, and then extended with one of the node-type
// modules.
import util from '../../util.js';
import _ from 'lodash';
export default class Node {
// Arguments passed in are defined by the canopy tool.
constructor(textValue, offset, elements, properties) {
this.textValue = textValue;
this.offset = offset;
this.elements = elements || [];
this.properties = properties;
// This is the current parser state (an instance
// [ParserState](./parser_state.html).)
this.state = Node.state;
}
// Node-type module to extend the Node instance with. Setting of this is
// done by canopy during parsing and is setup in [parser.js](./parser.html).
set module(mod) {
_.extend(this, mod);
if (this.setup) {
this.setup();
}
_.forOwn(this.definedProperties || {}, (methods, name) => {
Object.defineProperty(this, name, methods);
});
delete this.definedProperties;
}
// The SVG element to render this node into. A node-type class is
// automatically added to the container. The class to set is defined on the
// module set during parsing.
set container(container) {
this._container = container;
this._container.addClass(this.type);
}
get container() {
return this._container;
}
// The anchor defined the points on the left and right of the rendered node
// that the centerline of the rendered expression connects to. For most
// nodes, this element will be defined by the normalizeBBox method in
// [Util](../../util.html).
get anchor() {
if (this.proxy) {
return this.proxy.anchor;
} else {
return this._anchor || {};
}
}
// Returns the bounding box of the container with the anchor included.
getBBox() {
return _.extend(util.normalizeBBox(this.container.getBBox()), this.anchor);
}
// Transforms the container.
//
// - __matrix__ - A matrix transform to be applied. Created using Snap.svg.
transform(matrix) {
return this.container.transform(matrix);
}
// Returns a Promise that will be resolved with the provided value. If the
// render is cancelled before the Promise is resolved, then an exception will
// be thrown to halt any rendering.
//
// - __value__ - Value to resolve the returned promise with.
deferredStep(value) {
return util.tick().then(() => {
if (this.state.cancelRender) {
throw 'Render cancelled';
}
return value;
});
}
// Render this node.
//
// - __container__ - Optional element to render this node into. A container
// must be specified, but if it has already been set, then it does not
// need to be provided to render.
render(container) {
if (container) {
this.container = container;
}
if (this.proxy) {
// For nodes that proxy to a child node, just render the child.
return this.proxy.render(this.container);
} else {
// Non-proxied nodes call their _render method (defined by the node-type
// module).
this.state.renderCounter++;
return this._render()
.then(() => {
this.state.renderCounter--;
return this;
});
}
}
// Renders a label centered within a rectangle which can be styled. Returns
// a Promise which will be resolved with the SVG group the rect and text are
// rendered in.
//
// - __text__ - String or array of strings to render as a label.
renderLabel(text) {
let group = this.container.group()
.addClass('label'),
rect = group.rect(),
label = group.text(0, 0, _.flatten([text]));
return this.deferredStep()
.then(() => {
let box = label.getBBox(),
margin = 5;
label.transform(Snap.matrix()
.translate(margin, box.height / 2 + 2 * margin));
rect.attr({
width: box.width + 2 * margin,
height: box.height + 2 * margin
});
return group;
});
}
// Renders a labeled box around another SVG element. Returns a Promise.
//
// - __text__ - String or array of strings to label the box with.
// - __content__ - SVG element to wrap in the box.
// - __options.padding__ - Pixels of padding to place between the content and
// the box.
renderLabeledBox(text, content, options) {
let label = this.container.text(0, 0, _.flatten([text]))
.addClass(`${this.type}-label`),
box = this.container.rect()
.addClass(`${this.type}-box`)
.attr({
rx: 3,
ry: 3
});
options = _.defaults(options || {}, {
padding: 0
});
this.container.prepend(label);
this.container.prepend(box);
return this.deferredStep()
.then(() => {
let labelBox = label.getBBox(),
contentBox = content.getBBox(),
boxWidth = Math.max(contentBox.width + options.padding * 2, labelBox.width),
boxHeight = contentBox.height + options.padding * 2;
label.transform(Snap.matrix()
.translate(0, labelBox.height));
box
.transform(Snap.matrix()
.translate(0, labelBox.height))
.attr({
width: boxWidth,
height: boxHeight
});
content.transform(Snap.matrix()
.translate(boxWidth / 2 - contentBox.cx, labelBox.height + options.padding));
});
}
};

View File

@ -1,53 +0,0 @@
// Sets up the parser generated by canopy to use the
// [Node](./javascript/node.html) subclasses in the generated tree. This is all
// a bit of a hack that is dependent on how canopy creates nodes in its parse
// tree.
import parser from './grammar.peg';
import Node from './node.js';
import Root from './root.js';
import Regexp from './regexp.js';
import Match from './match.js';
import MatchFragment from './match_fragment.js';
import Anchor from './anchor.js';
import Subexp from './subexp.js';
import Charset from './charset.js';
import CharsetEscape from './charset_escape.js';
import CharsetRange from './charset_range.js';
import Literal from './literal.js';
import Escape from './escape.js';
import AnyCharacter from './any_character.js';
import Repeat from './repeat.js';
import RepeatAny from './repeat_any.js';
import RepeatOptional from './repeat_optional.js';
import RepeatRequired from './repeat_required.js';
import RepeatSpec from './repeat_spec.js';
// Canopy creates an instance of SyntaxNode for each element in the tree, then
// adds any necessary fields to that instance. In this case, we're replacing
// the default class with the Node class.
parser.Parser.SyntaxNode = Node;
// Once the SyntaxNode instance is created, the specific node type object is
// overlayed onto it. This causes the module attribute on the Node to be set,
// which updates the Node instance into the more specific "subclass" that is
// used for rendering.
parser.Parser.Root = { module: Root };
parser.Parser.Regexp = { module: Regexp };
parser.Parser.Match = { module: Match };
parser.Parser.MatchFragment = { module: MatchFragment };
parser.Parser.Anchor = { module: Anchor };
parser.Parser.Subexp = { module: Subexp };
parser.Parser.Charset = { module: Charset };
parser.Parser.CharsetEscape = { module: CharsetEscape };
parser.Parser.CharsetRange = { module: CharsetRange };
parser.Parser.Literal = { module: Literal };
parser.Parser.Escape = { module: Escape };
parser.Parser.AnyCharacter = { module: AnyCharacter };
parser.Parser.Repeat = { module: Repeat };
parser.Parser.RepeatAny = { module: RepeatAny };
parser.Parser.RepeatOptional = { module: RepeatOptional };
parser.Parser.RepeatRequired = { module: RepeatRequired };
parser.Parser.RepeatSpec = { module: RepeatSpec };
export default parser;

View File

@ -1,36 +0,0 @@
// State tracking for an in-progress parse and render.
export default class ParserState {
// - __progress__ - DOM node to update to indicate completion progress.
constructor(progress) {
// Tracks the number of capture groups in the expression.
this.groupCounter = 1;
// Cancels the in-progress render when set to true.
this.cancelRender = false;
// Warnings that have been generated while rendering.
this.warnings = [];
// Used to display the progress indicator
this._renderCounter = 0;
this._maxCounter = 0;
this._progress = progress;
}
// Counts the number of in-progress rendering steps. As the counter goes up,
// a maximum value is also tracked. The maximum value and current render
// counter are used to calculate the completion process.
get renderCounter() {
return this._renderCounter;
}
set renderCounter(value) {
if (value > this.renderCounter) {
this._maxCounter = value;
}
this._renderCounter = value;
if (this._maxCounter && !this.cancelRender) {
this._progress.style.width = ((1 - this.renderCounter / this._maxCounter) * 100).toFixed(2) + '%';
}
}
}

View File

@ -1,129 +0,0 @@
// Regexp nodes are the entire regular expression. They consist of a collection
// of [Match](./match.html) nodes separated by `|`.
import util from '../../util.js';
import _ from 'lodash';
export default {
type: 'regexp',
// Renders the regexp into the currently set container.
_render() {
let matchContainer = this.container.group()
.addClass('regexp-matches')
.transform(Snap.matrix()
.translate(20, 0));
// Renders each match into the match container.
return Promise.all(_.map(this.matches,
match => match.render(matchContainer.group())
))
.then(() => {
let containerBox,
paths;
// Space matches vertically in the match container.
util.spaceVertically(this.matches, {
padding: 5
});
containerBox = this.getBBox();
// Creates the curves from the side lines for each match.
paths = _.map(this.matches, match => this.makeCurve(containerBox, match));
// Add side lines to the list of paths.
paths.push(this.makeSide(containerBox, _.first(this.matches)));
paths.push(this.makeSide(containerBox, _.last(this.matches)));
// Render connector paths.
this.container.prepend(
this.container.path(_(paths).flatten().compact().values().join('')));
containerBox = matchContainer.getBBox();
// Create connections from side lines to each match and render into
// the match container.
paths = _.map(this.matches, match => this.makeConnector(containerBox, match));
matchContainer.prepend(
matchContainer.path(paths.join('')));
});
},
// Returns an array of SVG path strings to draw the vertical lines on the
// left and right of the node.
//
// - __containerBox__ - Bounding box of the container.
// - __match__ - Match node that the line will be drawn to.
makeSide(containerBox, match) {
let box = match.getBBox(),
distance = Math.abs(box.ay - containerBox.cy);
// Only need to draw side lines if the match is more than 15 pixels from
// the vertical center of the rendered regexp. Less that 15 pixels will be
// handled by the curve directly.
if (distance >= 15) {
let shift = (box.ay > containerBox.cy) ? 10 : -10,
edge = box.ay - shift;
return [
`M0,${containerBox.cy}q10,0 10,${shift}V${edge}`,
`M${containerBox.width + 40},${containerBox.cy}q-10,0 -10,${shift}V${edge}`
];
}
},
// Returns an array of SVG path strings to draw the curves from the
// sidelines up to the anchor of the match node.
//
// - __containerBox__ - Bounding box of the container.
// - __match__ - Match node that the line will be drawn to.
makeCurve(containerBox, match) {
let box = match.getBBox(),
distance = Math.abs(box.ay - containerBox.cy);
if (distance >= 15) {
// For match nodes more than 15 pixels from the center of the regexp, a
// quarter-circle curve is used to connect to the sideline.
let curve = (box.ay > containerBox.cy) ? 10 : -10;
return [
`M10,${box.ay - curve}q0,${curve} 10,${curve}`,
`M${containerBox.width + 30},${box.ay - curve}q0,${curve} -10,${curve}`
];
} else {
// For match nodes less than 15 pixels from the center of the regexp, a
// slightly curved line is used to connect to the sideline.
let anchor = box.ay - containerBox.cy;
return [
`M0,${containerBox.cy}c10,0 10,${anchor} 20,${anchor}`,
`M${containerBox.width + 40},${containerBox.cy}c-10,0 -10,${anchor} -20,${anchor}`
];
}
},
// Returns an array of SVG path strings to draw the connection from the
// curve to match node.
//
// - __containerBox__ - Bounding box of the container.
// - __match__ - Match node that the line will be drawn to.
makeConnector(containerBox, match) {
let box = match.getBBox();
return `M0,${box.ay}h${box.ax}M${box.ax2},${box.ay}H${containerBox.width}`;
},
setup() {
if (this.properties.alternates.elements.length === 0) {
// When there is only one match node to render, proxy to it.
this.proxy = this.properties.match;
} else {
// Merge all the match nodes into one array.
this.matches = [this.properties.match].concat(
_.map(this.properties.alternates.elements,
element => element.properties.match)
);
}
}
};

View File

@ -1,122 +0,0 @@
// Repeat nodes are for the various repetition syntaxes (`a*`, `a+`, `a?`, and
// `a{1,3}`). It is not rendered directly, but contains data used for the
// rendering of [MatchFragment](./match_fragment.html) nodes.
function formatTimes(times) {
if (times === 1) {
return 'once';
} else {
return `${times} times`;
}
}
export default {
definedProperties: {
// Translation to apply to content to be repeated to account for the loop
// and skip lines.
contentPosition: {
get: function() {
var matrix = Snap.matrix();
if (this.hasSkip) {
return matrix.translate(15, 10);
} else if (this.hasLoop) {
return matrix.translate(10, 0);
} else {
return matrix.translate(0, 0);
}
}
},
// Label to place of loop path to indicate the number of times that path
// may be followed.
label: {
get: function() {
if (this.minimum === this.maximum) {
if (this.minimum === 0) {
return undefined;
}
return formatTimes(this.minimum - 1);
} else if (this.minimum <= 1 && this.maximum >= 2) {
return `at most ${formatTimes(this.maximum - 1)}`;
} else if (this.minimum >= 2) {
if (this.maximum === -1) {
return `${this.minimum - 1}+ times`;
} else {
return `${this.minimum - 1}\u2026${formatTimes(this.maximum - 1)}`;
}
}
}
},
// Tooltip to place of loop path label to provide further details.
tooltip: {
get: function() {
let repeatCount;
if (this.minimum === this.maximum) {
if (this.minimum === 0) {
repeatCount = undefined;
} else {
repeatCount = formatTimes(this.minimum);
}
} else if (this.minimum <= 1 && this.maximum >= 2) {
repeatCount = `at most ${formatTimes(this.maximum)}`;
} else if (this.minimum >= 2) {
if (this.maximum === -1) {
repeatCount = `${this.minimum}+ times`;
} else {
repeatCount = `${this.minimum}\u2026${formatTimes(this.maximum)}`;
}
}
return repeatCount ? `repeats ${repeatCount} in total` : repeatCount;
}
}
},
// Returns the path spec to render the line that skips over the content for
// fragments that are optionally matched.
skipPath(box) {
let paths = [];
if (this.hasSkip) {
let vert = Math.max(0, box.ay - box.y - 10),
horiz = box.width - 10;
paths.push(`M0,${box.ay}q10,0 10,-10v${-vert}q0,-10 10,-10h${horiz}q10,0 10,10v${vert}q0,10 10,10`);
// When the repeat is not greedy, the skip path gets a preference arrow.
if (!this.greedy) {
paths.push(`M10,${box.ay - 15}l5,5m-5,-5l-5,5`);
}
}
return paths;
},
// Returns the path spec to render the line that repeats the content for
// fragments that are matched more than once.
loopPath(box) {
let paths = [];
if (this.hasLoop) {
let vert = box.y2 - box.ay - 10;
paths.push(`M${box.x},${box.ay}q-10,0 -10,10v${vert}q0,10 10,10h${box.width}q10,0 10,-10v${-vert}q0,-10 -10,-10`);
// When the repeat is greedy, the loop path gets the preference arrow.
if (this.greedy) {
paths.push(`M${box.x2 + 10},${box.ay + 15}l5,-5m-5,5l-5,-5`);
}
}
return paths;
},
setup() {
this.minimum = this.properties.spec.minimum;
this.maximum = this.properties.spec.maximum;
this.greedy = (this.properties.greedy.textValue === '');
this.hasSkip = (this.minimum === 0);
this.hasLoop = (this.maximum === -1 || this.maximum > 1);
}
}

View File

@ -1,8 +0,0 @@
// RepeatAny nodes are used for `a*` regular expression syntax. It is not
// rendered directly; it just indicates that the [Repeat](./repeat.html) node
// loops zero or more times.
export default {
minimum: 0,
maximum: -1
};

View File

@ -1,8 +0,0 @@
// RepeatOptional nodes are used for `a?` regular expression syntax. It is not
// rendered directly; it just indicates that the [Repeat](./repeat.html) node
// loops zero or one times.
export default {
minimum: 0,
maximum: 1
};

View File

@ -1,8 +0,0 @@
// RepeatRequired nodes are used for `a+` regular expression syntax. It is not
// rendered directly; it just indicates that the [Repeat](./repeat.html) node
// loops one or more times.
export default {
minimum: 1,
maximum: -1
};

View File

@ -1,28 +0,0 @@
// RepeatSpec nodes are used for `a{m,n}` regular expression syntax. It is not
// rendered directly; it just indicates how many times the
// [Repeat](./repeat.html) node loops.
export default {
setup() {
if (this.properties.min) {
this.minimum = Number(this.properties.min.textValue);
} else if (this.properties.exact) {
this.minimum = Number(this.properties.exact.textValue);
} else {
this.minimum = 0;
}
if (this.properties.max) {
this.maximum = Number(this.properties.max.textValue);
} else if (this.properties.exact) {
this.maximum = Number(this.properties.exact.textValue);
} else {
this.maximum = -1;
}
// Report invalid repeat when the minimum is larger than the maximum.
if (this.minimum > this.maximum && this.maximum !== -1) {
throw `Numbers out of order: ${this.textValue}`;
}
}
};

View File

@ -1,56 +0,0 @@
// Root nodes contain the top-level [Regexp](./regexp.html) node. Any flags
// and a few decorative elements are rendered by the root node.
import _ from 'lodash';
export default {
type: 'root',
flagLabels: {
i: 'Ignore Case',
g: 'Global',
m: 'Multiline',
y: 'Sticky',
u: 'Unicode'
},
// Renders the root into the currently set container.
_render() {
let flagText;
// Render a label for any flags that have been set of the expression.
if (this.flags.length > 0) {
flagText = this.container.text(0, 0, `Flags: ${this.flags.join(', ')}`);
}
// Render the content of the regular expression.
return this.regexp.render(this.container.group())
.then(() => {
// Move rendered regexp to account for flag label and to allow for
// decorative elements.
if (flagText) {
this.regexp.transform(Snap.matrix()
.translate(10, flagText.getBBox().height));
} else {
this.regexp.transform(Snap.matrix()
.translate(10, 0));
}
let box = this.regexp.getBBox();
// Render decorative elements.
this.container.path(`M${box.ax},${box.ay}H0M${box.ax2},${box.ay}H${box.x2 + 10}`);
this.container.circle(0, box.ay, 5);
this.container.circle(box.x2 + 10, box.ay, 5);
});
},
setup() {
// Convert list of flags into text describing each flag.
this.flags = _(this.properties.flags.textValue)
.uniq().sort()
.map(flag => this.flagLabels[flag]).value();
this.regexp = this.properties.regexp
}
};

View File

@ -1,66 +0,0 @@
// Subexp nodes are for expressions inside of parenthesis. It is rendered as a
// labeled box around the contained expression if a label is required.
import _ from 'lodash';
export default {
type: 'subexp',
definedProperties: {
// Default anchor is overridden to move it down to account for the group
// label and outline box.
_anchor: {
get: function() {
var anchor = this.regexp.getBBox(),
matrix = this.transform().localMatrix;
return {
ax: matrix.x(anchor.ax, anchor.ay),
ax2: matrix.x(anchor.ax2, anchor.ay),
ay: matrix.y(anchor.ax, anchor.ay)
};
}
}
},
labelMap: {
'?:': '',
'?=': 'positive lookahead',
'?!': 'negative lookahead'
},
// Renders the subexp into the currently set container.
_render() {
// **NOTE:** `this.label()` **MUST** be called here, in _render, and before
// any child nodes are rendered. This is to keep the group numbers in the
// correct order.
let label = this.label();
// Render the contained regexp.
return this.regexp.render(this.container.group())
// Create the labeled box around the regexp.
.then(() => this.renderLabeledBox(label, this.regexp, {
padding: 10
}));
},
// Returns the label for the subexpression.
label() {
if (_.has(this.labelMap, this.properties.capture.textValue)) {
return this.labelMap[this.properties.capture.textValue];
} else {
return `group #${this.state.groupCounter++}`;
}
},
setup() {
// **NOTE:** **DO NOT** call `this.label()` in setup. It will lead to
// groups being numbered in reverse order.
this.regexp = this.properties.regexp;
// If there is no need for a label, then proxy to the nested regexp.
if (this.properties.capture.textValue == '?:') {
this.proxy = this.regexp;
}
}
};

View File

@ -1,290 +0,0 @@
// The Regexper class manages the top-level behavior for the entire
// application. This includes event handlers for all user interactions.
import util from './util.js';
import Parser from './parser/javascript.js';
import _ from 'lodash';
export default class Regexper {
constructor(root) {
this.root = root;
this.buggyHash = false;
this.form = root.querySelector('#regexp-form');
this.field = root.querySelector('#regexp-input');
this.error = root.querySelector('#error');
this.warnings = root.querySelector('#warnings');
this.links = this.form.querySelector('ul');
this.permalink = this.links.querySelector('a[data-action="permalink"]');
this.downloadSvg = this.links.querySelector('a[data-action="download-svg"]');
this.downloadPng = this.links.querySelector('a[data-action="download-png"]');
this.svgContainer = root.querySelector('#regexp-render');
}
// Event handler for key presses in the regular expression form field.
keypressListener(event) {
// Pressing Shift-Enter displays the expression.
if (event.shiftKey && event.keyCode === 13) {
event.returnValue = false;
if (event.preventDefault) {
event.preventDefault();
}
this.form.dispatchEvent(util.customEvent('submit'));
}
}
// Event handler for key presses while focused anywhere in the application.
documentKeypressListener(event) {
// Pressing escape will cancel a currently running render.
if (event.keyCode === 27 && this.running) {
this.running.cancel();
}
}
// Event handler for submission of the regular expression. Changes the URL
// hash which leads to the expression being rendered.
submitListener(event) {
event.returnValue = false;
if (event.preventDefault) {
event.preventDefault();
}
try {
this._setHash(this.field.value);
}
catch(e) {
// Failed to set the URL hash (probably because the expression is too
// long). Turn off display of the permalink and just show the expression.
this.permalinkEnabled = false;
this.showExpression(this.field.value);
}
}
// Event handler for URL hash changes. Starts rendering of the expression.
hashchangeListener() {
let expr = this._getHash();
if (expr instanceof Error) {
this.state = 'has-error';
this.error.innerHTML = 'Malformed expression in URL';
util.track('send', 'event', 'visualization', 'malformed URL');
} else {
this.permalinkEnabled = true;
this.showExpression(expr);
}
}
// Binds all event listeners.
bindListeners() {
this.field.addEventListener('keypress', this.keypressListener.bind(this));
this.form.addEventListener('submit', this.submitListener.bind(this));
this.root.addEventListener('keyup', this.documentKeypressListener.bind(this));
window.addEventListener('hashchange', this.hashchangeListener.bind(this));
}
// Detect if https://bugzilla.mozilla.org/show_bug.cgi?id=483304 is in effect
detectBuggyHash() {
if (typeof window.URL === 'function') {
try {
let url = new URL('http://regexper.com/#%25');
this.buggyHash = (url.hash === '#%');
}
catch(e) {
this.buggyHash = false;
}
}
}
// Set the URL hash. This method exists to facilitate automated testing
// (since changing the URL can throw off most JavaScript testing tools).
_setHash(hash) {
location.hash = encodeURIComponent(hash)
.replace(/\(/g, '%28')
.replace(/\)/g, '%29');
}
// Retrieve the current URL hash. This method is also mostly for supporting
// automated testing, but also does some basic error handling for malformed
// URLs.
_getHash() {
try {
let hash = location.hash.slice(1)
return this.buggyHash ? hash : decodeURIComponent(hash);
}
catch(e) {
return e;
}
}
// Currently state of the application. Useful values are:
// - `''` - State of the application when the page initially loads
// - `'is-loading'` - Displays the loading indicator
// - `'has-error'` - Displays the error message
// - `'has-results'` - Displays rendered results
set state(state) {
this.root.className = state;
}
get state() {
return this.root.className;
}
// Start the rendering of a regular expression.
//
// - __expression__ - Regular expression to display.
showExpression(expression) {
this.field.value = expression;
this.state = '';
if (expression !== '') {
this.renderRegexp(expression).catch(util.exposeError);
}
}
// Creates a blob URL for linking to a rendered regular expression image.
//
// - __content__ - SVG image markup.
buildBlobURL(content) {
// Blob object has to stick around for IE, so the instance is stored on the
// `window` object.
window.blob = new Blob([content], { type: 'image/svg+xml' });
return URL.createObjectURL(window.blob);
}
// Update the URLs of the 'download' and 'permalink' links.
updateLinks() {
let classes = _.without(this.links.className.split(' '), ['hide-download-svg', 'hide-permalink']);
let svg = this.svgContainer.querySelector('.svg');
// Create the SVG 'download' image URL.
try {
this.downloadSvg.parentNode.style.display = null;
this.downloadSvg.href = this.buildBlobURL(svg.innerHTML);
}
catch(e) {
// Blobs or URLs created from a blob URL don't work in the current
// browser. Giving up on the download link.
classes.push('hide-download-svg');
}
//Create the PNG 'download' image URL.
try {
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
let loader = new Image;
loader.width = canvas.width = Number(svg.querySelector('svg').getAttribute('width'));
loader.height = canvas.height = Number(svg.querySelector('svg').getAttribute('height'));
loader.onload = () => {
try {
context.drawImage(loader, 0, 0, loader.width, loader.height);
canvas.toBlob(blob => {
window.pngBlob = blob;
this.downloadPng.href = URL.createObjectURL(window.pngBlob);
this.links.className = this.links.className.replace(/\bhide-download-png\b/, '');
}, 'image/png');
}
catch(e) {}
};
loader.src = 'data:image/svg+xml,' + encodeURIComponent(svg.innerHTML);
classes.push('hide-download-png');
}
catch(e) {}
// Create the 'permalink' URL.
if (this.permalinkEnabled) {
this.permalink.parentNode.style.display = null;
this.permalink.href = location.toString();
} else {
classes.push('hide-permalink');
}
this.links.className = classes.join(' ');
}
// Display any warnings that were generated while rendering a regular expression.
//
// - __warnings__ - Array of warning messages to display.
displayWarnings(warnings) {
this.warnings.innerHTML = _.map(warnings, warning => (
`<li class="inline-icon">${util.icon("#warning")}${warning}</li>`
)).join('');
}
// Render regular expression
//
// - __expression__ - Regular expression to render
renderRegexp(expression) {
let parseError = false,
startTime, endTime;
// When a render is already in progress, cancel it and try rendering again
// after a short delay (canceling a render is not instantaneous).
if (this.running) {
this.running.cancel();
return util.wait(10).then(() => this.renderRegexp(expression));
}
this.state = 'is-loading';
util.track('send', 'event', 'visualization', 'start');
startTime = new Date().getTime();
this.running = new Parser(this.svgContainer);
return this.running
// Parse the expression.
.parse(expression)
// Display any error messages from the parser and abort the render.
.catch(message => {
this.state = 'has-error';
this.error.innerHTML = '';
this.error.appendChild(document.createTextNode(message));
parseError = true;
throw message;
})
// When parsing is successful, render the parsed expression.
.then(parser => parser.render())
// Once rendering is complete:
// - Update links
// - Display any warnings
// - Track the completion of the render and how long it took
.then(() => {
this.state = 'has-results';
this.updateLinks();
this.displayWarnings(this.running.warnings);
util.track('send', 'event', 'visualization', 'complete');
endTime = new Date().getTime();
util.track('send', 'timing', 'visualization', 'total time', endTime - startTime);
})
// Handle any errors that happened during the rendering pipeline.
// Swallows parse errors and render cancellations. Any other exceptions
// are allowed to continue on to be tracked by the global error handler.
.catch(message => {
if (message === 'Render cancelled') {
util.track('send', 'event', 'visualization', 'cancelled');
this.state = '';
} else if (parseError) {
util.track('send', 'event', 'visualization', 'parse error');
} else {
throw message;
}
})
// Finally, mark rendering as complete (and pass along any exceptions
// that were thrown).
.then(
() => {
this.running = false;
},
message => {
this.running = false;
throw message;
}
);
}
}

View File

@ -1,149 +0,0 @@
// Utility functions used elsewhere in the codebase. Most JavaScript files on
// the site use some functions defined in this file.
import _ from 'lodash';
// Generate an `Event` object for triggering a custom event.
//
// - __name__ - Name of the custom event. This should be a String.
// - __detail__ - Event details. The event details are provided to the event
// handler.
function customEvent(name, detail) {
var evt = document.createEvent('Event');
evt.initEvent(name, true, true);
evt.detail = detail;
return evt;
}
// Add extra fields to a bounding box returned by `getBBox`. Specifically adds
// details about the box's axis points (used when positioning elements for
// display).
//
// - __box__ - Bounding box object to update. Attributes `ax`, `ax2`, and `ay`
// will be added if they are not already defined.
function normalizeBBox(box) {
return _.defaults(box, {
ax: box.x,
ax2: box.x2,
ay: box.cy
});
}
// Positions a collection of items with their axis points aligned along a
// horizontal line. This leads to the items being spaced horizontally and
// effectively centered vertically.
//
// - __items__ - Array of items to be positioned
// - __options.padding__ - Number of pixels to leave between items
function spaceHorizontally(items, options) {
var verticalCenter,
values;
options = _.defaults(options || {}, {
padding: 0
});
values = _.map(items, item => ({
box: normalizeBBox(item.getBBox()),
item
}));
// Calculate where the axis points should be positioned vertically.
verticalCenter = _.reduce(values,
(center, { box }) => Math.max(center, box.ay),
0);
// Position items with padding between them and aligned their axis points.
_.reduce(values, (offset, { item, box }) => {
item.transform(Snap.matrix()
.translate(offset, verticalCenter - box.ay));
return offset + options.padding + box.width;
}, 0);
}
// Positions a collection of items centered horizontally in a vertical stack.
//
// - __items__ - Array of items to be positioned
// - __options.padding__ - Number of pixels to leave between items
function spaceVertically(items, options) {
var horizontalCenter,
values;
options = _.defaults(options || {}, {
padding: 0
});
values = _.map(items, item => ({
box: item.getBBox(),
item
}));
// Calculate where the center of each item should be positioned horizontally.
horizontalCenter = _.reduce(values,
(center, { box }) => Math.max(center, box.cx),
0);
// Position items with padding between them and align their centers.
_.reduce(values, (offset, { item, box }) => {
item.transform(Snap.matrix()
.translate(horizontalCenter - box.cx, offset));
return offset + options.padding + box.height;
}, 0);
}
// Creates a Promise that will be resolved after a specified delay.
//
// - __delay__ - Time in milliseconds to wait before resolving promise.
function wait(delay) {
return new Promise((resolve, reject) => {
setTimeout(resolve, delay);
});
}
// Creates a Promise that will be resolved after 0 milliseconds. This is used
// to create a short delay that allows the browser to address any pending tasks
// while the JavaScript VM is not active.
function tick() {
return wait(0);
}
// Re-throws an exception asynchronously. This is used to expose an exception
// that was created during a Promise operation to be handled by global error
// handlers (and to be displayed in the browser's debug console).
//
// - __error__ - Error/exception object to be re-thrown to the browser.
function exposeError(error) {
setTimeout(() => {
throw error;
}, 0);
}
// Renders an SVG icon.
//
// - __selector__ - Selector to the SVG icon to render.
function icon(selector) {
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 8 8"><use xlink:href="${selector}" /></svg>`;
}
// Send tracking data.
function track() {
if (window.ga) {
ga.apply(ga, arguments);
} else {
console.debug.apply(console, arguments);
}
}
export default {
customEvent,
normalizeBBox,
spaceHorizontally,
spaceVertically,
wait,
tick,
exposeError,
icon,
track
};

View File

@ -1,3 +0,0 @@
# robotstxt.org/
User-agent: *

View File

View File

@ -1,50 +0,0 @@
@import 'bourbon';
$green: #bada55;
$dark-green: shade($green, 25%);
$light-green: tint($green, 25%);
$gray: #6b6659;
$light-gray: tint($gray, 25%);
$tan: #cbcbba;
$red: #b3151a;
$blue: #dae9e5;
$yellow: #f8ca00;
$black: #000;
$white: #fff;
$base-font-size: 16px;
$base-line-height: 24px;
@mixin box-shadow {
@include prefixer(box-shadow, 0 0 10px $black, webkit moz spec);
}
@mixin input-placeholder {
&:-moz-placeholder {
@content;
}
&::-moz-placeholder {
@content;
}
&:-ms-input-placeholder {
@content;
}
&::-webkit-input-placeholder {
@content;
}
}
@function rhythm($scale, $font-size: $base-font-size) {
@return ($scale * $base-line-height / $font-size) * 1em;
}
@mixin adjust-font-size-to($to-size, $lines: auto) {
font-size: ($to-size / $base-font-size) * 1em;
@if $lines == auto {
$lines: ceil($to-size / $base-font-size);
}
line-height: rhythm($lines, $to-size);
}

View File

@ -1,373 +0,0 @@
@import 'base';
@import 'reset';
body {
font-family: sans-serif;
font-size: $base-font-size;
line-height: $base-line-height;
background: $gray;
margin-bottom: rhythm(1);
}
a {
color: $black;
}
.inline-icon {
svg {
margin-right: rhythm(1/4);
width: 1em;
height: 1em;
vertical-align: middle;
background: transparent;
}
}
h1 {
@include adjust-font-size-to(48px, 2);
}
ul.inline-list {
@include adjust-font-size-to(14px, 2/3);
@include clearfix;
li {
list-style-type: none;
display: inline-block;
white-space: nowrap;
&::after {
content: '//';
padding: 0 rhythm(1/4);
}
&:last-child::after {
content: '';
}
}
}
.svg-container {
min-width: 200px;
&.loading .svg {
position: absolute;
top: -10000px;
}
}
header {
background: $green;
background: linear-gradient(to bottom, $green 0%, $dark-green 100%);
padding: rhythm(1);
@include box-shadow;
@include clearfix;
.logo {
display: inline-block;
span {
color: $gray;
}
}
h1 {
font-family: 'Bangers', 'cursive';
}
nav {
@include adjust-font-size-to(18px, 1);
display: inline-block;
margin-left: rhythm(1/4);
padding-left: rhythm(1/4);
}
a {
text-decoration: inherit;
&:active, &:focus {
text-decoration: underline;
}
}
}
#content {
padding: rhythm(1);
display: block;
.copy {
background-color: $tan;
padding: rhythm(1/2);
}
.changelog {
dt {
font-weight: bold;
}
dd {
&::before {
content: '\00BB';
font-weight: bold;
margin-right: rhythm(1/2);
}
}
}
.error {
overflow: hidden;
h1 {
@include adjust-font-size-to($base-font-size * 2);
font-weight: bold;
float: left;
}
blockquote {
background-color: $green;
position: relative;
padding: rhythm(1);
display: inline-block;
font-style: italic;
float: right;
&::before {
@include adjust-font-size-to($base-font-size * 4);
content: '\201c';
position: absolute;
left: 0;
top: 0;
font-style: normal;
}
&::after {
@include adjust-font-size-to($base-font-size * 4);
content: '\201d';
position: absolute;
right: 0;
bottom: -0.5em;
font-style: normal;
}
}
p {
clear: left;
}
}
.documentation {
h1 {
@include adjust-font-size-to($base-font-size * 2);
font-weight: bold;
}
h2 {
@include adjust-font-size-to($base-font-size);
font-weight: bold;
}
h3 {
@include adjust-font-size-to($base-font-size);
&::before {
content: '\00BB';
font-weight: bold;
margin-right: rhythm(1/4);
}
}
h1, h2, h3 {
clear: both;
}
h2, h3 {
margin-bottom: rhythm(1);
}
section, div.section {
margin: rhythm(1) 0;
overflow: hidden;
}
p {
margin: rhythm(1) 0;
}
figure {
line-height: 0;
background: $white;
margin: rhythm(1/4);
@include box-shadow;
&.shift-right {
float: right;
margin-left: rhythm(1/2);
}
&.shift-left {
float: left;
margin-right: rhythm(1/2);
}
.svg {
margin: 0;
text-align: center;
}
figcaption {
@include adjust-font-size-to($base-font-size);
background: $green;
font-weight: bold;
padding: 0 rhythm(1/4);
}
}
}
.application {
position: relative;
@include clearfix;
form {
overflow: hidden;
}
textarea {
@include adjust-font-size-to($base-font-size);
border: 0 none;
outline: none;
background: $tan;
padding: 0 0.5em;
margin-bottom: 0.25em;
width: 100% !important; // "!important" prevents user changing width
box-sizing: border-box;
font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
@include input-placeholder {
color: $gray;
}
}
button {
@include adjust-font-size-to($base-font-size);
width: 100px;
border: 0 none;
background: $green;
background: linear-gradient(to bottom, $green 0%, $dark-green 100%);
float: left;
cursor: pointer;
}
ul {
float: right;
display: none;
body.has-results & {
display: inline-block;
}
&.hide-download-png.hide-permalink .download-svg:after,
&.hide-permalink .download-png:after {
display: none;
}
&.hide-permalink .permalink,
&.hide-download-svg .download-svg,
&.hide-download-png .download-png {
display: none;
}
}
}
.results {
margin-top: rhythm(1);
display: none;
body.has-results &, body.has-error &, body.is-loading & {
display: block;
}
}
}
.progress {
width: 50%;
height: rhythm(1/2);
border: 1px solid $dark-green;
overflow: hidden;
margin: rhythm(1) auto;
div {
background: $green;
background: linear-gradient(135deg, $green 25%, $light-green 25%, $light-green 50%, $green 50%, $green 75%, $light-green 75%, $light-green 100%);
background-size: rhythm(2) rhythm(2);
background-repeat: repeat-x;
height: 100%;
animation: progress 1s infinite linear
}
}
@keyframes progress {
0% { background-position-x: rhythm(2); }
100% { background-position-x: 0; }
}
#error {
background: $red;
color: $white;
padding: 0 0.5em;
white-space: pre;
font-family: monospace;
font-weight: bold;
display: none;
overflow-x: auto;
body.has-error & {
display: block;
}
}
#warnings {
@include adjust-font-size-to($base-font-size, 1);
font-weight: bold;
background-color: $yellow;
display: none;
li {
margin: rhythm(1/4);
}
body.has-results & {
display: block;
}
}
#regexp-render {
background: $white;
width: 100%;
overflow: auto;
text-align: center;
display: none;
body.is-loading &,
body.has-results & {
display: block;
}
}
#open-iconic {
display: none;
path {
stroke: none;
fill-opacity: 1;
}
}
footer {
padding: 0 rhythm(1);
img {
vertical-align: middle;
width: 80px;
height: 15px;
}
}

View File

@ -1,48 +0,0 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

View File

@ -1,72 +0,0 @@
@import 'base';
svg {
background-color: $white;
}
.root text,
.root tspan {
font: 12px Arial;
}
.root path {
fill-opacity: 0;
stroke-width: 2px;
stroke: $black;
}
.root circle {
fill: $gray;
stroke-width: 2px;
stroke: $black;
}
.anchor text, .any-character text {
fill: $white;
}
.anchor rect, .any-character rect {
fill: $gray;
}
.escape text, .charset-escape text, .literal text {
fill: $black;
}
.escape rect, .charset-escape rect {
fill: $green;
}
.literal rect {
fill: $blue;
}
.charset .charset-box {
fill: $tan;
}
.subexp .subexp-label tspan,
.charset .charset-label tspan,
.match-fragment .repeat-label tspan {
font-size: 10px;
}
.repeat-label {
cursor: help;
}
.subexp .subexp-label tspan,
.charset .charset-label tspan {
dominant-baseline: text-after-edge;
}
.subexp .subexp-box {
stroke: $light-gray;
stroke-dasharray: 6,2;
stroke-width: 2px;
fill-opacity: 0;
}
.quote {
fill: $light-gray;
}

View File

@ -1,51 +0,0 @@
var webpack = require('webpack'),
bourbon = require('bourbon'),
config = require('./config');
module.exports = {
devtool: 'source-map',
entry: {
'js/main.js': ['babel-polyfill', './src/js/main.js'],
'__discard__/css/main.css.js': './src/sass/main.scss',
'__discard__/css/svg.css.js': './src/sass/svg.scss'
},
output: {
path: config.buildRoot,
filename: '[name]'
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
})
],
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: require.resolve('snapsvg'),
loader: 'imports-loader?this=>window,fix=>module.exports=0'
},
{
test: /\.peg$/,
loader: require.resolve('./lib/canopy-loader')
},
{
test: /\.scss$/,
exclude: /node_modules/,
loaders: [
'file-loader?name=css/[name].css',
'extract-loader',
'css-loader',
'sass-loader?includePaths[]=' + bourbon.includePaths
]
}
]
}
};

6615
yarn.lock

File diff suppressed because it is too large Load Diff