This commit is contained in:
PreslavKozovski 2021-02-03 13:13:23 +02:00
Родитель c035e1707a
Коммит cd74cfa66a
5 изменённых файлов: 316 добавлений и 337 удалений

Просмотреть файл

@ -28,4 +28,4 @@ indent_size = 4
[package.json]
indent_style = space
indent_size = 2
indent_size = 2

Просмотреть файл

@ -6,7 +6,8 @@
},
"env": {
"node": true,
"es6": true
"es6": true,
"mocha": true
},
"rules": {
"array-bracket-spacing": [ "error", "always" ],
@ -26,7 +27,7 @@
"key-spacing": [ "error", { "beforeColon": false, "afterColon": true } ],
"keyword-spacing": [ "error", { "before": true, "after": true } ],
"linebreak-style": [ "error", "unix" ],
"max-params": [ "error", 4 ],
"max-params": [ "error", 6 ],
"no-alert": "error",
"no-caller": "error",
"no-class-assign": "error",
@ -66,4 +67,4 @@
"space-infix-ops": [ "error", { "int32Hint": false } ],
"yoda": [ "error", "never", { "exceptRange": true } ]
}
}
}

616
dss.js
Просмотреть файл

@ -1,142 +1,145 @@
const _ = require('lodash');
// DSS Object
var dss = ( function () {
let dss = ( function() {
// Store reference
var _dss = function () {};
// Store reference
let _dss = function() {};
// Default detect function
_dss.detect = function () {
return true;
};
// Default detect function
_dss.detect = function() {
return true;
};
/*
/*
* Modify detector method
*
* @param (Function) The callback to be used to detect variables
*/
_dss.detector = function ( callback ) {
_dss.detect = callback;
};
_dss.detector = function( callback ) {
_dss.detect = callback;
};
// Store parsers
_dss.parsers = {};
// Store parsers
_dss.parsers = {};
/*
/*
* Add a parser for a specific variable
*
* @param (String) The name of the variable
* @param (Function) The callback to be executed at parse time
*/
_dss.parser = function ( name, callback ) {
_dss.parsers[ name ] = callback;
};
_dss.parser = function( name, callback ) {
_dss.parsers[ name ] = callback;
};
/*
/*
* Add an alias for a parser
*
* @param (String) The name of the new variable
* @param (String) The name of the existing parser to use
*/
_dss.alias = function ( newName, oldName ) {
_dss.parsers[ newName ] = _dss.parsers[ oldName ];
};
_dss.alias = function( newName, oldName ) {
_dss.parsers[ newName ] = _dss.parsers[ oldName ];
};
/*
/*
* Trim whitespace from string
*
* @param (String) The string to be trimmed
* @return (String) The trimmed string
*/
_dss.trim = function ( str, arr ) {
var defaults = [ /^\s\s*/, /\s\s*$/ ];
arr = ( _dss.isArray( arr ) ) ? arr.concat( defaults ) : defaults;
arr.forEach( function( regEx ) {
str = str.replace( regEx, '' );
} );
return str;
};
_dss.trim = function( str, arr ) {
/* eslint-disable no-param-reassign */
let defaults = [ /^\s\s*/, /\s\s*$/ ];
arr = ( _dss.isArray( arr ) ) ? arr.concat( defaults ) : defaults;
arr.forEach( function( regEx ) {
str = str.replace( regEx, '' );
} );
return str;
/* eslint-enable no-param-reassign */
};
/*
/*
* Check if object is an array
*
* @param (Object) The object to check
* @return (Boolean) The result of the test
*/
_dss.isArray = function ( obj ) {
return toString.call( obj ) == '[object Array]';
};
_dss.isArray = function( obj ) {
return toString.call( obj ) === '[object Array]';
};
/*
/*
* Check the size of an object
*
* @param (Object) The object to check
* @return (Boolean) The result of the test
*/
_dss.size = function ( obj ) {
var size = 0;
for ( var key in obj ) {
if ( Object.prototype.hasOwnProperty.call( obj, key ) )
size++;
}
return size;
};
_dss.size = function( obj ) {
let size = 0;
for ( let key in obj ) {
if ( Object.prototype.hasOwnProperty.call( obj, key ) ) {size++;}
}
return size;
};
/*
/*
* Iterate over an object
*
* @param (Object) The object to iterate over
* @param (Function) Callback function to use when iterating
* @param (Object) Optional context to pass to iterator
*/
_dss.each = function ( obj, iterator, context ) {
if ( obj == null ) {
return;
}
if ( obj.length === +obj.length ) {
for ( var i = 0, l = obj.length; i < l; i++ ) {
if ( iterator.call( context, obj[ i ], i, obj ) === {} ) {
return;
}
}
} else {
for ( var key in obj ) {
if ( _.has( obj, key ) ) {
if ( iterator.call( context, obj[ key ], key, obj ) === {} ) {
_dss.each = function( obj, iterator, context ) {
if ( obj === null ) {
return;
}
}
}
}
};
if ( obj.length === Number(obj.length) ) {
for ( let i = 0, l = obj.length; i < l; i++ ) {
if ( iterator.call( context, obj[ i ], i, obj ) === {} ) {
return;
}
}
} else {
for ( let key in obj ) {
if ( _.has( obj, key ) ) {
if ( iterator.call( context, obj[ key ], key, obj ) === {} ) {
return;
}
}
}
}
};
/*
/*
* Extend an object
*
* @param (Object) The object to extend
*/
_dss.extend = function ( obj ) {
_dss.each( Array.prototype.slice.call( arguments, 1 ), function ( source ) {
if ( source ) {
for ( var prop in source ) {
obj[ prop ] = source[ prop ];
}
}
});
return obj;
};
_dss.extend = function( obj ) {
_dss.each( Array.prototype.slice.call( arguments, 1 ), function( source ) {
if ( source ) {
for ( let prop in source ) {
obj[ prop ] = source[ prop ];
}
}
});
return obj;
};
/*
/*
* Squeeze unnecessary extra characters/string
*
* @param (String) The string to be squeeze
* @param (String) The string to be matched
* @return (String) The modified string
*/
_dss.squeeze = function ( str, def ) {
return str.replace( /\s{2,}/g, def );
};
_dss.squeeze = function( str, def ) {
return str.replace( /\s{2,}/g, def );
};
/*
/*
* Normalizes the comment block to ignore any consistent preceding
* whitespace. Consistent means the same amount of whitespace on every line
* of the comment block. Also strips any whitespace at the start and end of
@ -145,62 +148,59 @@ var dss = ( function () {
* @param (String) Text block
* @return (String) A cleaned up text block
*/
_dss.normalize = function ( text_block ) {
_dss.normalize = function( block ) {
// Strip out any preceding [whitespace]* that occur on every line. Not
// the smartest, but I wonder if I care.
text_block = text_block.replace( /^(\s*\*+)/, '' );
// Strip out any preceding [whitespace]* that occur on every line. Not
// the smartest, but I wonder if I care.
let textBlock = block.replace( /^(\s*\*+)/, '' );
// Strip consistent indenting by measuring first line's whitespace
var indent_size = false;
var unindented = ( function ( lines ) {
return lines.map( function ( line ) {
var preceding_whitespace = line.match( /^\s*/ )[ 0 ].length;
if ( !indent_size ) {
indent_size = preceding_whitespace;
}
if ( line == '' ) {
return '';
} else if ( indent_size <= preceding_whitespace && indent_size > 0 ) {
return line.slice( indent_size, ( line.length - 1 ) );
} else {
return line;
}
} ).join( "\n" );
} )( text_block.split( "\n" ) );
// Strip consistent indenting by measuring first line's whitespace
let indentSize = false;
// eslint-disable-next-line no-unused-vars
let unindented = ( function( lines ) {
return lines.map( function( line ) {
let precedingWhitespace = line.match( /^\s*/ )[ 0 ].length;
if ( !indentSize ) {
indentSize = precedingWhitespace;
}
if ( line === '' ) {
return '';
} else if ( indentSize <= precedingWhitespace && indentSize > 0 ) {
return line.slice( indentSize, ( line.length - 1 ) );
}
return line;
return _dss.trim( text_block );
} ).join( '\n' );
} )( textBlock.split( '\n' ) );
};
return _dss.trim( textBlock );
/*
};
/*
* Takes a file and extracts comments from it.
*
* @param (Object) options
* @param (Function) callback
*/
_dss.parse = function ( lines, options, callback ) {
_dss.parse = function( lines, options, callback ) {
// Options
options = ( options ) ? options : {};
options.preserve_whitespace = !!( options.preserve_whitespace );
// Options
// eslint-disable-next-line no-param-reassign
options = ( options ) ? options : {};
options.preserveWhitespace = Boolean(options.preserveWhitespace);
// Setup
var _this = this;
var current_block = '';
var inside_single_line_block = false;
var inside_multi_line_block = false;
var last_line = '';
var start = "{start}";
var end = "{/end}";
var _parsed = false;
var _blocks = [];
var parsed = '';
var blocks = [];
var temp = {};
var lineNum = 0;
// Setup
let currentBlock = '';
let insideSingleLineBlock = false;
let insideMultiLineBlock = false;
let _blocks = [];
let parsed = '';
let blocks = [];
let temp = {};
let lineNum = 0;
/*
/*
* Parses line
*
* @param (Num) the line number
@ -208,262 +208,238 @@ var dss = ( function () {
* @param (String) line to parse/check
* @return (Boolean) result of parsing
*/
var parser = function ( temp, line, block, file ) {
var indexer = function ( str, find ) {
return ( str.indexOf( find ) > 0 ) ? str.indexOf( find ) : false;
};
var parts = line.replace( /.*@/, '' );
var i = indexer( parts, ' ' ) || indexer( parts, '\n' ) || indexer( parts, '\r' ) || parts.length;
var name = _dss.trim( parts.substr( 0, i ) );
var description = _dss.trim( parts.substr( i ) );
var variable = _dss.parsers[ name ];
var index = block.indexOf( line );
line = {};
line[ name ] = ( variable ) ? variable.apply( null, [ index, description, block, file, name ] ) : '';
if ( temp[ name ] ) {
if ( !_dss.isArray( temp[ name ] ) ) {
temp[name] = [ temp[ name ] ];
}
if ( !_dss.isArray( line[ name ] ) ) {
temp[ name ].push( line[ name ] );
} else {
temp[ name ].push( line[ name ][ 0 ] );
}
} else {
temp = _dss.extend( temp, line );
}
return temp;
};
let parser = function( temp, line, block, file ) {
/* eslint-disable no-param-reassign */
let indexer = function( str, find ) {
return ( str.indexOf( find ) > 0 ) ? str.indexOf( find ) : false;
};
let parts = line.replace( /.*@/, '' );
let i = indexer( parts, ' ' ) || indexer( parts, '\n' ) || indexer( parts, '\r' ) || parts.length;
let name = _dss.trim( parts.substr( 0, i ) );
let description = _dss.trim( parts.substr( i ) );
let variable = _dss.parsers[ name ];
let index = block.indexOf( line );
line = {};
line[ name ] = ( variable ) ? variable.apply( null, [ index, description, block, file, name ] ) : ''; // eslint-disable-line no-useless-call
if ( temp[ name ] ) {
if ( !_dss.isArray( temp[ name ] ) ) {
temp[name] = [ temp[ name ] ];
}
if ( !_dss.isArray( line[ name ] ) ) {
temp[ name ].push( line[ name ] );
} else {
temp[ name ].push( line[ name ][ 0 ] );
}
} else {
temp = _dss.extend( temp, line );
}
return temp;
/* eslint-enable no-param-reassign */
};
/*
* Comment block
*/
var block = function () {
this._raw = ( comment_text ) ? comment_text : '';
this._filename = filename;
};
/*
/*
* Check for single-line comment
*
* @param (String) line to parse/check
* @return (Boolean) result of check
*/
var single_line_comment = function ( line ) {
return !!line.match( /^\s*\/\// );
};
let singleLineComment = function( line ) {
return Boolean(line.match( /^\s*\/\// ));
};
/*
/*
* Checks for start of a multi-line comment
*
* @param (String) line to parse/check
* @return (Boolean) result of check
*/
var start_multi_line_comment = function ( line ) {
return !!line.match( /^\s*\/\*/ );
};
let startMultiLineComment = function( line ) {
return Boolean(line.match( /^\s*\/\*/ ));
};
/*
/*
* Check for end of a multi-line comment
*
* @parse (String) line to parse/check
* @return (Boolean) result of check
*/
var end_multi_line_comment = function ( line ) {
if( single_line_comment( line ) ) {
return false;
}
return !!line.match( /.*\*\// );
};
let endMultiLineComment = function( line ) {
if ( singleLineComment( line ) ) {
return false;
}
return Boolean(line.match( /.*\*\// ));
};
/*
/*
* Removes comment identifiers for single-line comments.
*
* @param (String) line to parse/check
* @return (Boolean) result of check
*/
var parse_single_line = function ( line ) {
return line.replace( /\s*\/\//, '' );
};
let parseSingleLine = function( line ) {
return line.replace( /\s*\/\//, '' );
};
/*
/*
* Remove comment identifiers for multi-line comments.
*
* @param (String) line to parse/check
* @return (Boolean) result of check
*/
var parse_multi_line = function ( line ) {
var cleaned = line.replace( /\s*\/\*/, '' );
return cleaned.replace( /\*\//, '' );
let parseMultiLine = function( line ) {
let cleaned = line.replace( /\s*\/\*/, '' );
return cleaned.replace( /\*\//, '' );
};
/* eslint-disable no-param-reassign */
lines = String(lines);
lines.split( /\n/ ).forEach( function( line ) {
lineNum = lineNum + 1;
line = String(line);
// Parse Single line comment
if ( singleLineComment( line ) ) {
parsed = parseSingleLine( line );
if ( insideSingleLineBlock ) {
currentBlock += '\n' + parsed;
} else {
currentBlock = parsed;
insideSingleLineBlock = true;
}
}
// Parse multi-line comments
if ( startMultiLineComment( line ) || insideMultiLineBlock ) {
parsed = parseMultiLine( line );
if ( insideMultiLineBlock ) {
currentBlock += '\n' + parsed;
} else {
currentBlock += parsed;
insideMultiLineBlock = true;
}
}
// End a multi-line block
if ( endMultiLineComment( line ) ) {
insideMultiLineBlock = false;
}
// Store current block if done
if ( !singleLineComment( line ) && !insideMultiLineBlock ) {
if ( currentBlock ) {
_blocks.push( _dss.normalize( currentBlock ) );
}
insideSingleLineBlock = false;
currentBlock = '';
}
});
/* eslint-enable no-param-reassign */
// Create new blocks with custom parsing
_blocks.forEach( function( block ) {
/* eslint-disable no-param-reassign */
// Remove extra whitespace
block = block.split( '\n' ).filter( function( line ) {
return ( _dss.trim( _dss.normalize( line ) ) );
} ).join( '\n' );
// Split block into lines
block.split( '\n' ).forEach( function( line ) {
if ( _dss.detect( line ) ) {
temp = parser( temp, _dss.normalize( line ), block, lines );
}
});
// Push to blocks if object isn't empty
if ( _dss.size( temp ) ) {
blocks.push( temp );
}
temp = {};
/* eslint-enable no-param-reassign */
});
// Execute callback with filename and blocks
callback( { blocks: blocks } );
};
lines = lines + '';
lines.split( /\n/ ).forEach( function ( line ) {
lineNum = lineNum + 1;
line = line + '';
// Parse Single line comment
if ( single_line_comment( line ) ) {
parsed = parse_single_line( line );
if ( inside_single_line_block ) {
current_block += '\n' + parsed;
} else {
current_block = parsed;
inside_single_line_block = true;
}
}
// Parse multi-line comments
if ( start_multi_line_comment( line ) || inside_multi_line_block ) {
parsed = parse_multi_line( line );
if ( inside_multi_line_block ) {
current_block += '\n' + parsed;
} else {
current_block += parsed;
inside_multi_line_block = true;
}
}
// End a multi-line block
if ( end_multi_line_comment( line ) ) {
inside_multi_line_block = false;
}
// Store current block if done
if ( !single_line_comment( line ) && !inside_multi_line_block ) {
if ( current_block ) {
_blocks.push( _dss.normalize( current_block ) );
}
inside_single_line_block = false;
current_block = '';
last_line = '';
}
});
// Done first pass
_parsed = true;
// Create new blocks with custom parsing
_blocks.forEach( function ( block ) {
// Remove extra whitespace
block = block.split( '\n' ).filter( function ( line ) {
return ( _dss.trim( _dss.normalize( line ) ) );
} ).join( '\n' );
// Split block into lines
block.split( '\n' ).forEach( function ( line ) {
if ( _dss.detect( line ) ) {
temp = parser( temp, _dss.normalize( line ), block, lines );
}
});
// Push to blocks if object isn't empty
if( _dss.size( temp ) ) {
blocks.push( temp );
}
temp = {};
});
// Execute callback with filename and blocks
callback( { blocks: blocks } );
};
// Return function
return _dss;
// Return function
return _dss;
})();
// Describe detection pattern
dss.detector( function( line ) {
if( typeof line !== 'string' ) {
return false;
}
var reference = line.split( "\n\n" ).pop();
return !!reference.match( /.*@/ );
if ( typeof line !== 'string' ) {
return false;
}
let reference = line.split( '\n\n' ).pop();
return Boolean(reference.match( /.*@/ ));
});
// Describe parsing a name
dss.parser( 'name', function ( i, line, block, file ) {
return line;
dss.parser( 'name', function( i, line, block, file ) { // eslint-disable-line no-unused-vars
return line;
});
// Describe parsing a description
dss.parser( 'description', function ( i, line, block, file ) {
return line;
dss.parser( 'description', function( i, line, block, file ) { // eslint-disable-line no-unused-vars
return line;
});
// Describe parsing a state
dss.parser( 'state', function ( i, line, block, file ) {
var state = line.split( ' - ' );
return [{
name: ( state[ 0 ] ) ? dss.trim( state[ 0 ] ) : '',
escaped: ( state[ 0 ] ) ? dss.trim( state[ 0 ].replace( '.', ' ' ).replace( ':', ' pseudo-class-' ) ) : '',
description: ( state[ 1 ] ) ? dss.trim( state[ 1 ] ) : ''
}];
dss.parser( 'state', function( i, line, block, file ) { // eslint-disable-line no-unused-vars
let state = line.split( ' - ' );
return [ {
name: ( state[ 0 ] ) ? dss.trim( state[ 0 ] ) : '',
escaped: ( state[ 0 ] ) ? dss.trim( state[ 0 ].replace( '.', ' ' ).replace( ':', ' pseudo-class-' ) ) : '',
description: ( state[ 1 ] ) ? dss.trim( state[ 1 ] ) : ''
} ];
});
// Describe parsing markup
dss.parser( 'markup', function ( i, line, block, file, parserName ) {
dss.parser( 'markup', function( i, line, block, file, parserName ) {
/* eslint-disable no-param-reassign */
// find the next instance of a parser (if there is one based on the @ symbol)
// in order to isolate the current multi-line parser
let nextParserIndex = block.indexOf( '* @', i + 1 );
let markupLength = ( nextParserIndex > -1 ) ? nextParserIndex - i : block.length;
let markup = block.split( '' ).splice( i, markupLength ).join( '' );
let parserMarker = '@' + parserName;
// find the next instance of a parser (if there is one based on the @ symbol)
// in order to isolate the current multi-line parser
var nextParserIndex = block.indexOf( '* @', i + 1 );
var markupLength = ( nextParserIndex > -1 ) ? nextParserIndex - i : block.length;
var markup = block.split( '' ).splice( i, markupLength ).join( '' );
var parserMarker = '@' + parserName;
markup = ( function( markup ) {
let ret = [];
let lines = markup.split( '\n' );
markup = ( function( markup ) {
var ret = [];
var lines = markup.split( '\n' );
lines.forEach( function( line ) {
let pattern = '*';
let index = line.indexOf( pattern );
lines.forEach( function( line ) {
var pattern = '*';
var index = line.indexOf( pattern );
if ( index > 0 && index < 10 ) {
line = line.split( '' ).splice( ( index + pattern.length ), line.length ).join( '' );
}
if ( index > 0 && index < 10 ) {
line = line.split( '' ).splice( ( index + pattern.length ), line.length ).join( '' );
}
// multiline
if ( lines.length <= 2 ) {
line = dss.trim( line );
}
// multiline
if ( lines.length <= 2 ) {
line = dss.trim( line );
}
if ( line && line.indexOf( parserMarker ) === -1 ) {
ret.push( line );
}
if ( line && line.indexOf( parserMarker ) == -1 ) {
ret.push( line );
}
});
});
return ret.join( '\n' );
/* eslint-enable no-param-reassign */
})( markup );
return ret.join( '\n' );
})( markup );
return {
example: markup,
escaped: markup.replace( /</g, '&lt;' ).replace( />/g, '&gt;' )
};
return {
example: markup,
escaped: markup.replace( /</g, '&lt;' ).replace( />/g, '&gt;' )
};
});
// Module exports
if( typeof exports !== 'undefined' ) {
if ( typeof module !== 'undefined' && module.exports ) {
exports = module.exports = dss;
}
exports.dss = dss;
} else {
root[ 'dss' ] = dss;
}
// AMD definition
if ( typeof define === 'function' && define.amd ) {
define( function ( require ) {
return dss;
});
}
module.exports = dss;

Просмотреть файл

@ -29,7 +29,9 @@
"test": "mocha",
"lint": "eslint **/*.js"
},
"dependencies": {},
"dependencies": {
"lodash": "^4.17.20"
},
"devDependencies": {
"eslint": "^7.19.0",
"mocha": "^8.2.1"

Просмотреть файл

@ -8,19 +8,19 @@ describe('Core tests', function() {
const file = fs.readFileSync(path.join(__dirname, 'data/button.scss'), 'utf8');
dss.parse(file, {}, function(parsed) {
var data = parsed.blocks[0];
const data = parsed.blocks[0];
assert.strictEqual(data.name, "Button");
assert.strictEqual(data.description, "Your standard form button.");
assert.strictEqual(data.state[0].name, ":hover");
assert.strictEqual(data.state[0].description, "Highlights when hovering.");
assert.strictEqual(data.state[1].name, ":disabled");
assert.strictEqual(data.state[1].description, "Dims the button when disabled.");
assert.strictEqual(data.state[2].name, ".primary");
assert.strictEqual(data.state[2].description, "Indicates button is the primary action.");
assert.strictEqual(data.state[3].name, ".smaller");
assert.strictEqual(data.markup.example, "<button>This is a button</button>");
assert.strictEqual(data.markup.escaped, "&lt;button&gt;This is a button&lt;/button&gt;");
assert.strictEqual(data.name, 'Button');
assert.strictEqual(data.description, 'Your standard form button.');
assert.strictEqual(data.state[0].name, ':hover');
assert.strictEqual(data.state[0].description, 'Highlights when hovering.');
assert.strictEqual(data.state[1].name, ':disabled');
assert.strictEqual(data.state[1].description, 'Dims the button when disabled.');
assert.strictEqual(data.state[2].name, '.primary');
assert.strictEqual(data.state[2].description, 'Indicates button is the primary action.');
assert.strictEqual(data.state[3].name, '.smaller');
assert.strictEqual(data.markup.example, '<button>This is a button</button>');
assert.strictEqual(data.markup.escaped, '&lt;button&gt;This is a button&lt;/button&gt;');
});
});
});