This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+24
View File
@@ -0,0 +1,24 @@
# Media
**/media
# Tests
**/test
# Coverage Reports
**/.nyc_output/
**/coverage/
# ESLint
.eslint*
# CI
.nycrc
.travis.yml
.codeclimate.yml
# IDE Specific
.vscode
# Windows
Thumbs.db
Desktop.ini
+59
View File
@@ -0,0 +1,59 @@
## Changelog
### 1.5.0 ~ _24 Jun 2018_
- Extended data types
- Added all sql-99 standard data types plus some t-sql data types
- Keyword sets pruning
- Included non standard commonly used keywords [AFTER, AUTHORIZATION, BEFORE, BREAK, CLOSE, COLUMN, CURSOR, EACH, ELSEIF, EXIT, FETCH, FIRST, FOR, GRANT, LIMIT, LAST, LOOP, NEXT, OFFSET, OPEN, RELATIVE, REPLACE, REVOKE, ROLLBACK, ROW, ROWS, SCHEMA, USING, WITHOUT] and lesser keywords [ESCAPE, TO]
- Excluded seldom used dbms-specific keywords [PREPARE]
- Merged __1.4.0-0__ (Experimental support for custom-built rules)
#### 1.4.0-0 _(rc)_ ~ _15 May 2018_
- Experimental support for custom-built rules
#### 1.3.0 ~ _12 May 2018_
- Basic support for single and multi-line comments
- Extended support for latin script unicode characters
#### 1.2.0 ~ _08 Mar 2018_
- Support for local variables and parameters
#### 1.1.0 ~ _26 Feb 2018_
- Fixed types and keywords matching inside constants
- Support for optional types and keywords casing
- Performance improvements
#### 1.0.1 ~ _03 Jan 2018_
- LICENSE and README updates
- CHANGELOG
#### 1.0.0 ~ _29 Dec 2017_
- Promotion to stable release
- Fixed compound operators mismatch
- Fixed keywords and data types breaking format if inside delimited identifiers
- Custom pluggable output support
#### 0.5.0 ~ _17 Sep 2017_
- Custom keywords and data types support
- Module improvements
#### 0.4.0 ~ _29 Jul 2017_
- Postfix support
#### 0.3.0 ~ _27 Jul 2017_
- Module improvements
- README update and image examples
#### 0.2.0 ~ _25 Jul 2017_
- Minimal fixes
- README
#### 0.1.0 ~ _23 Jul 2017_
- Base syntax highlighter release
#### 0.0.2 ~ _20 Jul 2017_
- Numbers and operators support
- Improved prefix substitution
#### 0.0.1 ~ _19 Jul 2017_
- Initial release
+19
View File
@@ -0,0 +1,19 @@
MIT License
Copyright 2018 Lucas Astrada
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+355
View File
@@ -0,0 +1,355 @@
# Igniculus
SQL Syntax Highlighter and Logger. Unadorned and customizable.
[![version](https://img.shields.io/npm/v/igniculus.svg)](https://www.npmjs.com/package/igniculus)
[![license](https://img.shields.io/npm/l/igniculus.svg)](https://github.com/Undre4m/igniculus/blob/master/LICENSE)
[![downloads](https://img.shields.io/npm/dt/igniculus.svg?colorB=ffdf00)](https://www.npmjs.com/package/igniculus)
[![build status](https://img.shields.io/travis/Undre4m/igniculus.svg?logo=travis&logoWidth=15)](https://travis-ci.org/Undre4m/igniculus)
[![maintainability](https://api.codeclimate.com/v1/badges/30482d982a79b253aed9/maintainability)](https://codeclimate.com/github/Undre4m/igniculus/maintainability)
[![test Coverage](https://api.codeclimate.com/v1/badges/30482d982a79b253aed9/test_coverage)](https://codeclimate.com/github/Undre4m/igniculus/test_coverage)
## Install
```console
$ npm install igniculus
```
## Usage
```js
const igniculus = require('igniculus')();
igniculus('SELECT [port] AS Printer, \'on fire\' AS Status ' +
'FROM [Printers] P ' +
'WHERE P."online" = 1 AND P."check" = 1');
```
![Simple Query Default](https://raw.githubusercontent.com/Undre4m/igniculus/master/media/simple-query.png)
## Table of Contents
- [Logger](#logger)
- [Options](#options)
- [Rules](#rules)
- [Styles](#styles)
- [Custom Rules](#custom-rules)
- [Examples](#examples)
- [Integration](#integration)
- [Sequelize](#sequelize)
## Logger
A reference to the log function is returned on initialization but can be accessed anywhere through `.log`
```js
// config.js
const igniculus = require('igniculus');
const options = { ... };
igniculus(options);
```
```js
// any.js
const igniculus = require('igniculus');
let query = 'SELECT ...';
igniculus.log(query);
```
## Options
A default color scheme is provided. However, you can define the highlight style for each rule when instantiating:
```js
const igniculus = require('igniculus');
/* White constants over red background using inverse mode.
* Gray keywords.
* Prefixed by white '(query)' message.
*/
const options = {
constants: { mode: 'inverse', fg: 'red', bg: 'white' },
standardKeywords: { mode: 'bold', fg: 'black' },
lesserKeywords: { mode: 'bold', fg: 'black' },
prefix: { mode: 'bold', fg: 'white', text: '(query) '}
};
const illumine = igniculus(options);
illumine('SELECT * FROM Student s ' +
'WHERE s.programme = \'IT\' AND EXISTS (' +
'SELECT * FROM Enrolled e ' +
'JOIN Class c ON c.code = e.code ' +
'JOIN Tutor t ON t.tid = c.tid ' +
'WHERE e.sid = s.sid AND t.name LIKE \'%Hoffman\')');
```
![Subquery](https://raw.githubusercontent.com/Undre4m/igniculus/master/media/subquery.png)
The _options_ argument is optional and each property should be one of the following.
### Rules
- options.**comments** - Single and multi-line comments. _E.g:_ `-- comments` or `/* Author: undre4m */`
- options.**constants** - Values surrounded by single quotes. _E.g:_ `'static'`
- options.**numbers** - Numeric values. _E.g:_ `2.5`
- options.**operators** - Arithmetic, Bitwise and Comparison operators. _E.g:_ `+` or `>=`
- options.**variables** - Local variables and parameters. _E.g:_ `@name` or `@@IDENTITY`
- options.**delimitedIdentifiers** - Text between brackets or double quotes. _E.g:_ `[Employee]` or `"salary"`
- options.**dataTypes** - One of the included data types. _E.g:_ `INTEGER` or `VARCHAR`
- dataTypes.**types** - Array of custom data types. Replaces the ones by default. _E.g:_ `['SERIAL', 'TIMESTAMP']`
- dataTypes.**casing** - Either `'lowercase'` or `'uppercase'`. If not defined data types won't be capitalized.
- options.**standardKeywords** - One the included keywords. _E.g:_ `SELECT` or `CONSTRAINT`
- standardKeywords.**keywords** - Array of custom standard keywords. Replaces the ones by default. _E.g:_ `['CLUSTER', 'NATURAL']`
- standardKeywords.**casing** - Either `'lowercase'` or `'uppercase'`. If not defined standard keywords won't be capitalized.
- options.**lesserKeywords** - One of the included lesser keywords. _E.g:_ `ANY`, `AVG` or `DESC`
- lesserKeywords.**keywords** - Array of custom lesser keywords. Replaces the ones by default. _E.g:_ `['VOLATILE', 'ASYMMETRIC']`
- lesserKeywords.**casing** - Either `'lowercase'` or `'uppercase'`. If not defined lesser keywords won't be capitalized.
- options.**prefix**
- prefix.**text** - A prefix can be appended to every log through this option. This prefix can be styled like any previous options.
- prefix.**replace** - Also, a _string_ or _regular expression_ can be provided and it will replace (if a prefix.**text** was given) or remove a prefix that matches such parameter. _E.g:_ [Sequelize](https://www.npmjs.com/package/sequelize) prefixes every _SQL statement_ with `Executing (default|transaction_id):` This is removed by **default** by the option `prefix: { replace: /.*?: / }`
- options.**postfix**
- postfix.**text** - A postfix can be appended to every log through this option. This postfix can be styled like any previous options.
- options.**output** - Output function for the highlighted statements, `console.log` by default. _E.g:_ `process.stdout`, `st => st`
- options.**own** - Your own custom-built rules can be defined here. See _[(Custom Rules)](#custom-rules)_ below for details.
If defined, the _options_ argument takes precedence over _default_ options. If a rule or it's style is missing it won't be applied. This allows to _"enable"_ or _"disable"_ certain syntax highlighting as you see fit. _[(Examples below)](#examples)_
>#### A word on types and keywords
>Most often, highlighting every reserved keyword can make syntax difficult to read, defeating the purpose altogether. Therefore, three distinct rules are provided: _dataTypes_, _standardKeywords_ and _lesserKeywords_.
Each of these rules can be customized individually and come with a [predefined list](https://github.com/Undre4m/igniculus/blob/master/index.js#L3) of most widely used T-SQL and SQL-92 keywords and data types. Furthermore each of this lists can be customized as described above.
>
>Starting from [v1.1.0](https://github.com/Undre4m/igniculus/blob/master/CHANGELOG.md#110--26-feb-2018) _types_ and _keywords_ are no longer uppercased by default. Custom styles should use the `casing: 'uppercase'` option for this behaviour. Predefined style already provides this option so no changes should be required.
### Styles
All of the previous rule styles can be defined like this:
```js
/* options = {"rule": style, ... } where
* style = { mode: "modifier", fg: "color", bg: "color"}
*/
const options = {
constants: {
mode: 'inverse',
fg: 'red',
bg: 'white'
},
...
};
```
Each style having an optional:
- style.**mode** - Modifier. _E.g:_ `'bold'`
- style.**fg** - Foreground text color. _E.g:_ `'red'`
- style.**bg** - Background color. _E.g:_ `'black'`
These can be one of the following.
#### Modifiers
- `reset`
- `bold`
- `dim`
- `italic`
- `underline`
- `blink`
- `inverse`
- `hidden`
- `strikethrough`
#### Colors (Foreground and Background)
- `black`
- `red`
- `green`
- `yellow`
- `blue`
- `magenta`
- `cyan`
- `white`
### Custom Rules
>#### ⚠ Be advised
>This feature is experimental and should be used with discretion. Custom pattern-matching has the potential to disrupt other rules and induce defects in highlighting.
You can define as many rules as needed. Like built-in rules, an optional style can be set for each one. Every **rule** can be named as desired, simple names are encouraged to avoid problems though. Option **transform** is not required, **regexp** is.
- options.**own**
- own.**rule**
- rule.**regexp** - A _regular expression_ must be provided for the rule to be applied. _E.g:_ `/(https?|ftp):\/\/[^\s/$.?#].[^\s]*/g`
- rule.**transform** - Each matched expression can be either replaced by a _string_ or transformed by a _function_. The function takes one argument, the matched expression, and it's return value will be used for replacement. _E.g:_ `'hidden'` or `match => match.trim()`
## Examples
```js
/* Predifined style */
const defaults = {
comments: { mode: 'dim', fg: 'white' },
constants: { mode: 'dim', fg: 'red' },
delimitedIdentifiers: { mode: 'dim', fg: 'yellow' },
variables: { mode: 'dim', fg: 'magenta' },
dataTypes: { mode: 'dim', fg: 'green', casing: 'uppercase' },
standardKeywords: { mode: 'dim', fg: 'cyan', casing: 'uppercase' },
lesserKeywords: { mode: 'bold', fg: 'black', casing: 'uppercase' },
prefix: { replace: /.*?: / }
};
```
![Defaults](https://raw.githubusercontent.com/Undre4m/igniculus/master/media/default.png)
```js
const igniculus = require('igniculus')(
{
constants: { mode: 'bold', fg: 'yellow' },
numbers: { mode: 'bold', fg: 'magenta' },
delimitedIdentifiers: { mode: 'bold', fg: 'red' },
standardKeywords: { mode: 'bold', fg: 'blue' }
}
);
igniculus("INSERT INTO [Printers] ([port], [name], [ready], [online], [check]) " +
"VALUES ('lp0', 'Bob Marley', 0, 1, 1)");
```
![Custom Insert](https://raw.githubusercontent.com/Undre4m/igniculus/master/media/simple-insert-custom.png)
```js
const igniculus = require('igniculus');
const options = {
delimitedIdentifiers: {
fg: 'yellow'
},
dataTypes: {
fg: 'magenta',
types: ['VARBINARY']
},
standardKeywords: {
fg: 'red',
keywords: ['CREATE', 'PRIMARY', 'KEY']
},
lesserKeywords: {
mode: 'bold',
fg: 'black',
keywords: ['TABLE', 'NOT', 'NULL']
},
prefix: {
text: '\n'
}
};
igniculus(options);
igniculus.log('CREATE TABLE User (' +
'[username] VARCHAR(20) NOT NULL, ' +
'[password] BINARY(64) NOT NULL, ' +
'[avatar] VARBINARY(MAX), PRIMARY KEY ([username]))');
```
![Custom Create](https://raw.githubusercontent.com/Undre4m/igniculus/master/media/simple-create-custom.png)
```js
const igniculus = require('igniculus');
const log = igniculus({
constants: { fg: 'red' },
delimitedIdentifiers: { mode: 'bold', fg: 'cyan' },
standardKeywords: { fg: 'blue', casing: 'uppercase' },
own: {
_: {
mode: 'bold',
fg: 'white',
regexp: /^/,
transform: '█ '
},
comments: {
regexp: /(-{2}.*)|(\/\*(.|[\r\n])*?\*\/)[\r\n]*/g,
transform: ''
},
UUIDv4s: {
mode: 'bold',
fg: 'black',
regexp: /'[A-F\d]{8}-[A-F\d]{4}-4[A-F\d]{3}-[89AB][A-F\d]{3}-[A-F\d]{12}'/gi,
transform: (uuid) => uuid.replace(/\w{1}/g, 'x')
}
}
});
log("/* May 13th, 2018 | 06:09:28.262 | http://server.local:8000 */" +
"select [username], [password] from Users where [_uuid] = '4072FA1B-D9E7-4F0E-9553-5F2CFFE6CC7A'");
```
![Custom Rules](https://raw.githubusercontent.com/Undre4m/igniculus/v1.4.0-rc/media/simple-query-custom-rules.png)
## Integration
Igniculus' logger is a _drop in_ replacement on any tool that passes the log function either a `string` or `Object` paramater. In the latest case the `toString()` method will be called to obtain a `string` primitive.
### Sequelize
Using igniculus with sequelize is straightforward.
```js
const Sequelize = require('sequelize');
const igniculus = require('igniculus')();
const sequelize = new Sequelize('database', 'username', 'password', {
logging: igniculus
});
```
```js
/* Or add some customizations */
const Sequelize = require('sequelize');
const igniculus = require('igniculus')(
{
constants: { fg: 'red' },
delimitedIdentifiers: { fg: 'yellow' },
dataTypes: { fg: 'red' },
standardKeywords: { fg: 'magenta' },
lesserKeywords: { mode: 'bold', fg: 'black' },
prefix: {
mode: 'bold',
fg: 'white',
replace: /.*?:/,
text: '(Sequelize)'
},
postfix: { text: '\r\n' }
}
);
const sequelize = new Sequelize('database', 'username', 'password', {
logging: igniculus
});
...
sequelize.sync({ logging: igniculus});
```
#### Before
![Before](https://raw.githubusercontent.com/Undre4m/igniculus/master/media/sequelize-without.png)
#### After
![After](https://raw.githubusercontent.com/Undre4m/igniculus/master/media/sequelize-with.png)
## Notes
### Changes
For a full list of changes please refer to the [changelog](https://github.com/Undre4m/igniculus/blob/master/CHANGELOG.md).
### Future Upgrades
#### [v2.0.0 milestone](https://github.com/Undre4m/igniculus/milestone/1)
- Separation of style-related and option-specific configurations **BC**
- Adding and omitting data types and keywords from the predefined sets
- Basic built-in themes
- Easier to read documentation
- Option validation and friendly error detection
## Maintainers
[Lucas Astrada](https://github.com/undre4m)
## License
[MIT](https://github.com/Undre4m/igniculus/blob/master/LICENSE)
+461
View File
@@ -0,0 +1,461 @@
'use strict';
/* SQL-92 standard data types and keywords
* http://www.frontbase.com/docs/5.3.html
*/
const sql92 = {
defaultDataTypes: ['SMALLINT', 'INTEGER', 'INT', 'NUMERIC', 'DECIMAL', 'DEC', 'FLOAT', 'REAL', 'DOUBLE PRECISION', 'CHARACTER', 'CHAR', 'NCHAR', 'VARCHAR', 'BIT', 'DATE', 'TIME', 'TIMESTAMP', 'INTERVAL', 'NATIONAL', 'VARYING', 'TIME ZONE']
};
/* Oracle data types
* https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/Data-Types.html#GUID-7B72E154-677A-4342-A1EA-C74C1EA928E6
*/
const oracle = {
defaultDataTypes: ['VARCHAR2', 'NVARCHAR2', 'NUMBER', 'FLOAT', 'LONG', 'DATE', 'BINARY_FLOAT', 'BINARY_DOUBLE', 'TIMESTAMP', 'INTERVAL', 'RAW', 'ROWID', 'UROWID', 'CHAR', 'NCHAR', 'CLOB', 'NCLOB', 'BLOB', 'BFILE', 'BYTE', 'LOCAL', 'TIME ZONE']
};
/* T-SQL data types and keywords
* https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-2017
*/
const tsql = {
defaultDataTypes: ['BIGINT', 'NUMERIC', 'BIT', 'SMALLINT', 'DECIMAL', 'SMALLMONEY', 'INT', 'TINYINT', 'MONEY', 'FLOAT', 'REAL', 'DATE', 'DATETIMEOFFSET', 'DATETIME2', 'SMALLDATETIME', 'DATETIME', 'TIME', 'CHAR', 'VARCHAR', 'TEXT', 'NCHAR', 'NVARCHAR', 'NTEXT', 'BINARY', 'VARBINARY', 'IMAGE', 'GEOMETRY', 'GEOGRAPHY', 'UNIQUEIDENTIFIER', 'XML']
};
/* PostgreSQL data types and keywords
* https://www.postgresql.org/docs/10/static/datatype.html
*/
const postgresql = {
defaultDataTypes: ['SMALLINT', 'INTEGER', 'BIGINT', 'DECIMAL', 'NUMERIC', 'REAL', 'DOUBLE PRECISION', 'SMALLSERIAL', 'SERIAL', 'BIGSERIAL', 'MONEY', 'CHAR', 'CHARACTER', 'VARCHAR', 'TEXT', 'BYTEA', 'TIMESTAMP', 'TIMESTAMPTZ', 'DATE', 'TIME', 'INTERVAL', 'BOOLEAN', 'ENUM', 'POINT', 'LINE', 'LSEG', 'BOX', 'PATH', 'POLYGON', 'CIRCLE', 'CIDR', 'INET', 'MACADDR', 'MACADDR8', 'BIT', 'UUID', 'XML', 'JSON', 'JSONB', 'TSQUERY', 'TSVECTOR', 'INT4RANGE', 'INT8RANGE', 'NUMRANGE', 'TSRANGE', 'TSTZRANGE', 'DATERANGE', 'ARRAY', 'TIME ZONE']
};
/* MariaDB data types and keywords
* https://mariadb.com/kb/en/library/data-types
*/
const mariadb = {
defaultDataTypes: ['TINYINT', 'BOOLEAN', 'SMALLINT', 'MEDIUMINT', 'INT', 'INTEGER', 'BIGINT', 'DECIMAL', 'DEC', 'NUMERIC', 'FIXED', 'FLOAT', 'DOUBLE', 'DOUBLE PRECISION', 'REAL', 'BIT', 'CHAR', 'VARCHAR', 'BINARY', 'CHAR BYTE', 'VARBINARY', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'LONGBLOB', 'TINYTEXT', 'TEXT', 'MEDIUMTEXT', 'LONGTEXT', 'JSON', 'ENUM', 'SET', 'ROW', 'DATE', 'TIME', 'DATETIME', 'TIMESTAMP', 'YEAR', 'POINT', 'LINESTRING', 'POLYGON', 'MULTIPOINT', 'MULTILINESTRING', 'MULTIPOLYGON', 'GEOMETRYCOLLECTION', 'GEOMETRY']
};
/**
* Creates an array of unique values
* @param {...Array} [arrays]
* @returns {Array}
*/
/* Incompatible with current node version
* Bump version on 2.0 release
*
* function union(...arrays) {
* return [...new Set([].concat(...arrays))];
* }
*/
function union() {
let array = [];
for (let arg of arguments)
for (let item of arg)
if (!~array.indexOf(item))
array.push(item);
return array;
}
/**
* Moves all compound keywords to the front of the array
* @param {string[]} array - Array of keywords to mutate
* @returns {string[]}
*/
function unshiftCompoundKeywords(array) {
for (let i = array.length - 1; i >= 0; i--) {
const word = array[i];
if (/\w+\s\w+/.test(word))
array.unshift(array.splice(i, 1)[0]);
}
return array;
}
const defaultDataTypes = unshiftCompoundKeywords(union(sql92.defaultDataTypes, tsql.defaultDataTypes));
const defaultStandardKeywords = ['ACTION', 'ADD', 'AFTER', 'ALTER', 'AUTHORIZATION', 'BEFORE', 'BEGIN', 'BREAK', 'BY', 'CASCADE', 'CASE', 'CHECK', 'CHECKPOINT', 'CLOSE', 'COLUMN', 'COMMIT', 'CONSTRAINT', 'CONTINUE', 'CREATE', 'CROSS', 'CURSOR', 'DATABASE', 'DECLARE', 'DEFAULT', 'DELETE', 'DISTINCT', 'DROP', 'EACH', 'ELSE', 'ELSEIF', 'END', 'EXCEPT', 'EXEC', 'EXECUTE', 'EXIT', 'FETCH', 'FIRST', 'FOR', 'FOREIGN', 'FROM', 'FULL', 'FUNCTION', 'GO', 'GRANT', 'GROUP', 'HAVING', 'IDENTITY', 'IF', 'INDEX', 'INNER', 'INSERT', 'INTERSECT', 'INTO', 'JOIN', 'KEY', 'LEFT', 'LIMIT', 'LAST', 'LOOP', 'MERGE', 'MODIFY', 'NEXT', 'NO', 'OFFSET', 'ON', 'OPEN', 'ORDER', 'OUTER', 'PRIMARY', 'PROC', 'PROCEDURE', 'REFERENCES', 'RELATIVE', 'REPLACE', 'RETURN', 'RETURNS', 'REVOKE', 'RIGHT', 'ROLLBACK', 'ROW', 'ROWS', 'SAVE', 'SCHEMA', 'SELECT', 'SET', 'TABLE', 'THEN', 'TOP', 'TRAN', 'TRANSACTION', 'TRIGGER', 'TRUNCATE', 'UNION', 'UNIQUE', 'UPDATE', 'USE', 'USING', 'VALUES', 'VIEW', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WITHOUT'];
const defaultLesserKeywords = ['ALL', 'AND', 'ANY', 'AS', 'ASC', 'AVG', 'BETWEEN', 'COLLATE', 'COUNT', 'DESC', 'ESCAPE', 'EXISTS', 'IN', 'IS', 'LIKE', 'MAX', 'MIN', 'NOT', 'NULL', 'OR', 'SOME', 'SUM', 'TO'];
let dataTypes = defaultDataTypes.slice();
let standardKeywords = defaultStandardKeywords.slice();
let lesserKeywords = defaultLesserKeywords.slice();
const ANSIModes = {
reset: '\x1b[0m',
bold: '\x1b[1m',
dim: '\x1b[2m',
italic: '\x1b[3m',
underline: '\x1b[4m',
blink: '\x1b[5m',
inverse: '\x1b[7m',
hidden: '\x1b[8m',
strikethrough: '\x1b[9m'
};
const ANSIColours = {
fg: {
black: '\x1b[30m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m'
},
bg: {
black: '\x1b[40m',
red: '\x1b[41m',
green: '\x1b[42m',
yellow: '\x1b[43m',
blue: '\x1b[44m',
magenta: '\x1b[45m',
cyan: '\x1b[46m',
white: '\x1b[47m'
}
};
const defaults = {
comments: { mode: 'dim', fg: 'white' },
constants: { mode: 'dim', fg: 'red' },
delimitedIdentifiers: { mode: 'dim', fg: 'yellow' },
variables: { mode: 'dim', fg: 'magenta' },
dataTypes: { mode: 'dim', fg: 'green', casing: 'uppercase' },
standardKeywords: { mode: 'dim', fg: 'cyan', casing: 'uppercase' },
lesserKeywords: { mode: 'bold', fg: 'black', casing: 'uppercase' },
prefix: { replace: /.*?: / }
};
let runestone;
/**
* Forge ANSI escape code sequence for text formatting.
* @param {Object} rule - Object that defines the colors to use for formatting a particular rule.
* @returns {string}
*/
function forgeANSISequence(rule) {
let mode, fg, bg;
mode = (rule.mode && ANSIModes[rule.mode]) ? ANSIModes[rule.mode] : '';
fg = (rule.fg && ANSIColours.fg[rule.fg]) ? ANSIColours.fg[rule.fg] : '';
bg = (rule.bg && ANSIColours.bg[rule.bg]) ? ANSIColours.bg[rule.bg] : '';
let ANSISequence = mode + bg + fg;
return ANSISequence;
}
/**
* Remove all ANSI escape code sequences from text.
* @param {string} text - Text piece from which to void all formatting.
* @returns {string}
*/
function voidFormatting(text) {
return text.replace(/\x1b\[\d{1,2}m/g, '');
}
/**
* Highlight syntax of SQL-statments and log to terminal.
* @param {string|Object} text - String of SQL-statements to highlight.
*/
function illumine(text) {
let output,
type = typeof text;
// Coerce entry to string primitive capable of being altered or exit.
if (text && (type === 'string' || type === 'object'))
output = type === 'string' ? text : text.toString();
else
return;
// If a given prefix should be replaced or removed, extract it before any subsequent highlights taint it.
let __prefix;
if (runestone.prefix && runestone.prefix.replace) {
let match = runestone.prefix.replace.exec(output);
if (match) {
__prefix = match[0];
output = output.substr(__prefix.length);
}
}
let __archetypes = {};
if (runestone.own) {
for (const key of Object.keys(runestone.own)) {
const rule = runestone.own[key];
// Extract custom-built archetypes so no subsequent operations alter them. Mark their positions for reinsertion.
__archetypes[key] = output.match(rule.regexp);
if (__archetypes[key] && __archetypes[key].length) {
output = output.replace(rule.regexp, '⥂_' + key + '⥄');
}
}
}
// Extract delimited identifiers so no subsequent operations alter them. Mark their positions for reinsertion.
let __identifiers = output.match(/(\[.*?\]|".*?")/g);
if (__identifiers && __identifiers.length) {
output = output.replace(/(\[.*?\]|".*?")/g, '⇁※↼');
}
// Extract constants so no subsequent operations alter them. Mark their positions for reinsertion.
let __constants = output.match(/('.*?')/g);
if (__constants && __constants.length) {
output = output.replace(/('.*?')/g, '⇝※⇜');
}
// Extract local variables so no subsequent operations alter them. Mark their positions for reinsertion.
let __variables = output.match(/(\B@[@#$_\w\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u017f\u0180-\u024f]*)/g);
if (__variables && __variables.length) {
output = output.replace(/(\B@[@#$_\w\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u017f\u0180-\u024f]*)/g, '↪※↩');
}
// Extract comment sections so no subsequent operations alter them. Mark their positions for reinsertion.
let __comments = output.match(/(-{2}.*)|(\/\*(.|[\r\n])*?\*\/)/g);
if (__comments && __comments.length) {
output = output.replace(/(-{2}.*)|(\/\*(.|[\r\n])*?\*\/)/g, '⥤※⥢');
}
if (runestone.dataTypes && runestone.dataTypes.sequence) {
let regex = new RegExp('\\b' + '(' + dataTypes.join('|') + ')' + '\\b' + '(?![\'"\\]])', 'gi');
output = output.replace(regex, (match, g1) => {
let word = g1;
const casing = runestone.dataTypes.casing;
if (typeof casing === 'string' && (casing === 'lowercase' || casing === 'uppercase'))
word = casing === 'lowercase' ? word.toLowerCase() : word.toUpperCase();
return runestone.dataTypes.sequence + word + ANSIModes.reset;
});
}
if (runestone.standardKeywords && runestone.standardKeywords.sequence) {
let regex = new RegExp('\\b' + '(' + standardKeywords.join('|') + ')' + '\\b' + '(?![\'"\\]])', 'gi');
output = output.replace(regex, (match, g1) => {
let word = g1;
const casing = runestone.standardKeywords.casing;
if (typeof casing === 'string' && (casing === 'lowercase' || casing === 'uppercase'))
word = casing === 'lowercase' ? word.toLowerCase() : word.toUpperCase();
return runestone.standardKeywords.sequence + word + ANSIModes.reset;
});
}
if (runestone.lesserKeywords && runestone.lesserKeywords.sequence) {
let regex = new RegExp('\\b' + '(' + lesserKeywords.join('|') + ')' + '\\b' + '(?![\'"\\]])', 'gi');
output = output.replace(regex, (match, g1) => {
let word = g1;
const casing = runestone.lesserKeywords.casing;
if (typeof casing === 'string' && (casing === 'lowercase' || casing === 'uppercase'))
word = casing === 'lowercase' ? word.toLowerCase() : word.toUpperCase();
return runestone.lesserKeywords.sequence + word + ANSIModes.reset;
});
}
if (runestone.numbers && runestone.numbers.sequence) {
output = output.replace(/((\d+\.{1}){0,1}(\d+)(?![a-z\x1b]))(?!\d)/gi, runestone.numbers.sequence + '$1' + ANSIModes.reset);
}
if (runestone.operators && runestone.operators.sequence) {
output = output.replace(/(\+|-|\*|\/|%|&|\||\^|=|>|<)+/g, runestone.operators.sequence + '$&' + ANSIModes.reset);
}
// If comment sections were found and extracted, reinsert them on the marked positions and cordon off the area for reference.
if (__comments && __comments.length) {
for (let i of __comments) {
// If comment sections were to be formatted, apply the provided style.
if (runestone.comments && runestone.comments.sequence)
output = output.replace('⥤※⥢', runestone.comments.sequence + 'c†s' + i + 'c‡e' + ANSIModes.reset);
else
output = output.replace('⥤※⥢', 'c†s' + i + 'c‡e');
}
}
// If local variables were found and extracted, reinsert them on the marked positions.
if (__variables && __variables.length) {
for (let i of __variables) {
// If local variables were to be formatted, apply the provided style.
if (runestone.variables && runestone.variables.sequence)
output = output.replace('↪※↩', runestone.variables.sequence + i + ANSIModes.reset);
else
output = output.replace('↪※↩', i);
}
}
// If constants were found and extracted, reinsert them on the marked positions.
if (__constants && __constants.length) {
for (let i of __constants) {
// If constants were to be formatted, apply the provided style.
if (runestone.constants && runestone.constants.sequence)
output = output.replace('⇝※⇜', runestone.constants.sequence + i + ANSIModes.reset);
else
output = output.replace('⇝※⇜', i);
}
}
// If delimited identifiers were found and extracted, reinsert them on the marked positions.
if (__identifiers && __identifiers.length) {
for (let i of __identifiers) {
// If delimited identifiers were to be formatted, apply the provided style.
if (runestone.delimitedIdentifiers && runestone.delimitedIdentifiers.sequence)
output = output.replace('⇁※↼', runestone.delimitedIdentifiers.sequence + i + ANSIModes.reset);
else
output = output.replace('⇁※↼', i);
}
}
if (runestone.own) {
for (const key of Object.keys(runestone.own).reverse()) {
const rule = runestone.own[key];
// If custom-built archetypes were found and extracted, reinsert them on the marked positions.
if (__archetypes[key] && __archetypes[key].length) {
for (let i of __archetypes[key]) {
let re = i;
if (typeof rule.transform === 'string')
re = rule.transform;
else if (typeof rule.transform === 'function')
re = rule.transform(i);
// Prevent back-reference
re = re.replace(/\$/g,'$$$');
// If custom-built archetypes were to be formatted, apply the provided style.
if (rule && rule.sequence)
output = output.replace('⥂_' + key + '⥄', rule.sequence + re + ANSIModes.reset);
else
output = output.replace('⥂_' + key + '⥄', re);
}
}
}
}
// Constants are to be formatted as a whole and no other format should exist inside them. Void any that could have been applied.
output = output.replace(/('.*?')/g, (match) => {
return voidFormatting(match);
});
// Comment sections are to be formatted as a whole and no other format should exist inside them. Void any that could have been applied and remove cordon.
output = output.replace(/(c†s)((-{2}.*)|(\/\*(.|[\r\n])*?\*\/))(c‡e)/g, (match, p1, p2) => {
return voidFormatting(p2);
});
// If the given prefix was found and a replacement pattern was provided, substitute it.
if (__prefix && typeof runestone.prefix.text === 'string') {
output = __prefix + output;
output = output.replace(__prefix, runestone.prefix.sequence + runestone.prefix.text + ANSIModes.reset);
}
// If only the prefix text was provided, append it.
else if (runestone.prefix && runestone.prefix.text && !runestone.prefix.replace) {
output = runestone.prefix.sequence + runestone.prefix.text + ANSIModes.reset + output;
}
if (runestone.postfix && runestone.postfix.text) {
output = output + runestone.postfix.sequence + runestone.postfix.text + ANSIModes.reset;
}
return runestone.output(output);
}
/**
* Create logger.
* @param {any} [options] - Custom format rules.
* @returns {function} - Syntax highlighter and logging function.
*/
function igniculus(options) {
/* Draft all format sequences from the provided or default
* configuration and save them.
*/
runestone = options || defaults;
if (runestone.comments) {
runestone.comments.sequence = forgeANSISequence(runestone.comments);
}
if (runestone.constants) {
runestone.constants.sequence = forgeANSISequence(runestone.constants);
}
if (runestone.delimitedIdentifiers) {
runestone.delimitedIdentifiers.sequence = forgeANSISequence(runestone.delimitedIdentifiers);
}
if (runestone.numbers) {
runestone.numbers.sequence = forgeANSISequence(runestone.numbers);
}
if (runestone.operators) {
runestone.operators.sequence = forgeANSISequence(runestone.operators);
}
if (runestone.variables) {
runestone.variables.sequence = forgeANSISequence(runestone.variables);
}
if (runestone.dataTypes) {
if (Array.isArray(runestone.dataTypes.types))
dataTypes = runestone.dataTypes.types;
else
dataTypes = defaultDataTypes.slice();
runestone.dataTypes.sequence = forgeANSISequence(runestone.dataTypes);
}
if (runestone.standardKeywords) {
if (Array.isArray(runestone.standardKeywords.keywords))
standardKeywords = runestone.standardKeywords.keywords;
else
standardKeywords = defaultStandardKeywords.slice();
runestone.standardKeywords.sequence = forgeANSISequence(runestone.standardKeywords);
}
if (runestone.lesserKeywords) {
if (Array.isArray(runestone.lesserKeywords.keywords))
lesserKeywords = runestone.lesserKeywords.keywords;
else
lesserKeywords = defaultLesserKeywords.slice();
runestone.lesserKeywords.sequence = forgeANSISequence(runestone.lesserKeywords);
}
if (runestone.prefix) {
runestone.prefix.sequence = forgeANSISequence(runestone.prefix);
/* If prefix should replace a given pattern and that pattern is a string,
* escape it so it can be passed to the RegExp constructor.
*/
if (runestone.prefix.replace && typeof runestone.prefix.replace === 'string') {
runestone.prefix.replace = runestone.prefix.replace.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
runestone.prefix.replace = new RegExp('^' + runestone.prefix.replace, 'i');
}
}
if (runestone.postfix) {
runestone.postfix.sequence = forgeANSISequence(runestone.postfix);
}
if (runestone.own) {
for (const key of Object.keys(runestone.own)) {
const rule = runestone.own[key];
rule.sequence = forgeANSISequence(rule);
}
}
if (typeof runestone.output !== 'function') {
runestone.output = console.log;
}
return illumine;
}
module.exports = igniculus;
module.exports.log = illumine;
+46
View File
@@ -0,0 +1,46 @@
{
"name": "igniculus",
"version": "1.5.0",
"description": "SQL Syntax Highlighter and Logger. Unadorned and customizable.",
"keywords": [
"ansi",
"color",
"console",
"format",
"highlight",
"log",
"sql",
"sequel",
"style",
"syntax",
"terminal"
],
"main": "index.js",
"scripts": {
"test": "ava test",
"eslint": "eslint **/*.js",
"coverage": "nyc --reporter=text ava test && rimraf .nyc_output",
"nyc": "nyc ava --fail-fast test",
"travis": "npm run eslint && npm run nyc"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Undre4m/igniculus.git"
},
"author": "Lucas Astrada <astrada.sis@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/Undre4m/igniculus/issues"
},
"homepage": "https://github.com/Undre4m/igniculus#readme",
"engines": {
"node": ">=4.0.0"
},
"devDependencies": {
"ava": "^0.25.0",
"dedent": "^0.7.0",
"eslint": "^4.19.1",
"nyc": "^11.7.3",
"rimraf": "^2.6.2"
}
}