From 68dc0a481301d885d716a067140345d77c105454 Mon Sep 17 00:00:00 2001 From: ache Date: Mon, 23 Apr 2018 03:58:36 +0200 Subject: Add a well tested plugin --- __tests__/index.js | 527 +++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 33 ++++ src/index.js | 248 +++++++++++++++++++++++++ 3 files changed, 808 insertions(+) create mode 100644 __tests__/index.js create mode 100644 package.json create mode 100644 src/index.js diff --git a/__tests__/index.js b/__tests__/index.js new file mode 100644 index 0000000..09bb450 --- /dev/null +++ b/__tests__/index.js @@ -0,0 +1,527 @@ +'use stric'; + +import test from 'ava'; +import parse from '../src'; + +const erreurHappened = {prop: {key: undefined, class: undefined, id: undefined}, eaten: ''}; + +test('line-input', t => { + const toParse = '{key=value}'; + const r = parse(toParse); + t.is(r.prop.key, 'value'); + t.is(r.eaten, toParse); +}); + +test('curvy-brace optional', t => { + const toParse = 'key=value'; + const r = parse(toParse); + t.is(r.prop.key, 'value'); + t.is(r.eaten, toParse); +}); + +test('simple', t => { + const toParse = 'key'; + const r = parse(toParse); + t.is(r.eaten, toParse); + t.is('key' in r.prop, true); + t.is(r.prop.key, undefined); +}); + +test('simple-class', t => { + const toParse = '.class'; + const r = parse(toParse); + t.is(r.eaten, '.class'); + t.deepEqual(r.prop.class, ['class']); + t.is(r.prop.key, undefined); +}); + +test('simple-id', t => { + const toParse = '{#id}'; + const r = parse(toParse); + t.is(r.eaten, toParse); + t.is(r.prop.id, 'id'); + t.is(r.prop.key, undefined); +}); + +test('id and class', t => { + const toParse = '{#id .class}'; + const r = parse(toParse); + t.is(r.eaten, toParse); + t.is(r.prop.id, 'id'); + t.deepEqual(r.prop.class, ['class']); +}); + +test('classes', t => { + const toParse = '{.class2 .class .class3}'; + const r = parse(toParse); + const expected = ['class', 'class2', 'class3']; + t.is(r.eaten, toParse); + r.prop.class.forEach(elem => t.is(expected.indexOf(elem) > -1, true)); + expected.forEach(elem => t.is(r.prop.class.indexOf(elem) > -1, true)); +}); + +test('prop with class', t => { + const toParse = '{class=another .class}'; + const r = parse(toParse); + t.is(r.eaten, toParse); + t.deepEqual(r.prop.class, ['class']); +}); + +test('id and id key', t => { + const toParse = '#id id=falseId'; + const r = parse(toParse); + t.is(r.eaten, toParse); +}); + +test('class with key class', t => { + const toParse = '{class=falseClass .class}'; + const r = parse(toParse); + const expected = ['class']; + t.is(r.eaten, toParse); + r.prop.class.forEach(elem => t.is(expected.indexOf(elem) > -1, true)); + expected.forEach(elem => t.is(r.prop.class.indexOf(elem) > -1, true)); +}); + +test('classes with key class', t => { + const toParse = '{class=falseClass .class .class2 class=falseClass2}'; + const r = parse(toParse); + const expected = ['class', 'class2']; + t.is(r.eaten, toParse); + r.prop.class.forEach(elem => t.is(expected.indexOf(elem) > -1, true)); + expected.forEach(elem => t.is(r.prop.class.indexOf(elem) > -1, true)); +}); + +test('classes with key class2', t => { + const toParse = 'class=falseClass .class .class2 class=falseClass2 .class3'; + const r = parse(toParse); + const expected = ['class', 'class2', 'class3']; + t.is(r.eaten, toParse); + r.prop.class.forEach(elem => t.is(expected.indexOf(elem) > -1, true)); + expected.forEach(elem => t.is(r.prop.class.indexOf(elem) > -1, true)); +}); + +test('id and id key with class', t => { + const toParse = '{id=falseId #id .id}'; + const r = parse(toParse); + t.is(r.prop.id, 'id'); + t.deepEqual(r.prop.class, ['id']); + t.is(r.eaten, toParse); +}); + +test('multiple id', t => { + const toParse = '#id #badId #again'; + const r = parse(toParse); + t.is(r.prop.id, 'id'); + t.is(r.eaten, toParse); +}); + +test('empty', t => { + const toParse = ''; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, toParse); +}); + +test('empty brace', t => { + const toParse = '{}'; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, toParse); +}); + +test('only spaces', t => { + const toParse = ' '; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, toParse); +}); + +test('only one space', t => { + const toParse = ' '; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, toParse); +}); + +test('only spaces with tab', t => { + const toParse = ' '; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, toParse); +}); + +test('braces with spaces', t => { + const toParse = '{ }'; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, toParse); +}); + +test('braces with spaces2', t => { + const toParse = '{ }'; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, toParse); +}); + +test('braces in braces', t => { + const toParse = '{{}}'; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.deepEqual(r, erreurHappened); + t.is(r.eaten, ''); // It's an error +}); + +test('spaces then braces', t => { + const toParse = ' {}'; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, toParse); +}); + +test('braces then spaces', t => { + const toParse = '{} '; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, '{}'); +}); + +test('newline in brace', t => { + const toParse = '{#id \n.class}'; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, ''); // It's an error +}); + +test('non latin value', t => { + const toParse = '{#id .class key=🕶 neko="🐱"}'; + const r = parse(toParse); + t.is(r.prop.id, 'id'); + t.is(r.prop.key, '🕶'); + t.is(r.prop.neko, '🐱'); + t.is(r.eaten, toParse); +}); + +test('non latin key and value', t => { + const toParse = '{ねこ=🐈}'; + const r = parse(toParse); + t.is(r.prop.ねこ, '🐈'); + t.is(r.eaten, toParse); +}); + +test('value in quote', t => { + const toParse = 'key="value"'; + const r = parse(toParse); + t.is(r.prop.key, 'value'); + t.is(r.eaten, toParse); +}); + +test('value in single quote', t => { + const toParse = `key='value'`; + const r = parse(toParse); + t.is(r.prop.key, 'value'); + t.is(r.eaten, toParse); +}); + +test('mutiple value to same key', t => { + const toParse = '{key=value key=value2 key=value3}'; + const r = parse(toParse); + t.is(r.prop.key, 'value3'); + t.is(r.eaten, toParse); +}); + +test('id key', t => { + const toParse = '{id=id}'; + const r = parse(toParse); + t.is(r.prop.id, undefined); + t.is(r.eaten, toParse); +}); + +test('class key', t => { + const toParse = 'class=class'; + const r = parse(toParse); + t.is(r.prop.class, undefined); + t.is(r.eaten, toParse); +}); + +test('newline stop', t => { + const toParse = 'hello=yeah \nnotParsed=stop'; + const r = parse(toParse); + t.is(r.prop.hello, 'yeah'); + t.not(r.prop.notParsed, 'stop'); + t.is(r.eaten, toParse.split('\n')[0]); +}); + +test('cariage return stop', t => { + const toParse = 'hello="yeah" \rnotParsed=stop'; + const r = parse(toParse); + t.is(r.prop.hello, 'yeah'); + t.not(r.prop.notParsed, 'stop'); + t.is(r.eaten, toParse.split('\r')[0]); +}); + +test('class with spaces', t => { + const toParse = '{.class="hello !"}'; + const r = parse(toParse); + t.is(r.prop.class, undefined); + t.is(r.eaten, ''); +}); + +test('class with spaces2', t => { + const toParse = `{.class='Hello ! }`; + const r = parse(toParse); + t.is(r.prop.class, undefined); + t.is(r.eaten, ''); +}); + +test('class with spaces3', t => { + const toParse = `{ .'Hello !'}`; + const r = parse(toParse); + t.deepEqual(r.prop.class, ['\'Hello']); + t.is('!\'' in r.prop, true); + t.is(r.eaten, toParse); +}); + +test('opening brace in value', t => { + const toParse = 'key=va{ue'; + const r = parse(toParse); + t.is(r.prop.key, 'va'); + t.is(r.eaten, 'key=va'); +}); + +test('normal case', t => { + const toParse = 'unicorn="unicorn" type="text" editable .answer #first-answer'; + const r = parse(toParse); + t.is(r.prop.unicorn, 'unicorn'); + t.is(r.prop.type, 'text'); + t.is(r.prop.editable, undefined); + t.is('editable' in r.prop, true); + t.is(r.prop.id, 'first-answer'); + t.deepEqual(r.prop.class, ['answer']); + t.is(r.eaten, toParse); +}); + +test('normal case2', t => { + const toParse = '{kind=textarea display #second-answer .nyan-cat}'; + const r = parse(toParse); + t.is(r.prop.kind, 'textarea'); + t.is(r.prop.display, undefined); + t.is(r.prop.id, 'second-answer'); + t.deepEqual(r.prop.class, ['nyan-cat']); + t.is(r.eaten, toParse); +}); + +test('normal case not prety-formated', t => { + const toParse = '{ kind= textarea display= #second-answer }'; + const r = parse(toParse); + t.is(r.prop.kind, ''); + t.is(r.prop.textarea, undefined); + t.is(r.prop.display, ''); + t.is(r.prop.id, 'second-answer'); + t.is(r.eaten, toParse); +}); + +test('normal case not prety-formated2', t => { + const toParse = ' kind=? textarea display=none #second-answer '; + const r = parse(toParse); + t.is(r.prop.kind, '?'); + t.is(r.prop.textarea, undefined); + t.is(r.prop.display, 'none'); + t.is(r.prop.id, 'second-answer'); + t.is(r.eaten, toParse); +}); + +test('double class', t => { + const toParse = '{.class .class}'; + const r = parse(toParse); + t.deepEqual(r.prop.class, ['class']); + t.is(r.eaten, toParse); +}); + +test('class look\'s like a key', t => { + const toParse = '{.class=key}'; + const r = parse(toParse); + t.is(r.prop.class, undefined); + t.is(r.eaten, ''); +}); + +test('id look\'s like a key', t => { + const toParse = '{#class=key}'; + const r = parse(toParse); + t.is(r.prop.id, undefined); + t.is(r.eaten, ''); +}); + +test('key\'s value lookes like a key', t => { + const toParse = '{key=key=value}'; + const r = parse(toParse); + t.is(r.prop.key, undefined); + t.is(r.eaten, ''); +}); + +test('key\'s value lookes like a key2', t => { + const toParse = 'key=key=value'; + const r = parse(toParse); + t.is(r.prop.key, 'key'); + t.is(r.eaten, 'key=key'); +}); + +test('long too parse', t => { + const toParse = '#Lorem-ipsum dolor sit ame=consectetur adipiscing elit .Sed ac placerat ex .Donec molestie .consequat dux=sodales laoreet justo vulputate .bibendum .Nunc sed ante .loborti qsd=venenatis velit ir=interdum lacus .Proin sed dapibus magna .Suspendisse .id faucibus ligula .Praesent fringilla auctor metus eget egestas .Cras at convallis justo .Duis mollis purus eras=ut maximus felis dignissim vel .Nulla fringille=ex id pellentesque .feugia sid=purus elit egestas odi=sagittis fringilla purus nulla in ligula .Etiam vitae .varius turpis .Sed scelerisque .non augue .vel dignissim .Class aptent taciti sociosqu ad litora torquent per conubia nostro=per inceptos himenaeos'; + const r = parse(toParse); + t.is(r.eaten, toParse); + t.snapshot(r); +}); + +test('very long too parse', t => { + const toParse = 'In lobortis sapien in libero dapibu=eget aliquam sapien congue ..Fusce .et eros non est placerat dignissim .In et risus sed risus posuere .dignissim sed vel risus .Curabitur sodales dui eu ex ornari=sit amet cursus tellus lobortis .Mauris sit amet aliquam purus=in pulvinar velit .Proin sit amet dignissim libero .Praesent imperdiet eros ut libero ultriciet=eget congue .ante .faucibus .Donec imperdiet mi ut neque .finibus pharetra .Ut vel semper leo .Sed pulvina=lorem vitae .convallis viverro=purus nulla dignissim dul=in placerat lectus enim fermentum odio .Ut et nisi id eros fermentum tristique ..Pellentesque .venenatis faucibus magna hendrerit fermentum .Mauris euismod finibus lorem ac pellentesque ..Suspendisse .facilisis ex a lorem molestir=ac rhoncus massa porta .Fusce .suscipit sapien dus=vitae .imperdiet velit tempus nec .Aenean ut est ac ligula molestie .bibendum .Quisque .vitae .placerat elit .Phasellus vel bibendum tellu=at ultricies felis .Mauris viverra urna in nibh volutpat congue ..Maecenas rhoncus commodo nisi id tristique ..Praesent id nisl at lacus elementum tempor .Quisque .eleifend nunc dolor .Nulla non sodales lacus=ut mattis nisi .Nullam nibh risut=auctor ut arcu eleifenlid=iaculis vulputate .dolor .Phasellus leo nunblu=malesuada vitae .orci i=pellentesque .rutrum libero .Etiam quam velib=accumsan sit amet gravida velo=finibus et ligula .Vivamus justo lacunaire=placerat in felis dapibu=vestibulum fermentum ex .Aenean ultricies felis luctus=condimentum lacus interdu97=placerat lacus .In libero nis=aliquet molestie .lectus sit ame=pulvinar mollis purus .Sed fringilla dolor non eros fermentum vulputate ..Donec a porta tellus .Mauris interdum egestas cursus .Quisque .luctus risus eget massa euismod euismod .Mauris in tellus ut neque .vehicula posuere ..Integer vitae .vehicula mauris .Fusce .non libero condimentus=efficitur massa quid=fringilla tellus .Nunc a arcu lacinia sem venenatis finibus .In egestas ex niso=nec tempus metus ornare .vel .Class aptent taciti sociosqu ad litora torquent per conubia nostre=per inceptos himenaeos .Fusce .sit amet vehicula mi .Sed nec rutrum tortor .Donec pretium mi nibu=ut sodales nibh volutpat sed .Vivamus finibus sollicitudin finibus .Quisque .euismo qsd=dolor porta feugiat ultricie dis=dui nunc pretium elikopter=vel suscipit dui nibh sit amet tortor .In suscipit condimentum nibus=quis condimentum lacus consequat in .Phasellus quis purus et ligula elementum fringilla id ut mi .Nullam ornare .magna ut arcu accumsa=non commodo nisi convallis .Sed eget consectetur orci .Sed vestibulum elit non erat sollicitudi=non maximus ipsum lobortis .Cras sed nisl iaculis massa eleifend accumsan .Suspendisse .nec ipsum elit .Quisque .at turpis erat .In lobortis sapien in libero dapibu=eget aliquam sapien congue ..Fusce .et eros non est placerat dignissim .In et risus sed risus posuere .dignissim sed vel risus .Curabitur sodales dui eu ex ornar=sit amet cursus tellus lobortis .Mauris sit amet aliquam purus=in pulvinar velit .Proin sit amet dignissim libero .Praesent imperdiet eros ut libero ultricien=eget congue .ante .faucibus .Donec imperdiet mi ut neque .finibus pharetra .Ut vel semper leo .Sed pulvina=lorem vitae .convallis viverr=purus nulla dignissim dus=in placerat lectus enim fermentum odio .Ut et nisi id eros fermentum tristique ..Pellentesque .venenatis faucibus magna hendrerit fermentum .Mauris euismod finibus #lorem ac pellentesque ..Suspendisse .facilisis ex a lorem molesti=ac rhoncus massa porta .Fusce .suscipit sapien du=vitae .imperdiet velit tempus nec .Aenean ut est ac ligula molestie .bibendum .Quisque .vitae .placerat elit .Phasellus vel bibendum tellus=at ultricies felis .Mauris viverra urna in nibh volutpat congue ..Maecenas rhoncus commodo nisi id tristique ..Praesent id nisl at lacus elementum tempor .Quisque .eleifend nunc dolor .Nulla non sodales lacu=ut mattis nisi .Nullam nibh risut=auctor ut arcu eleifenlid=iaculis vulputate .dolor .Phasellus leo nunblum=malesuada vitae .orci i=pellentesque .rutrum libero .Etiam quam veli=accumsan sit amet gravida ve=finibus et ligula .Vivamus justo lacu=placerat in felis dapibuqsdf=vestibulum fermentum ex .Aenean ultricies felis luctuqsdf=condimentum lacus interduaez=placerat lacus .In libero nisdf=aliquet molestie .lectus sit ameqsdf=pulvinar mollis purus .Sed fringilla dolor non eros fermentum vulputate ..Donec a porta tellus .Mauris interdum egestas cursus .Quisque .luctus risus eget massa euismod euismod .Mauris in tellus ut neque .vehicula posuere ..Integer vitae .vehicula mauris .Fusce .non libero condimentuazer=efficitur massa qui qsfd=fringilla tellus .Nunc a arcu lacinia sem venenatis finibus .In egestas ex niskip=nec tempus metus ornare .vel .Class aptent taciti sociosqu ad litora torquent per conubia nostro=per inceptos himenaeos .Fusce .sit amet vehicula mi .Sed nec rutrum tortor .Donec pretium mi nibus=ut sodales nibh volutpat sed .Vivamus finibus sollicitudin finibus .Quisque euismop=dolor porta feugiat ultricieser=dui nunc pretium elidi=vel suscipit dui nibh sit amet tortor .In suscipit condimentum nibus=quis condimentum lacus consequat in .Phasellus quis purus et ligula elementum fringilla id ut mi .Nullam ornare .magna ut arcu accumsame=non commodo nisi convallis .Sed eget consectetur orci .Sed vestibulum elit non erat sollicituditis=non maximus ipsum lobortis .Cras sed nisl iaculis massa eleifend accumsan .Suspendisse .nec ipsum elit .Quisque .at turpis erat'; + const r = parse(toParse); + t.is(r.eaten, toParse); + t.snapshot(r); +}); + +test('id with a hash', t => { + const toParse = '##id'; + const r = parse(toParse); + + t.is(r.prop.id, '#id'); + t.is(r.eaten, toParse); +}); + +test('class with a dot', t => { + const toParse = '..class'; + const r = parse(toParse); + t.deepEqual(r.prop.class, ['.class']); + t.is(r.eaten, toParse); +}); + +test('value start with a equal', t => { + const toParse = '{key==value}'; + const r = parse(toParse); + /* Maybe this + t.is(r.prop.key, ''); + t.is(r.eaten, toParse); + */ + t.is(r.prop.key, undefined); + t.is(r.eaten, ''); +}); + +test('single quote in key', t => { + const toParse = '{ke\'y=value}'; + const r = parse(toParse); + t.is(r.prop['ke\'y'], 'value'); + t.is(r.eaten, toParse); +}); + +test('quoted key', t => { + const toParse = '{"key"=value}'; + const r = parse(toParse); + t.is(r.prop['"key"'], 'value'); + t.is(r.eaten, toParse); +}); + +test('quoted class', t => { + const toParse = '{."key"}'; + const r = parse(toParse); + t.deepEqual(r.prop.class, ['"key"']); + t.is(r.eaten, toParse); +}); + +test('quoted id', t => { + const toParse = '{#"id"}'; + const r = parse(toParse); + t.is(r.prop.id, '"id"'); + t.is(r.eaten, toParse); +}); + +test('key that start by a quote', t => { + const toParse = '{\'key=value}'; + const r = parse(toParse); + t.is(r.prop['\'key'], 'value'); + t.is(r.eaten, toParse); +}); + +test('single-quoted key', t => { + const toParse = '"key"=value'; + const r = parse(toParse); + t.is(r.prop['"key"'], 'value'); + t.is(r.eaten, toParse); +}); + +test('space in quoted value', t => { + const toParse = 'key="value and value"'; + const r = parse(toParse); + t.is(r.prop.key, 'value and value'); + t.is(r.eaten, toParse); +}); + +test('return in quoted value', t => { + const toParse = 'key="value and\nvalue"'; + const r = parse(toParse); + t.is(r.prop.key, undefined); + t.is(r.eaten, ''); +}); + +test('line feed in quoted value (brace version)', t => { + const toParse = 'key="value and\nvalue"'; + const r = parse(toParse); + t.is(r.prop.key, undefined); + t.is(r.eaten, ''); +}); + +test('cariage-return in quoted value (brace version)', t => { + const toParse = 'key="value and\rvalue"'; + const r = parse(toParse); + t.is(r.prop.key, undefined); + t.is(r.eaten, ''); +}); + +test('brace in quoted value', t => { + const toParse = `onclic="function() { return \\"Yeah !\\"; }"`; + const r = parse(toParse); + t.is(r.prop.onclic, 'function() { return "Yeah !"; }'); + t.is(r.eaten, toParse); +}); + +test('braces as key name', t => { + const toParse = '{ {}=qsf }'; + const r = parse(toParse); + t.is(r.prop['{}'], undefined); + t.is(r.eaten, ''); +}); + +test('braces as class name', t => { + const toParse = '{ .{} }'; + const r = parse(toParse); + t.is(r.prop.class, undefined); + t.is(r.eaten, ''); +}); + +test('alone dot', t => { + const toParse = '{ . }'; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, ''); // It's an error +}); + +test('start by a equal', t => { + const toParse = '{ =qsdf }'; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, ''); // It's an error +}); + +test('alone hash', t => { + const toParse = '#'; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, ''); // It's an error +}); + +test('alone brace', t => { + const toParse = '{'; + const r = parse(toParse); + t.deepEqual(r.prop, erreurHappened.prop); + t.is(r.eaten, ''); // It's an error +}); + diff --git a/package.json b/package.json new file mode 100644 index 0000000..698be3a --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "markdown-attribute-parser", + "version": "1.0.0", + "description": "A parser for pandoc-style markdown's attributes", + "main": "src/index.js", + "scripts": { + "pretest": "xo", + "test": "ava" + }, + "repository": { + "type": "git", + "url": "srv:git/markdown-attribute-parser" + }, + "keywords": [ + "markdown", + "attribute" + ], + "devDependencies": { + "ava": "^0.25.0", + "xo": "^0.18.2" + }, + "license": "GPL-3.0", + "xo": { + "space": true, + "rules": { + "comma-dangle": [ + "error", + "always-multiline" + ] + } + }, + "author": "ache " +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..ac3c426 --- /dev/null +++ b/src/index.js @@ -0,0 +1,248 @@ +'use strict'; + +// A valid output which mean nothing has been parsed. +// Used as error return / invalid output +const nothingHappend = { + prop: { + key: undefined, + class: undefined, + id: undefined, + }, + eaten: '', +}; + +// Main function +function parse(value, indexNext = 0) { + let letsEat = ''; + let stopOnBrace = false; + let errorDetected = false; + + const prop = {key: undefined /* {} */, class: undefined /* [] */, id: undefined}; + + /* They is at leat one label and at best two */ + /* ekqsdf <- one label + * qsdfqsfd=qsdfqsdf <- two */ + let labelFirst = ''; + let labelSecond; + + /* 3 types : + * .azcv <- class + * #poi <- id + * dfgh=zert <- key + * lkj <- this is also a key but with a undefined value + */ + let type; + const forbidenCharacters = '\n\r{}'; + + // A function that detect if it's time to end the parsing + const shouldStop = () => { + if (indexNext >= value.length || forbidenCharacters.indexOf(value[indexNext]) > -1) { + if (stopOnBrace && value[indexNext] !== '}') { + errorDetected = true; + } + return true; + } + return value[indexNext] === '}' && stopOnBrace; + }; + + let eaten = ''; + // Couple of functions that parse same kinds of characters + // Used to parse spaces or identifiers + const eat = chars => { + eaten = ''; + + while (indexNext < value.length && + forbidenCharacters.indexOf(value.charAt(indexNext)) < 0 && + chars.indexOf(value.charAt(indexNext)) >= 0) { + letsEat += value.charAt(indexNext); + eaten += value.charAt(indexNext); + indexNext++; + } + + return shouldStop(); + }; + const eatUntil = (chars, shouldSave) => { + eaten = ''; + + while (indexNext < value.length && + forbidenCharacters.indexOf(value.charAt(indexNext)) < 0 && + chars.indexOf(value.charAt(indexNext)) < 0) { + letsEat += value.charAt(indexNext); + eaten += value.charAt(indexNext); + indexNext++; + } + + // Ugly but keep the main loop readable + if (shouldSave) { + if (labelFirst) { + labelSecond = eaten; + } else { + labelFirst = eaten; + } + } + + return shouldStop(); + }; + + const eatInQuote = quote => { + eaten = ''; + if (value[indexNext] === quote) { + return; + } + + while (indexNext < value.length && + !(quote === value[indexNext] && value[indexNext - 1] !== '\\') && + value[indexNext] !== '\n' && value[indexNext] !== '\r') { + letsEat += value.charAt(indexNext); + eaten += value.charAt(indexNext); + indexNext++; + } + if (value[indexNext] === '\n' || value[indexNext] === '\r' || indexNext >= value.length) { + errorDetected = true; + return true; + } + + // Ugly but keep the main loop readable + if (labelFirst) { + labelSecond = eaten.replace(/\\"/g, '"'); + } else { + labelFirst = eaten.replace(/\\"/g, '"'); + } + + return shouldStop(); + }; + + // It's realy commun to eat only one character so let's make it a function + const eatOne = c => { + letsEat += c; + indexNext++; + return shouldStop(); + }; + + const addAttribute = () => { + switch (type) { + case 'id': // ID + prop.id = prop.id || labelFirst; + break; + case 'class': + if (!prop.class) { + prop.class = []; + } + + if (prop.class.indexOf(labelFirst) < 0) { + prop.class.push(labelFirst); + } + + break; + case 'key': + if (!labelFirst) { + return nothingHappend; + } + if (labelFirst !== 'id' && labelFirst !== 'class') { + prop[labelFirst] = labelSecond; + } + break; + default: + } + type = undefined; + labelFirst = ''; + labelSecond = undefined; + }; + + /** *********************** Start parsing ************************ */ + + // Let's check for trelling spaces first + eat(' \t\v'); + + if (value[indexNext] === '{') { + eatOne('{'); + stopOnBrace = true; + } + + while (!shouldStop()) { + if (eat(' \t\v')) { + break; + } + + if (value.charAt(indexNext) === '.') { // Classes + type = 'class'; + if (eatOne('.')) { + errorDetected = true; + break; + } + } else if (value.charAt(indexNext) === '#') { // ID + type = 'id'; + if (eatOne('#')) { + errorDetected = true; + break; + } + } else { // Key + type = 'key'; + } + + // Extract name + if (eatUntil('=\t\b\v  ', true) || !labelFirst) { + break; + } + if (value.charAt(indexNext) === '=' && type === 'key') { // Set labelSecond + if (eatOne('=')) { + break; + } + + if (value.charAt(indexNext) === '"') { + if (eatOne('"')) { + break; + } + + if (eatInQuote('"')) { + break; + } + + if (value.charAt(indexNext) === '"') { + if (eatOne('"')) { + break; + } + } else { + return nothingHappend; + } + } else if (value.charAt(indexNext) === '\'') { + if (eatOne('\'')) { + break; + } + if (eatInQuote('\'')) { + break; + } + + if (value.charAt(indexNext) === '\'') { + if (eatOne('\'')) { + break; + } + } else { + return nothingHappend; + } + } else if (eatUntil(' \t\n\r\v=}', true)) { + break; + } + } + + // Add the parsed attribute to the output prop with the ad hoc type + addAttribute(); + } + addAttribute(); + if (stopOnBrace) { + if (indexNext < value.length && value[indexNext] === '}') { + stopOnBrace = false; + eatOne('}'); + } else { + return nothingHappend; + } + } + + if (errorDetected) { + return nothingHappend; + } + + return {prop, eaten: letsEat}; +} + +module.exports = parse; -- cgit v1.2.3