tusktsk
Version:
TuskTsk - The Freedom Configuration Language. Query databases, use any syntax, never bow to any king!
213 lines (180 loc) • 5.94 kB
JavaScript
/**
* TuskLang for JavaScript/Node.js
* ================================
* The freedom configuration language
*
* "We don't bow to any king" - Use any syntax you prefer!
*/
const TuskLangEnhanced = require('./tsk-enhanced.js');
// Database adapters
const adapters = {
sqlite: null,
postgres: null,
mysql: null,
mongodb: null,
redis: null
};
// Try to load optional database adapters
try {
adapters.sqlite = require('./adapters/sqlite.js');
} catch (e) {}
try {
adapters.postgres = require('./adapters/postgres.js');
} catch (e) {}
try {
adapters.mysql = require('./adapters/mysql.js');
} catch (e) {}
try {
adapters.mongodb = require('./adapters/mongodb.js');
} catch (e) {}
try {
adapters.redis = require('./adapters/redis.js');
} catch (e) {}
/**
* Main TuskLang class
*/
class TuskLang {
constructor(options = {}) {
this.parser = new TuskLangEnhanced();
// Set database adapter if provided
if (options.database) {
this.setDatabase(options.database);
}
}
/**
* Parse TuskLang content
* @param {string} content - TuskLang formatted content
* @returns {object} Parsed configuration object
*/
parse(content) {
return this.parser.parse(content);
}
/**
* Parse TuskLang file
* @param {string} filename - Path to .tsk file
* @returns {object} Parsed configuration object
*/
parseFile(filename) {
const fs = require('fs');
const content = fs.readFileSync(filename, 'utf8');
return this.parse(content);
}
/**
* Parse TuskLang file asynchronously
* @param {string} filename - Path to .tsk file
* @returns {Promise<object>} Parsed configuration object
*/
async parseFileAsync(filename) {
const fs = require('fs').promises;
const content = await fs.readFile(filename, 'utf8');
return this.parse(content);
}
/**
* Set database adapter
* @param {object} config - Database configuration
*/
setDatabase(config) {
const { type, ...options } = config;
if (!type) {
throw new Error('Database type is required');
}
const AdapterClass = adapters[type];
if (!AdapterClass) {
throw new Error(`Database adapter '${type}' not found. Install the required package.`);
}
const adapter = new AdapterClass(options);
this.parser.setDatabaseAdapter(adapter);
}
/**
* Convert object to TuskLang format
* @param {object} data - Data to serialize
* @returns {string} TuskLang formatted string
*/
static stringify(data, options = {}) {
const indent = options.indent || ' ';
const style = options.style || 'mixed'; // 'flat', 'sections', 'objects', 'mixed'
let output = '# Generated by TuskLang\n';
output += `# ${new Date().toISOString()}\n\n`;
// Serialize based on style preference
output += TuskLang.serializeValue(data, 0, indent, style);
return output;
}
/**
* Serialize a value to TuskLang format
*/
static serializeValue(value, depth = 0, indent = ' ', style = 'mixed') {
const spacing = indent.repeat(depth);
if (value === null) return 'null';
if (typeof value === 'boolean') return String(value);
if (typeof value === 'number') return String(value);
if (typeof value === 'string') {
// Quote if contains special characters
if (/[\s"'{}[\]:,#]/.test(value) || value === 'true' || value === 'false' || value === 'null') {
return `"${value.replace(/"/g, '\\"')}"`;
}
return value;
}
if (Array.isArray(value)) {
if (value.length === 0) return '[]';
if (value.length <= 3 && value.every(v => typeof v !== 'object')) {
// Inline small arrays
return '[' + value.map(v => TuskLang.serializeValue(v)).join(', ') + ']';
}
// Multiline arrays
let output = '[\n';
for (const item of value) {
output += spacing + indent + TuskLang.serializeValue(item, depth + 1, indent, style) + '\n';
}
output += spacing + ']';
return output;
}
if (typeof value === 'object') {
const keys = Object.keys(value);
if (keys.length === 0) return '{}';
let output = '';
// Choose style based on preference and content
const useSection = style === 'sections' || (style === 'mixed' && depth === 0);
const useBraces = style === 'objects' || (style === 'mixed' && depth > 0);
if (useSection && depth === 0) {
// Use [section] style for top level
for (const key of keys) {
if (typeof value[key] === 'object' && !Array.isArray(value[key])) {
output += `\n[${key}]\n`;
const subKeys = Object.keys(value[key]);
for (const subKey of subKeys) {
output += `${subKey}: ${TuskLang.serializeValue(value[key][subKey], depth + 1, indent, style)}\n`;
}
} else {
output += `${key}: ${TuskLang.serializeValue(value[key], depth + 1, indent, style)}\n`;
}
}
} else if (useBraces) {
// Use {} style
output = '{\n';
for (const key of keys) {
output += spacing + indent + `${key}: ${TuskLang.serializeValue(value[key], depth + 1, indent, style)}\n`;
}
output += spacing + '}';
} else {
// Flat style
for (const key of keys) {
output += spacing + `${key}: ${TuskLang.serializeValue(value[key], depth + 1, indent, style)}\n`;
}
}
return output.trim();
}
return String(value);
}
/**
* Get available database adapters
*/
static getAvailableAdapters() {
return Object.keys(adapters).filter(key => adapters[key] !== null);
}
}
// Export the main class
module.exports = TuskLang;
// Also export the parser directly
module.exports.TuskLangParser = TuskLangEnhanced;
// Export adapter classes
module.exports.adapters = adapters;