зеркало из https://github.com/telerik/dss.git
451 строка
14 KiB
JavaScript
451 строка
14 KiB
JavaScript
const _ = require('lodash');
|
|
|
|
// DSS Object
|
|
let dss = ( function() {
|
|
|
|
// Store reference
|
|
let _dss = function() {};
|
|
|
|
// 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;
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
|
|
/*
|
|
* 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 ];
|
|
};
|
|
|
|
/*
|
|
* Trim whitespace from string
|
|
*
|
|
* @param (String) The string to be trimmed
|
|
* @return (String) The trimmed string
|
|
*/
|
|
_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]';
|
|
};
|
|
|
|
/*
|
|
* Check the size of an object
|
|
*
|
|
* @param (Object) The object to check
|
|
* @return (Boolean) The result of the test
|
|
*/
|
|
_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 === 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 ( 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 );
|
|
};
|
|
|
|
/*
|
|
* 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
|
|
* the whole block.
|
|
*
|
|
* @param (String) Text block
|
|
* @return (String) A cleaned up 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.
|
|
let textBlock = block.replace( /^(\s*\*+)/, '' );
|
|
|
|
// 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;
|
|
|
|
} ).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 ) {
|
|
|
|
// Options
|
|
// eslint-disable-next-line no-param-reassign
|
|
options = ( options ) ? options : {};
|
|
options.preserveWhitespace = Boolean(options.preserveWhitespace);
|
|
|
|
// 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
|
|
* @param (Num) number of lines
|
|
* @param (String) line to parse/check
|
|
* @return (Boolean) result of parsing
|
|
*/
|
|
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 */
|
|
};
|
|
|
|
/*
|
|
* Check for single-line comment
|
|
*
|
|
* @param (String) line to parse/check
|
|
* @return (Boolean) result of check
|
|
*/
|
|
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
|
|
*/
|
|
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
|
|
*/
|
|
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
|
|
*/
|
|
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
|
|
*/
|
|
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 } );
|
|
|
|
};
|
|
|
|
_dss.getMultiLineContent = (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 = _dss.trim(block.split('').splice(i, markupLength).join(''));
|
|
let parserMarker = '@' + parserName;
|
|
|
|
markup = ((markup) => {
|
|
let ret = [];
|
|
let lines = markup.split('\n');
|
|
|
|
lines.forEach((line) => {
|
|
let pattern = '*';
|
|
let index = line.indexOf(pattern);
|
|
|
|
if (index > 0 && index < 10) {
|
|
line = line.split('').splice((index + pattern.length), line.length).join('');
|
|
}
|
|
|
|
// multiline
|
|
if (lines.length <= 2) {
|
|
line = dss.trim( line );
|
|
}
|
|
|
|
if (line && line.indexOf(parserMarker) === -1) {
|
|
ret.push(line);
|
|
}
|
|
|
|
});
|
|
|
|
return ret.join('\n');
|
|
})( markup );
|
|
/* eslint-enable no-param-reassign */
|
|
|
|
return markup;
|
|
};
|
|
|
|
// Return function
|
|
return _dss;
|
|
})();
|
|
|
|
// Describe detection pattern
|
|
dss.detector((line) => {
|
|
if (typeof line !== 'string') {
|
|
return false;
|
|
}
|
|
let reference = line.split( '\n\n' ).pop();
|
|
return Boolean(reference.match(/.*@/));
|
|
});
|
|
|
|
// Describe parsing a name
|
|
dss.parser('name', (i, line, block, file) => { // eslint-disable-line no-unused-vars
|
|
return line;
|
|
});
|
|
|
|
// Describe parsing a description
|
|
dss.parser('description', (i, line, block, file) => { // eslint-disable-line no-unused-vars
|
|
return line;
|
|
});
|
|
|
|
// Describe parsing a state
|
|
dss.parser('state', (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', (i, line, block, file, parserName) => {
|
|
let markup = dss.getMultiLineContent(i, line, block, file, parserName);
|
|
|
|
return {
|
|
example: markup,
|
|
escaped: markup.replace(/</g, '<').replace(/>/g, '>')
|
|
};
|
|
});
|
|
|
|
module.exports = dss;
|