diff --git a/helper/assetUtils.js b/helper/assetUtils.js
deleted file mode 100644
index 2155549..0000000
--- a/helper/assetUtils.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export function getAssetVariables() {
-
-}
\ No newline at end of file
diff --git a/helper/childRegistrationUtils.js b/helper/childRegistrationUtils.js
deleted file mode 100644
index a7181a0..0000000
--- a/helper/childRegistrationUtils.js
+++ /dev/null
@@ -1,243 +0,0 @@
-// ChildRegistrationUtils.js
-class ChildRegistrationUtils {
- constructor(mainClass) {
- this.mainClass = mainClass; // Reference to the main class
- this.logger = mainClass.logger;
- }
-
- async registerChild(child, positionVsParent) {
- const { softwareType } = child.config.functionality;
- const { name, id, unit } = child.config.general;
- const { type = "", subType = "" } = child.config.asset || {};
- const emitter = child.emitter;
-
- //define position vs parent in child
- child.positionVsParent = positionVsParent;
- child.parent = this.mainClass;
-
- if (!this.mainClass.child) this.mainClass.child = {};
- if (!this.mainClass.child[softwareType])
- this.mainClass.child[softwareType] = {};
- if (!this.mainClass.child[softwareType][type])
- this.mainClass.child[softwareType][type] = {};
- if (!this.mainClass.child[softwareType][type][subType])
- this.mainClass.child[softwareType][type][subType] = {};
-
- // Use an array to handle multiple subtypes
- if (!Array.isArray(this.mainClass.child[softwareType][type][subType])) {
- this.mainClass.child[softwareType][type][subType] = [];
- }
-
- // Update the child in the cloud when available and supply the new child on base of tagcode OLIFANT WE NEED TO FIX THIS SO ITS DYNAMIC!
- /*
- try{
- const url = "https://pimmoerman.nl/rdlab/tagcode.app/v2.1/api/asset/create_asset.php?";
- const TagCode = child.config.asset.tagCode;
- //console.log(`Register child => ${TagCode}`);
- const completeURL = url + `asset_product_model_id=1&asset_product_model_uuid=123456789&asset_name=AssetNaam&asset_description=Beschrijving&asset_status=actief&asset_profile_id=1&asset_location_id=1&asset_process_id=11&asset_tag_number=${TagCode}&child_assets=[L6616]`;
-
- await fetch(completeURL, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- }
- });
-
- }catch(e){
- console.log("Error saving assetID and tagnumber", e);
- }*/
-
- // Push the new child to the array of the mainclass so we can track the childs
- this.mainClass.child[softwareType][type][subType].push({
- name,
- id,
- unit,
- emitter,
- });
-
- //then connect the child depending on the type subtype etc..
- this.connectChild(
- id,
- softwareType,
- emitter,
- type,
- child,
- subType,
- positionVsParent
- );
- }
-
- connectChild(
- id,
- softwareType,
- emitter,
- type,
- child,
- subType,
- positionVsParent
- ) {
- this.logger.debug(
- `Connecting child id=${id}: desc=${softwareType}, type=${type},subType=${subType}, position=${positionVsParent}`
- );
-
- switch (softwareType) {
- case "measurement":
- this.logger.debug(
- `Registering measurement child: ${id} with type=${type}`
- );
- this.connectMeasurement(child, subType, positionVsParent);
- break;
-
- case "machine":
- this.logger.debug(`Registering complete machine child: ${id}`);
- this.connectMachine(child);
- break;
-
- case "valve":
- this.logger.debug(`Registering complete valve child: ${id}`);
- this.connectValve(child);
- break;
-
- case "actuator":
- this.logger.debug(`Registering linear actuator child: ${id}`);
- this.connectActuator(child,positionVsParent);
- break;
-
- default:
- this.logger.error(`Child registration unrecognized desc: ${desc}`);
- this.logger.error(`Unrecognized softwareType: ${softwareType}`);
- }
- }
-
- connectMeasurement(child, subType, position) {
- this.logger.debug(
- `Connecting measurement child: ${subType} with position=${position}`
- );
-
- // Check if subType is valid
- if (!subType) {
- this.logger.error(`Invalid subType for measurement: ${subType}`);
- return;
- }
-
- // initialize the measurement to a number - logging each step for debugging
- try {
- this.logger.debug(
- `Initializing measurement: ${subType}, position: ${position} value: 0`
- );
- const typeResult = this.mainClass.measurements.type(subType);
- const variantResult = typeResult.variant("measured");
- const positionResult = variantResult.position(position);
- positionResult.value(0);
-
- this.logger.debug(
- `Subscribing on mAbs event for measurement: ${subType}, position: ${position}`
- );
- // Listen for the mAbs event and update the measurement
-
- this.logger.debug(
- `Successfully initialized measurement: ${subType}, position: ${position}`
- );
- } catch (error) {
- this.logger.error(`Failed to initialize measurement: ${error.message}`);
- return;
- }
-
- child.emitter.on("mAbs", (value) => {
- // Use the same method chaining approach that worked during initialization
- this.mainClass.measurements
- .type(subType)
- .variant("measured")
- .position(position)
- .value(value);
- this.mainClass.updateMeasurement("measured", subType, value, position);
- //this.logger.debug(`--------->>>>>>>>>Updated measurement: ${subType}, value: ${value}, position: ${position}`);
- });
- }
-
- connectMachine(machine) {
- if (!machine) {
- this.logger.error("Invalid machine provided.");
- return;
- }
-
- const machineId = Object.keys(this.mainClass.machines).length + 1;
- this.mainClass.machines[machineId] = machine;
-
- this.logger.info(
- `Setting up pressureChange listener for machine ${machineId}`
- );
-
- machine.emitter.on("pressureChange", () =>
- this.mainClass.handlePressureChange(machine)
- );
-
- //update of child triggers the handler
- this.mainClass.handleChildChange();
-
- this.logger.info(`Machine ${machineId} registered successfully.`);
- }
-
- connectValve(valve) {
- if (!valve) {
- this.logger.warn("Invalid valve provided.");
- return;
- }
- const valveId = Object.keys(this.mainClass.valves).length + 1;
- this.mainClass.valves[valveId] = valve; // Gooit valve object in de valves attribute met valve objects
-
- valve.state.emitter.on("positionChange", (data) => {
- //ValveGroupController abboneren op klepstand verandering
- this.mainClass.logger.debug(`Position change of valve detected: ${data}`);
- this.mainClass.calcValveFlows();
- }); //bepaal nieuwe flow per valve
- valve.emitter.on("deltaPChange", () => {
- this.mainClass.logger.debug("DeltaP change of valve detected");
- this.mainClass.calcMaxDeltaP();
- }); //bepaal nieuwe max deltaP
-
- this.logger.info(`Valve ${valveId} registered successfully.`);
- }
-
- connectActuator(actuator, positionVsParent) {
- if (!actuator) {
- this.logger.warn("Invalid actuator provided.");
- return;
- }
-
- //Special case gateGroupControl
- if (
- this.mainClass.config.functionality.softwareType == "gateGroupControl"
- ) {
- if (Object.keys(this.mainClass.actuators).length < 2) {
- if (positionVsParent == "downstream") {
- this.mainClass.actuators[0] = actuator;
- }
-
- if (positionVsParent == "upstream") {
- this.mainClass.actuators[1] = actuator;
- }
- //define emitters
- actuator.state.emitter.on("positionChange", (data) => {
- this.mainClass.logger.debug(`Position change of actuator detected: ${data}`);
- this.mainClass.eventUpdate();
- });
-
- //define emitters
- actuator.state.emitter.on("stateChange", (data) => {
- this.mainClass.logger.debug(`State change of actuator detected: ${data}`);
- this.mainClass.eventUpdate();
- });
-
- } else {
- this.logger.error(
- "Too many actuators registered. Only two are allowed."
- );
- }
- }
- }
-
- //wanneer hij deze ontvangt is deltaP van een van de valves veranderd (kan ook zijn niet child zijn, maar dat maakt niet uit)
-}
-
-module.exports = ChildRegistrationUtils;
diff --git a/helper/configUtils.js b/helper/configUtils.js
deleted file mode 100644
index e81d1d5..0000000
--- a/helper/configUtils.js
+++ /dev/null
@@ -1,94 +0,0 @@
-/**
- * @file configUtils.js
- *
- * Permission is hereby granted to any person obtaining a copy of this software
- * and associated documentation files (the "Software"), to use it for personal
- * or non-commercial purposes, with the following restrictions:
- *
- * 1. **No Copying or Redistribution**: The Software or any of its parts may not
- * be copied, merged, distributed, sublicensed, or sold without explicit
- * prior written permission from the author.
- *
- * 2. **Commercial Use**: Any use of the Software for commercial purposes requires
- * a valid license, obtainable only with the explicit consent of the author.
- *
- * 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.
- *
- * Ownership of this code remains solely with the original author. Unauthorized
- * use of this Software is strictly prohibited.
- *
- * @summary Utility for managing and validating configuration values.
- * @description Utility for managing and validating configuration values.
- * @module ConfigUtils
- * @requires ValidationUtils
- * @requires Logger
- * @exports ConfigUtils
- * @version 0.1.0
- * @since 0.1.0
- */
-
-
-const ValidationUtils = require("./validationUtils");
-const Logger = require("./logger");
-
-class ConfigUtils {
- constructor(defaultConfig, IloggerEnabled , IloggerLevel) {
- const loggerEnabled = IloggerEnabled || true;
- const loggerLevel = IloggerLevel || "warn";
- this.logger = new Logger(loggerEnabled, loggerLevel, 'ConfigUtils');
- this.defaultConfig = defaultConfig;
- this.validationUtils = new ValidationUtils(loggerEnabled, loggerLevel);
- }
-
- // Initialize configuration
- initConfig(config) {
- this.logger.info("Initializing configuration...");
-
- // Validate the provided configuration
- const validatedConfig = this.validationUtils.validateSchema(config, this.defaultConfig, this.defaultConfig.functionality.softwareType.default);
-
- this.logger.info("Configuration initialized successfully.");
-
- return validatedConfig;
- }
-
-
- // Update configuration
- updateConfig(currentConfig, newConfig) {
- this.logger.info("Updating configuration...");
-
- // Merge 2 configs and validate the result
- const mergedConfig = this.mergeObjects(currentConfig, newConfig);
-
- // Merge current configuration with new values
- const updatedConfig = this.validationUtils.validateSchema(mergedConfig, this.defaultConfig, this.defaultConfig.functionality.softwareType.default);
-
- this.logger.info("Configuration updated successfully.");
- return updatedConfig;
- }
-
- // loop through objects and merge them obj1 will be updated with obj2 values
- mergeObjects(obj1, obj2) {
- for (let key in obj2) {
- if (obj2.hasOwnProperty(key)) {
- if (typeof obj2[key] === 'object') {
- if (!obj1[key]) {
- obj1[key] = {};
- }
- this.mergeObjects(obj1[key], obj2[key]);
- } else {
- obj1[key] = obj2[key];
- }
- }
- }
- return obj1;
- }
-}
-
-module.exports = ConfigUtils;
diff --git a/helper/logger.js b/helper/logger.js
deleted file mode 100644
index 8b4f696..0000000
--- a/helper/logger.js
+++ /dev/null
@@ -1,57 +0,0 @@
-class Logger {
- constructor(logging = true, logLevel = 'debug', nameModule = 'N/A') {
- this.logging = logging; // Boolean flag to enable/disable logging
- this.logLevel = logLevel; // Default log level: 'debug', 'info', 'warn', 'error'
- this.levels = ['debug', 'info', 'warn', 'error']; // Log levels in order of severity
- this.nameModule = nameModule; // Name of the module that uses the logger
- }
-
- // Helper function to check if a log message should be displayed based on current log level
- shouldLog(level) {
- return this.levels.indexOf(level) >= this.levels.indexOf(this.logLevel);
- }
-
- // Log a debug message
- debug(message) {
- if (this.logging && this.shouldLog('debug')) {
- console.debug(`[DEBUG] -> ${this.nameModule}: ${message}`);
- }
- }
-
- // Log an info message
- info(message) {
- if (this.logging && this.shouldLog('info')) {
- console.info(`[INFO] -> ${this.nameModule}: ${message}`);
- }
- }
-
- // Log a warning message
- warn(message) {
- if (this.logging && this.shouldLog('warn')) {
- console.warn(`[WARN] -> ${this.nameModule}: ${message}`);
- }
- }
-
- // Log an error message
- error(message) {
- if (this.logging && this.shouldLog('error')) {
- console.error(`[ERROR] -> ${this.nameModule}: ${message}`);
- }
- }
-
- // Set the log level dynamically
- setLogLevel(level) {
- if (this.levels.includes(level)) {
- this.logLevel = level;
- } else {
- console.error(`[ERROR ${nameModule}]: Invalid log level: ${level}`);
- }
- }
-
- // Toggle the logging on or off
- toggleLogging() {
- this.logging = !this.logging;
- }
- }
-
- module.exports = Logger;
\ No newline at end of file
diff --git a/helper/measurements/Measurement.js b/helper/measurements/Measurement.js
deleted file mode 100644
index f9882b5..0000000
--- a/helper/measurements/Measurement.js
+++ /dev/null
@@ -1,187 +0,0 @@
-// Add unit conversion support
-const convertModule = require('../../../convert/dependencies/index');
-
-class Measurement {
- constructor(type, variant, position, windowSize) {
- this.type = type; // e.g. 'pressure', 'flow', etc.
- this.variant = variant; // e.g. 'predicted' or 'measured', etc..
- this.position = position; // Downstream or upstream of parent object
- this.windowSize = windowSize; // Rolling window size
-
- // Place all data inside an array
- this.values = []; // Array to store all values
- this.timestamps = []; // Array to store all timestamps
-
- // Unit tracking
- this.unit = null; // Current unit of measurement
-
- // For tracking differences if this is a calculated difference measurement
- this.isDifference = false;
- this.sourcePositions = [];
- }
-
- // -- Updater methods --
- setType(type) {
- this.type = type;
- return this;
- }
-
- setVariant(variant) {
- this.variant = variant;
- return this;
- }
-
- setPosition(position) {
- this.position = position;
- return this;
- }
-
- setValue(value, timestamp = Date.now()) {
- /*
- if (value === undefined || value === null) {
- value = null ;
- //throw new Error('Value cannot be null or undefined');
- }
- */
-
- //shift the oldest value
- if(this.values.length >= this.windowSize){
- this.values.shift();
- this.timestamps.shift();
- }
-
- //push the new value
- this.values.push(value);
- this.timestamps.push(timestamp);
-
- return this;
- }
-
- setUnit(unit) {
- this.unit = unit;
- return this;
- }
-
- // -- Getter methods --
- getCurrentValue() {
- if (this.values.length === 0) return null;
- return this.values[this.values.length - 1];
- }
-
- getAverage() {
- if (this.values.length === 0) return null;
- const sum = this.values.reduce((acc, val) => acc + val, 0);
- return sum / this.values.length;
- }
-
- getMin() {
- if (this.values.length === 0) return null;
- return Math.min(...this.values);
- }
-
- getMax() {
- if (this.values.length === 0) return null;
- return Math.max(...this.values);
- }
-
- getAllValues() {
- return {
- values: [...this.values],
- timestamps: [...this.timestamps],
- unit: this.unit
- };
- }
-
- // -- Position-based methods --
-
- // Create a new measurement that is the difference between two positions
- static createDifference(upstreamMeasurement, downstreamMeasurement) {
- console.log('hello:');
- if (upstreamMeasurement.type !== downstreamMeasurement.type ||
- upstreamMeasurement.variant !== downstreamMeasurement.variant) {
- throw new Error('Cannot calculate difference between different measurement types or variants');
- }
-
- // Ensure units match
- let downstream = downstreamMeasurement;
- if (upstreamMeasurement.unit && downstream.unit &&
- upstreamMeasurement.unit !== downstream.unit) {
- downstream = downstream.convertTo(upstreamMeasurement.unit);
- }
-
- // Create a new difference measurement
- const diffMeasurement = new Measurement(
- upstreamMeasurement.type,
- upstreamMeasurement.variant,
- 'difference',
- Math.min(upstreamMeasurement.windowSize, downstream.windowSize)
- );
-
- // Mark as a difference measurement and keep track of sources
- diffMeasurement.isDifference = true;
- diffMeasurement.sourcePositions = ['upstream', 'downstream'];
-
- // Calculate all differences where timestamps match
- const upValues = upstreamMeasurement.getAllValues();
- const downValues = downstream.getAllValues();
-
- // Match timestamps and calculate differences
- for (let i = 0; i < upValues.timestamps.length; i++) {
- const upTimestamp = upValues.timestamps[i];
- const downIndex = downValues.timestamps.indexOf(upTimestamp);
-
- if (downIndex !== -1) {
-
- const diff = upValues.values[i] - downValues.values[downIndex];
- diffMeasurement.setValue(diff, upTimestamp);
- }
- }
-
- diffMeasurement.setUnit(upstreamMeasurement.unit);
-
- return diffMeasurement;
- }
-
- // -- Additional getter methods --
- getLatestTimestamp() {
- if (this.timestamps.length === 0) return null;
- return this.timestamps[this.timestamps.length - 1];
- }
-
- getValueAtTimestamp(timestamp) {
- const index = this.timestamps.indexOf(timestamp);
- if (index === -1) return null;
- return this.values[index];
- }
-
- // -- Unit conversion methods --
- convertTo(targetUnit) {
- if (!this.unit) {
- throw new Error('Current unit not set, cannot convert');
- }
-
- try {
- const convertedValues = this.values.map(value =>
- convertModule.convert(value).from(this.unit).to(targetUnit)
- );
-
- const newMeasurement = new Measurement(
- this.type,
- this.variant,
- this.position,
- this.windowSize
- );
-
- // Copy values and timestamps
- newMeasurement.values = convertedValues;
- newMeasurement.timestamps = [...this.timestamps];
- newMeasurement.unit = targetUnit;
-
- return newMeasurement;
- } catch (error) {
- throw new Error(`Unit conversion failed: ${error.message}`);
- }
- }
-}
-
-module.exports = Measurement;
diff --git a/helper/measurements/MeasurementBuilder.js b/helper/measurements/MeasurementBuilder.js
deleted file mode 100644
index af95d05..0000000
--- a/helper/measurements/MeasurementBuilder.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const Measurement = require('./Measurement');
-
-class MeasurementBuilder {
- constructor() {
- this.type = null;
- this.variant = null;
- this.position = null;
- this.windowSize = 10; // Default window size
- }
-
- // e.g. 'pressure', 'flow', etc.
- setType(type) {
- this.type = type;
- return this;
- }
-
- // e.g. 'predicted' or 'measured', etc..
- setVariant(variant) {
- this.variant = variant;
- return this;
- }
-
- // Downstream or upstream of parent object
- setPosition(position) {
- this.position = position;
- return this;
- }
-
- // default size of the data that gets tracked
- setWindowSize(windowSize) {
- this.windowSize = windowSize;
- return this;
- }
-
- build() {
- // Validate required fields
- if (!this.type) {
- throw new Error('Measurement type is required');
- }
- if (!this.variant) {
- throw new Error('Measurement variant is required');
- }
- if (!this.position) {
- throw new Error('Measurement position is required');
- }
-
- return new Measurement(
- this.type,
- this.variant,
- this.position,
- this.windowSize
- );
- }
-}
-
-module.exports = MeasurementBuilder;
diff --git a/helper/measurements/MeasurementContainer.js b/helper/measurements/MeasurementContainer.js
deleted file mode 100644
index 95cf168..0000000
--- a/helper/measurements/MeasurementContainer.js
+++ /dev/null
@@ -1,200 +0,0 @@
-const MeasurementBuilder = require('./MeasurementBuilder');
-
-class MeasurementContainer {
- constructor(options = {}, logger) {
- this.logger = logger;
- this.measurements = {};
- this.windowSize = options.windowSize || 10; // Default window size
-
- // For chaining context
- this._currentType = null;
- this._currentVariant = null;
- this._currentPosition = null;
- }
-
- // Chainable methods
- type(typeName) {
- this._currentType = typeName;
- this._currentVariant = null;
- this._currentPosition = null;
- return this;
- }
-
- variant(variantName) {
- if (!this._currentType) {
- throw new Error('Type must be specified before variant');
- }
- this._currentVariant = variantName;
- this._currentPosition = null;
- return this;
- }
-
- position(positionName) {
- if (!this._currentVariant) {
- throw new Error('Variant must be specified before position');
- }
- this._currentPosition = positionName;
- return this;
- }
-
- // Core methods that complete the chain
- value(val, timestamp = Date.now()) {
- if (!this._ensureChainIsValid()) return this;
-
- const measurement = this._getOrCreateMeasurement();
- measurement.setValue(val, timestamp);
- return this;
- }
-
- unit(unitName) {
- if (!this._ensureChainIsValid()) return this;
-
- const measurement = this._getOrCreateMeasurement();
- measurement.setUnit(unitName);
- return this;
- }
-
- // Terminal operations - get data out
- get() {
- if (!this._ensureChainIsValid()) return null;
- return this._getOrCreateMeasurement();
- }
-
- getCurrentValue() {
- const measurement = this.get();
- return measurement ? measurement.getCurrentValue() : null;
- }
-
- getAverage() {
- const measurement = this.get();
- return measurement ? measurement.getAverage() : null;
- }
-
- getMin() {
- const measurement = this.get();
- return measurement ? measurement.getMin() : null;
- }
-
- getMax() {
- const measurement = this.get();
- return measurement ? measurement.getMax() : null;
- }
-
- getAllValues() {
- const measurement = this.get();
- return measurement ? measurement.getAllValues() : null;
- }
-
- // Difference calculations between positions
- difference() {
- if (!this._currentType || !this._currentVariant) {
- throw new Error('Type and variant must be specified for difference calculation');
- }
-
- // Save position to restore chain state after operation
- const savedPosition = this._currentPosition;
-
- // Get upstream measurement
- this._currentPosition = 'upstream';
- const upstream = this.get();
-
- // Get downstream measurement
- this._currentPosition = 'downstream';
- const downstream = this.get();
-
- // Restore chain state
- this._currentPosition = savedPosition;
-
- if (!upstream || !downstream || upstream.values.length === 0 || downstream.values.length === 0) {
- return null;
- }
-
- // Ensure units match
- let downstreamForCalc = downstream;
- if (upstream.unit && downstream.unit && upstream.unit !== downstream.unit) {
- try {
- downstreamForCalc = downstream.convertTo(upstream.unit);
- } catch (error) {
- if (this.logger) {
- this.logger.error(`Unit conversion failed: ${error.message}`);
- }
- return null;
- }
- }
-
- return {
- value: downstreamForCalc.getCurrentValue() - upstream.getCurrentValue() ,
- avgDiff: downstreamForCalc.getAverage() - upstream.getAverage() ,
- unit: upstream.unit
- };
- }
-
- // Helper methods
- _ensureChainIsValid() {
- if (!this._currentType || !this._currentVariant || !this._currentPosition) {
- if (this.logger) {
- this.logger.error('Incomplete measurement chain, required: type, variant, and position');
- }
- return false;
- }
- return true;
- }
-
- _getOrCreateMeasurement() {
- // Initialize nested structure if needed
- if (!this.measurements[this._currentType]) {
- this.measurements[this._currentType] = {};
- }
-
- if (!this.measurements[this._currentType][this._currentVariant]) {
- this.measurements[this._currentType][this._currentVariant] = {};
- }
-
- if (!this.measurements[this._currentType][this._currentVariant][this._currentPosition]) {
- this.measurements[this._currentType][this._currentVariant][this._currentPosition] =
- new MeasurementBuilder()
- .setType(this._currentType)
- .setVariant(this._currentVariant)
- .setPosition(this._currentPosition)
- .setWindowSize(this.windowSize)
- .build();
- }
-
- return this.measurements[this._currentType][this._currentVariant][this._currentPosition];
- }
-
- // Additional utility methods
- getTypes() {
- return Object.keys(this.measurements);
- }
-
- getVariants() {
- if (!this._currentType) {
- throw new Error('Type must be specified before listing variants');
- }
- return this.measurements[this._currentType] ?
- Object.keys(this.measurements[this._currentType]) : [];
- }
-
- getPositions() {
- if (!this._currentType || !this._currentVariant) {
- throw new Error('Type and variant must be specified before listing positions');
- }
-
- if (!this.measurements[this._currentType] ||
- !this.measurements[this._currentType][this._currentVariant]) {
- return [];
- }
-
- return Object.keys(this.measurements[this._currentType][this._currentVariant]);
- }
-
- clear() {
- this.measurements = {};
- this._currentType = null;
- this._currentVariant = null;
- this._currentPosition = null;
- }
-}
-
-module.exports = MeasurementContainer;
diff --git a/helper/measurements/README.md b/helper/measurements/README.md
deleted file mode 100644
index b5f16ae..0000000
--- a/helper/measurements/README.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# Measurement System Documentation
-
-This system provides a flexible way to store, retrieve, and analyze measurement data using a chainable API.
-
-## Basic Usage
-
-```javascript
-const { MeasurementContainer } = require('./index');
-const container = new MeasurementContainer({ windowSize: 20 });
-
-// Set values
-container.type('pressure').variant('measured').position('upstream').value(100).unit('psi');
-
-// Get values
-const upstreamPressure = container.type('pressure').variant('measured').position('upstream').getCurrentValue();
-console.log(`Upstream pressure: ${upstreamPressure}`);
-```
-
-## Chainable API Methods
-
-### Setting Context
-- `type(typeName)` - Set the measurement type (pressure, flow, etc.)
-- `variant(variantName)` - Set the variant (measured, predicted, etc.)
-- `position(positionName)` - Set the position (upstream, downstream, etc.)
-
-### Setting Data
-- `value(val, [timestamp])` - Add a value with optional timestamp
-- `unit(unitName)` - Set the measurement unit
-
-### Getting Data
-- `get()` - Get the measurement object
-- `getCurrentValue()` - Get the most recent value
-- `getAverage()` - Calculate average of all values
-- `getMin()` - Get minimum value
-- `getMax()` - Get maximum value
-
-### Calculations
-- `difference()` - Calculate difference between upstream and downstream positions
-
-### Listing Available Data
-- `getTypes()` - Get all measurement types
-- `listVariants()` - List variants for current type
-- `listPositions()` - List positions for current type and variant
-
-## Example Workflows
-
-### Setting and retrieving values
-```javascript
-// Set a measurement
-container.type('flow')
- .variant('measured')
- .position('upstream')
- .value(120)
- .unit('gpm');
-
-// Retrieve the same measurement
-const flow = container.type('flow')
- .variant('measured')
- .position('upstream')
- .getCurrentValue();
-```
-
-### Calculating differences
-```javascript
-// Set upstream and downstream measurements
-container.type('pressure').variant('measured').position('upstream').value(100).unit('psi');
-container.type('pressure').variant('measured').position('downstream').value(95).unit('psi');
-
-// Calculate the difference
-const diff = container.type('pressure').variant('measured').difference();
-console.log(`Pressure drop: ${diff.currentDiff} ${diff.unit}`);
-```
-
-### Working with historical data
-```javascript
-// Add multiple values
-container.type('temperature')
- .variant('measured')
- .position('outlet')
- .value(72)
- .value(74)
- .value(73)
- .unit('F');
-
-// Get statistics
-const avg = container.type('temperature').variant('measured').position('outlet').getAverage();
-const min = container.type('temperature').variant('measured').position('outlet').getMin();
-const max = container.type('temperature').variant('measured').position('outlet').getMax();
-```
diff --git a/helper/measurements/examples.js b/helper/measurements/examples.js
deleted file mode 100644
index 2c69c63..0000000
--- a/helper/measurements/examples.js
+++ /dev/null
@@ -1,58 +0,0 @@
-const { MeasurementContainer } = require('./index');
-
-// Create a container
-const container = new MeasurementContainer({ windowSize: 20 });
-
-// Example 1: Setting values with chaining
-console.log('--- Example 1: Setting values ---');
-container.type('pressure').variant('measured').position('upstream').value(100).unit('psi');
-container.type('pressure').variant('measured').position('downstream').value(95).unit('psi');
-container.type('pressure').variant('measured').position('downstream').value(80);
-
-// Example 2: Getting values with chaining
-console.log('--- Example 2: Getting values ---');
-const upstreamValue = container.type('pressure').variant('measured').position('upstream').getCurrentValue();
-const upstreamUnit = container.type('pressure').variant('measured').position('upstream').get().unit;
-console.log(`Upstream pressure: ${upstreamValue} ${upstreamUnit}`);
-const downstreamValue = container.type('pressure').variant('measured').position('downstream').getCurrentValue();
-const downstreamUnit = container.type('pressure').variant('measured').position('downstream').get().unit;
-console.log(`Downstream pressure: ${downstreamValue} ${downstreamUnit}`);
-
-// Example 3: Calculations using chained methods
-console.log('--- Example 3: Calculations ---');
-container.type('flow').variant('predicted').position('upstream').value(200).unit('gpm');
-container.type('flow').variant('predicted').position('downstream').value(195).unit('gpm');
-
-const flowAvg = container.type('flow').variant('predicted').position('upstream').getAverage();
-console.log(`Average upstream flow: ${flowAvg} gpm`);
-
-// Example 4: Getting pressure difference
-console.log('--- Example 4: Difference calculations ---');
-const pressureDiff = container.type('pressure').variant('measured').difference();
-console.log(`Pressure difference: ${pressureDiff.value} ${pressureDiff.unit}`);
-
-// Example 5: Adding multiple values to track history
-console.log('--- Example 5: Multiple values ---');
-// Add several values to upstream flow
-container.type('flow').variant('measured').position('upstream')
- .value(210).value(215).value(205).unit('gpm');
-
-// Then get statistics
-console.log('Flow statistics:');
-console.log(`- Current: ${container.type('flow').variant('measured').position('upstream').getCurrentValue()} gpm`);
-console.log(`- Average: ${container.type('flow').variant('measured').position('upstream').getAverage()} gpm`);
-console.log(`- Min: ${container.type('flow').variant('measured').position('upstream').getMin()} gpm`);
-console.log(`- Max: ${container.type('flow').variant('measured').position('upstream').getMax()} gpm`);
-console.log(`Show all values : ${JSON.stringify(container.type('flow').variant('measured').position('upstream').getAllValues())}`);
-
-// Example 6: Listing available data
-console.log('--- Example 6: Listing available data ---');
-console.log('Types:', container.getTypes());
-console.log('Pressure variants:', container.type('pressure').getVariants());
-console.log('Measured pressure positions:', container.type('pressure').variant('measured').getPositions());
-
-module.exports = {
- runExamples: () => {
- console.log('Examples of the measurement chainable API');
- }
-};
diff --git a/helper/measurements/index.js b/helper/measurements/index.js
deleted file mode 100644
index 68fbcb6..0000000
--- a/helper/measurements/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-const MeasurementContainer = require('./MeasurementContainer');
-const Measurement = require('./Measurement');
-const MeasurementBuilder = require('./MeasurementBuilder');
-
-module.exports = {
- MeasurementContainer,
- Measurement,
- MeasurementBuilder
-};
diff --git a/helper/menuUtils.js b/helper/menuUtils.js
deleted file mode 100644
index 17d2a24..0000000
--- a/helper/menuUtils.js
+++ /dev/null
@@ -1,489 +0,0 @@
-class MenuUtils {
-
-initBasicToggles(elements) {
- // Toggle visibility for log level
- elements.logCheckbox.addEventListener("change", function () {
- elements.rowLogLevel.style.display = this.checked ? "block" : "none";
- });
- elements.rowLogLevel.style.display = elements.logCheckbox.checked
- ? "block"
- : "none";
-}
-
-// Define the initialize toggles function within scope
-initMeasurementToggles(elements) {
- // Toggle visibility for scaling inputs
- elements.scalingCheckbox.addEventListener("change", function () {
- elements.rowInputMin.style.display = this.checked ? "block" : "none";
- elements.rowInputMax.style.display = this.checked ? "block" : "none";
- });
-
- // Set initial states
- elements.rowInputMin.style.display = elements.scalingCheckbox.checked
- ? "block"
- : "none";
- elements.rowInputMax.style.display = elements.scalingCheckbox.checked
- ? "block"
- : "none";
-}
-
-initTensionToggles(elements, node) {
- const currentMethod = node.interpolationMethod;
- elements.rowTension.style.display =
- currentMethod === "monotone_cubic_spline" ? "block" : "none";
- console.log(
- "Initial tension row display: ",
- elements.rowTension.style.display
- );
-
- elements.interpolationMethodInput.addEventListener("change", function () {
- const selectedMethod = this.value;
- console.log(`Interpolation method changed: ${selectedMethod}`);
- node.interpolationMethod = selectedMethod;
-
- // Toggle visibility for tension input
- elements.rowTension.style.display =
- selectedMethod === "monotone_cubic_spline" ? "block" : "none";
- console.log("Tension row display: ", elements.rowTension.style.display);
- });
-}
-// Define the smoothing methods population function within scope
-populateSmoothingMethods(configUrls, elements, node) {
- this.fetchData(configUrls.cloud.config, configUrls.local.config)
- .then((configData) => {
- const smoothingMethods =
- configData.smoothing?.smoothMethod?.rules?.values?.map(
- (o) => o.value
- ) || [];
- this.populateDropdown(
- elements.smoothMethod,
- smoothingMethods,
- node,
- "smooth_method"
- );
- })
- .catch((err) => {
- console.error("Error loading smoothing methods", err);
- });
-}
-
-populateInterpolationMethods(configUrls, elements, node) {
- this.fetchData(configUrls.cloud.config, configUrls.local.config)
- .then((configData) => {
- const interpolationMethods =
- configData?.interpolation?.type?.rules?.values.map((m) => m.value) ||
- [];
- this.populateDropdown(
- elements.interpolationMethodInput,
- interpolationMethods,
- node,
- "interpolationMethod"
- );
-
- // Find the selected method and use it to spawn 1 more field to fill in tension
- //const selectedMethod = interpolationMethods.find(m => m === node.interpolationMethod);
- this.initTensionToggles(elements, node);
- })
- .catch((err) => {
- console.error("Error loading interpolation methods", err);
- });
-}
-
-populateLogLevelOptions(logLevelSelect, configData, node) {
- // debug log level
- //console.log("Displaying configData => ", configData) ;
-
- const logLevels =
- configData?.general?.logging?.logLevel?.rules?.values?.map(
- (l) => l.value
- ) || [];
-
- //console.log("Displaying logLevels => ", logLevels);
-
- // Reuse your existing generic populateDropdown helper
- this.populateDropdown(logLevelSelect, logLevels, node.logLevel);
-}
-
-//cascade dropdowns for asset type, supplier, subType, model, unit
-fetchAndPopulateDropdowns(configUrls, elements, node) {
- this.fetchData(configUrls.cloud.config, configUrls.local.config)
- .then((configData) => {
- const assetType = configData.asset?.type?.default;
- const localSuppliersUrl = this.constructUrl(configUrls.local.taggcodeAPI,`${assetType}s`,"suppliers.json");
- const cloudSuppliersUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/vendor/get_vendors.php");
-
- return this.fetchData(cloudSuppliersUrl, localSuppliersUrl)
- .then((supplierData) => {
-
- const suppliers = supplierData.map((supplier) => supplier.name);
-
- // Populate suppliers dropdown and set up its change handler
- return this.populateDropdown(
- elements.supplier,
- suppliers,
- node,
- "supplier",
- function (selectedSupplier) {
- if (selectedSupplier) {
- this.populateSubTypes(configUrls, elements, node, selectedSupplier);
- }
- }
- );
- })
- .then(() => {
- // If we have a saved supplier, trigger subTypes population
- if (node.supplier) {
- this.populateSubTypes(configUrls, elements, node, node.supplier);
- }
- });
- })
- .catch((error) => {
- console.error("Error in initial dropdown population:", error);
- });
-}
-
-getSpecificConfigUrl(nodeName,cloudAPI) {
-
- const cloudConfigURL = cloudAPI + "/config/" + nodeName + ".json";
- const localConfigURL = "http://localhost:1880/"+ nodeName + "/dependencies/"+ nodeName + "/" + nodeName + "Config.json";
-
- return { cloudConfigURL, localConfigURL };
-
-}
-
-// Save changes to API
-async apiCall(node) {
- try{
- // OLFIANT when a browser refreshes the tag code is lost!!! fix this later!!!!!
- // FIX UUID ALSO LATER
-
- if(node.assetTagCode !== "" || node.assetTagCode !== null){ }
- // API call to register or check asset in central database
- let assetregisterAPI = node.configUrls.cloud.taggcodeAPI + "/asset/create_asset.php";
-
- const assetModelId = node.modelMetadata.id; //asset_product_model_id
- const uuid = node.uuid; //asset_product_model_uuid
- const assetName = node.assetType; //asset_name / type?
- const description = node.name; // asset_description
- const assetStatus = "actief"; //asset_status -> koppel aan enable / disable node ? or make dropdown ?
- const assetProfileId = 1; //asset_profile_id these are the rules to check if the childs are valid under this node (parent / child id?)
- const child_assets = ["63247"]; //child_assets tagnummer of id?
- const assetProcessId = node.processId; //asset_process_id
- const assetLocationId = node.locationId; //asset_location_id
- const tagCode = node.assetTagCode; // if already exists in the node information use it to tell the api it exists and it will update else we will get it from the api call
- //console.log(`this is my tagCode: ${tagCode}`);
-
- // Build base URL with required parameters
- let apiUrl = `?asset_product_model_id=${assetModelId}&asset_product_model_uuid=${uuid}&asset_name=${assetName}&asset_description=${description}&asset_status=${assetStatus}&asset_profile_id=${assetProfileId}&asset_location_id=${assetLocationId}&asset_process_id=${assetProcessId}&child_assets=${child_assets}`;
-
- // Only add tagCode to URL if it exists
- if (tagCode) {
- apiUrl += `&asset_tag_number=${tagCode}`;
- console.log('hello there');
- }
-
- assetregisterAPI += apiUrl;
- console.log("API call to register asset in central database", assetregisterAPI);
-
- const response = await fetch(assetregisterAPI, {
- method: "POST"
- });
-
- // Get the response text first
- const responseText = await response.text();
- console.log("Raw API response:", responseText);
-
- // Try to parse the JSON, handling potential parsing errors
- let jsonResponse;
- try {
- jsonResponse = JSON.parse(responseText);
- } catch (parseError) {
- console.error("JSON Parsing Error:", parseError);
- console.error("Response that could not be parsed:", responseText);
- throw new Error("Failed to parse API response");
- }
-
- console.log(jsonResponse);
-
- if(jsonResponse.success){
- console.log(`${jsonResponse.message}, tag number: ${jsonResponse.asset_tag_number}, asset id: ${jsonResponse.asset_id}`);
- // Save the asset tag number and id to the node
- } else {
- console.log("Asset not registered in central database");
- }
- return jsonResponse;
-
- } catch (error) {
- console.log("Error saving changes to asset register API", error);
- }
-}
-
-
-async fetchData(url, fallbackUrl) {
- try {
- const response = await fetch(url);
- if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
- const responsData = await response.json();
- //responsData
- const data = responsData.data;
- /* .map(item => {
- const { vendor_name, ...rest } = item;
- return {
- name: vendor_name,
- ...rest
- };
- }); */
- console.log(url);
- console.log("Response Data: ", data);
- return data;
-
- } catch (err) {
- console.warn(
- `Primary URL failed: ${url}. Trying fallback URL: ${fallbackUrl}`,
- err
- );
- try {
- const response = await fetch(fallbackUrl);
- if (!response.ok)
- throw new Error(`HTTP error! status: ${response.status}`);
- return await response.json();
- } catch (fallbackErr) {
- console.error("Both primary and fallback URLs failed:", fallbackErr);
- return [];
- }
- }
-}
-
-async fetchProjectData(url) {
- try {
- const response = await fetch(url);
- if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
- const responsData = await response.json();
- console.log("Response Data: ", responsData);
- return responsData;
-
- } catch (err) {
- }
-}
-
-async populateDropdown(
- htmlElement,
- options,
- node,
- property,
- callback
-) {
- this.generateHtml(htmlElement, options, node[property]);
-
- htmlElement.addEventListener("change", async (e) => {
- const newValue = e.target.value;
- console.log(`Dropdown changed: ${property} = ${newValue}`);
- node[property] = newValue;
-
- RED.nodes.dirty(true);
- if (callback) await callback(newValue); // Ensure async callback completion
- });
-}
-
-// Helper function to construct a URL from a base and path internal
-constructUrl(base, ...paths) {
-
- // Remove trailing slash from base and leading slashes from paths
- const sanitizedBase = (base || "").replace(/\/+$/, "");
- const sanitizedPaths = paths.map((path) => path.replace(/^\/+|\/+$/g, ""));
-
- // Join sanitized base and paths
- const url = `${sanitizedBase}/${sanitizedPaths.join("/")}`;
- console.log("Base:", sanitizedBase);
- console.log("Paths:", sanitizedPaths);
- console.log("Constructed URL:", url);
- return url;
-}
-
-//Adjust for API Gateway
-constructCloudURL(base, ...paths) {
- // Remove trailing slash from base and leading slashes from paths
- const sanitizedBase = base.replace(/\/+$/, "");
- const sanitizedPaths = paths.map((path) => path.replace(/^\/+|\/+$/g, ""));
- // Join sanitized base and paths
- const url = `${sanitizedBase}/${sanitizedPaths.join("/")}`;
- return url;
-}
-
-populateSubTypes(configUrls, elements, node, selectedSupplier) {
-
- this.fetchData(configUrls.cloud.config, configUrls.local.config)
- .then((configData) => {
- const assetType = configData.asset?.type?.default;
- const supplierFolder = this.constructUrl( configUrls.local.taggcodeAPI, `${assetType}s`, selectedSupplier );
-
- const localSubTypesUrl = this.constructUrl(supplierFolder, "subtypes.json");
- const cloudSubTypesUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/product/get_subtypesFromVendor.php?vendor_name=" + selectedSupplier);
-
- return this.fetchData(cloudSubTypesUrl, localSubTypesUrl)
- .then((subTypeData) => {
- const subTypes = subTypeData.map((subType) => subType.name);
-
- return this.populateDropdown(
- elements.subType,
- subTypes,
- node,
- "subType",
- function (selectedSubType) {
- if (selectedSubType) {
- // When subType changes, update both models and units
- this.populateModels(
- configUrls,
- elements,
- node,
- selectedSupplier,
- selectedSubType
- );
- this.populateUnitsForSubType(
- configUrls,
- elements,
- node,
- selectedSubType
- );
- }
- }
- );
- })
- .then(() => {
- // If we have a saved subType, trigger both models and units population
- if (node.subType) {
- this.populateModels(
- configUrls,
- elements,
- node,
- selectedSupplier,
- node.subType
- );
- this.populateUnitsForSubType(configUrls, elements, node, node.subType);
- }
- //console.log("In fetch part of subtypes ");
- // Store all data from selected model
-/* node["modelMetadata"] = modelData.find(
- (model) => model.name === node.model
- );
- console.log("Model Metadata: ", node["modelMetadata"]); */
- });
- })
- .catch((error) => {
- console.error("Error populating subtypes:", error);
- });
-}
-
-populateUnitsForSubType(configUrls, elements, node, selectedSubType) {
- // Fetch the units data
- this.fetchData(configUrls.cloud.units, configUrls.local.units)
- .then((unitsData) => {
- // Find the category that matches the subType name
- const categoryData = unitsData.units.find(
- (category) =>
- category.category.toLowerCase() === selectedSubType.toLowerCase()
- );
-
- if (categoryData) {
- // Extract just the unit values and descriptions
- const units = categoryData.values.map((unit) => ({
- value: unit.value,
- description: unit.description,
- }));
-
- // Create the options array with descriptions as labels
- const options = units.map((unit) => ({
- value: unit.value,
- label: `${unit.value} - ${unit.description}`,
- }));
-
- // Populate the units dropdown
- this.populateDropdown(
- elements.unit,
- options.map((opt) => opt.value),
- node,
- "unit"
- );
-
- // If there's no currently selected unit but we have options, select the first one
- if (!node.unit && options.length > 0) {
- node.unit = options[0].value;
- elements.unit.value = options[0].value;
- }
- } else {
- // If no matching category is found, provide a default % option
- const defaultUnits = [{ value: "%", description: "Percentage" }];
- this.populateDropdown(
- elements.unit,
- defaultUnits.map((unit) => unit.value),
- node,
- "unit"
- );
- console.warn(
- `No matching unit category found for subType: ${selectedSubType}`
- );
- }
- })
- .catch((error) => {
- console.error("Error fetching units:", error);
- });
-}
-
-populateModels(
- configUrls,
- elements,
- node,
- selectedSupplier,
- selectedSubType
-) {
-
- this.fetchData(configUrls.cloud.config, configUrls.local.config)
- .then((configData) => {
- const assetType = configData.asset?.type?.default;
- // save assetType to fetch later
- node.assetType = assetType;
-
- const supplierFolder = this.constructUrl( configUrls.local.taggcodeAPI,`${assetType}s`,selectedSupplier);
- const subTypeFolder = this.constructUrl(supplierFolder, selectedSubType);
- const localModelsUrl = this.constructUrl(subTypeFolder, "models.json");
- const cloudModelsUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/product/get_product_models.php?vendor_name=" + selectedSupplier + "&product_subtype_name=" + selectedSubType);
-
- return this.fetchData(cloudModelsUrl, localModelsUrl).then((modelData) => {
- const models = modelData.map((model) => model.name); // use this to populate the dropdown
-
- // If a model is already selected, store its metadata immediately
- if (node.model) {
- node["modelMetadata"] = modelData.find((model) => model.name === node.model);
- }
-
- this.populateDropdown(elements.model, models, node, "model", (selectedModel) => {
- // Store only the metadata for the selected model
- node["modelMetadata"] = modelData.find((model) => model.name === selectedModel);
- });
- /*
- console.log('hello here I am:');
- console.log(node["modelMetadata"]);
-*/
- });
-
- })
- .catch((error) => {
- console.error("Error populating models:", error);
- });
-}
-
-generateHtml(htmlElement, options, savedValue) {
- htmlElement.innerHTML = options.length
- ? `${options
- .map((opt) => ``)
- .join("")}`
- : "";
-
- if (savedValue && options.includes(savedValue)) {
- htmlElement.value = savedValue;
- }
-}
-
-}
-
-module.exports = MenuUtils;
\ No newline at end of file
diff --git a/helper/nodeTemplates.js b/helper/nodeTemplates.js
deleted file mode 100644
index da259e4..0000000
--- a/helper/nodeTemplates.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const nodeTemplates = {
- asset: {
- category: "digital asset",
- color: "#4f8582",
- defaults: {
- name: { value: "", required: true },
- enableLog: { value: false },
- logLevel: { value: "error" },
- parent: { value: "downstream" }, // indicates the position vs the parent in the process downstream,upstream or none.
- supplier: { value: "" },
- subType: { value: "" },
- model: { value: "" },
- unit: { value: "" },
- },
- inputs: 1,
- outputs: 3,
- inputLabels: ["Machine Input"],
- outputLabels: ["process", "dbase", "parent"],
- icon: "font-awesome/fa-cogs",
- elements: {
- // Basic fields
- name: "node-input-name",
- // Logging fields
- logCheckbox: "node-input-enableLog",
- logLevelSelect: "node-input-logLevel",
- rowLogLevel: "row-logLevel",
- // Asset fields
- supplier: "node-input-supplier",
- subType: "node-input-subType",
- model: "node-input-model",
- unit: "node-input-unit",
- //position vs parent
- parent: "node-input-parent",
- },
- projectSettingsURL:
- "http://localhost:1880/generalFunctions/settings/projectSettings.json",
- },
-
- exampleTemplate: {
- category: "digital twin",
- color: "#004080",
- defaults: {
- name: { value: "", required: true },
- foo: { value: 42 },
- },
- inputs: 2,
- outputs: 2,
- inputLabels: ["In A", "In B"],
- outputLabels: ["Out A", "Out B"],
- icon: "font-awesome/fa-gears",
- },
-
- // …add more node “templates” here…
-};
-
-export default nodeTemplates;
diff --git a/helper/nrmse/errorMetric.test.js b/helper/nrmse/errorMetric.test.js
deleted file mode 100644
index 51a22f2..0000000
--- a/helper/nrmse/errorMetric.test.js
+++ /dev/null
@@ -1,297 +0,0 @@
-const ErrorMetrics = require('./errorMetrics');
-
-// Dummy logger for tests
-const logger = {
- error: console.error,
- debug: console.log,
- info: console.log
-};
-
-const config = {
- thresholds: {
- NRMSE_LOW: 0.05,
- NRMSE_MEDIUM: 0.10,
- NRMSE_HIGH: 0.15,
- LONG_TERM_LOW: 0.02,
- LONG_TERM_MEDIUM: 0.04,
- LONG_TERM_HIGH: 0.06
- }
-};
-
-class ErrorMetricsTester {
- constructor() {
- this.totalTests = 0;
- this.passedTests = 0;
- this.failedTests = 0;
- this.errorMetrics = new ErrorMetrics(config, logger);
- }
-
- assert(condition, message) {
- this.totalTests++;
- if (condition) {
- console.log(`✓ PASS: ${message}`);
- this.passedTests++;
- } else {
- console.log(`✗ FAIL: ${message}`);
- this.failedTests++;
- }
- }
-
- testMeanSquaredError() {
- console.log("\nTesting Mean Squared Error...");
- const predicted = [1, 2, 3];
- const measured = [1, 3, 5];
- const mse = this.errorMetrics.meanSquaredError(predicted, measured);
- this.assert(Math.abs(mse - 1.67) < 0.1, "MSE correctly calculated");
- }
-
- testRootMeanSquaredError() {
- console.log("\nTesting Root Mean Squared Error...");
- const predicted = [1, 2, 3];
- const measured = [1, 3, 5];
- const rmse = this.errorMetrics.rootMeanSquaredError(predicted, measured);
- this.assert(Math.abs(rmse - 1.29) < 0.1, "RMSE correctly calculated");
- }
-
- testNormalizedRMSE() {
- console.log("\nTesting Normalized RMSE...");
- const predicted = [100, 102, 104];
- const measured = [98, 103, 107];
- const processMin = 90, processMax = 110;
- const nrmse = this.errorMetrics.normalizedRootMeanSquaredError(predicted, measured, processMin, processMax);
- this.assert(typeof nrmse === 'number' && nrmse > 0, "Normalized RMSE calculated correctly");
- }
-
- testNormalizeUsingRealtime() {
- console.log("\nTesting Normalize Using Realtime...");
- const predicted = [100, 102, 104];
- const measured = [98, 103, 107];
-
- try {
- const nrmse = this.errorMetrics.normalizeUsingRealtime(predicted, measured);
- this.assert(typeof nrmse === 'number' && nrmse > 0, "Normalize using realtime calculated correctly");
- } catch (error) {
- this.assert(false, `Normalize using realtime failed: ${error.message}`);
- }
-
- // Test with identical values to check error handling
- const sameValues = [100, 100, 100];
- try {
- this.errorMetrics.normalizeUsingRealtime(sameValues, sameValues);
- this.assert(false, "Should throw error with identical values");
- } catch (error) {
- this.assert(true, "Correctly throws error when min/max are the same");
- }
- }
-
- testLongTermNRMSD() {
- console.log("\nTesting Long Term NRMSD Accumulation...");
- // Reset the accumulation values
- this.errorMetrics.cumNRMSD = 0;
- this.errorMetrics.cumCount = 0;
-
- let lastValue = 0;
- for (let i = 0; i < 100; i++) {
- lastValue = this.errorMetrics.longTermNRMSD(0.1 + i * 0.001);
- }
-
- this.assert(
- this.errorMetrics.cumCount === 100 &&
- this.errorMetrics.cumNRMSD !== 0 &&
- lastValue !== 0,
- "Long term NRMSD accumulates over 100 iterations"
- );
-
- // Test that values are returned only after accumulating 100 samples
- this.errorMetrics.cumNRMSD = 0;
- this.errorMetrics.cumCount = 0;
-
- for (let i = 0; i < 99; i++) {
- const result = this.errorMetrics.longTermNRMSD(0.1);
- this.assert(result === 0, "No longTermNRMSD returned before 100 samples");
- }
-
- // Use a different value for the 100th sample to ensure a non-zero result
- const result = this.errorMetrics.longTermNRMSD(0.2);
- this.assert(result !== 0, "longTermNRMSD returned after 100 samples");
- }
-
- testDetectImmediateDrift() {
- console.log("\nTesting Immediate Drift Detection...");
-
- // Test high drift
- let drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_HIGH + 0.01);
- this.assert(drift.level === 3, "Detects high immediate drift correctly");
-
- // Test medium drift
- drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_MEDIUM + 0.01);
- this.assert(drift.level === 2, "Detects medium immediate drift correctly");
-
- // Test low drift
- drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_LOW + 0.01);
- this.assert(drift.level === 1, "Detects low immediate drift correctly");
-
- // Test no drift
- drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_LOW - 0.01);
- this.assert(drift.level === 0, "Detects no immediate drift correctly");
- }
-
- testDetectLongTermDrift() {
- console.log("\nTesting Long Term Drift Detection...");
-
- // Test high drift
- let drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_HIGH + 0.01);
- this.assert(drift.level === 3, "Detects high long-term drift correctly");
-
- // Test medium drift
- drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_MEDIUM + 0.01);
- this.assert(drift.level === 2, "Detects medium long-term drift correctly");
-
- // Test low drift
- drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_LOW + 0.01);
- this.assert(drift.level === 1, "Detects low long-term drift correctly");
-
- // Test no drift
- drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_LOW - 0.01);
- this.assert(drift.level === 0, "Detects no long-term drift correctly");
-
- // Test negative drift values
- drift = this.errorMetrics.detectLongTermDrift(-config.thresholds.LONG_TERM_HIGH - 0.01);
- this.assert(drift.level === 3, "Detects negative high long-term drift correctly");
- }
-
- testDriftDetection() {
- console.log("\nTesting Combined Drift Detection...");
-
- let nrmseHigh = config.thresholds.NRMSE_HIGH + 0.01;
- let ltNRMSD = 0;
-
- let result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
-
- this.assert(
- result !== null &&
- result.ImmDrift &&
- result.ImmDrift.level === 3 &&
- result.LongTermDrift.level === 0,
- "Detects high immediate drift with no long-term drift"
- );
-
- nrmseHigh = config.thresholds.NRMSE_LOW - 0.01;
- ltNRMSD = config.thresholds.LONG_TERM_MEDIUM + 0.01;
- result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
- this.assert(
- result !== null &&
- result.ImmDrift.level === 0 &&
- result.LongTermDrift &&
- result.LongTermDrift.level === 2,
- "Detects medium long-term drift with no immediate drift"
- );
-
- nrmseHigh = config.thresholds.NRMSE_MEDIUM + 0.01;
- ltNRMSD = config.thresholds.LONG_TERM_MEDIUM + 0.01;
- result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
- this.assert(
- result.ImmDrift.level === 2 &&
- result.LongTermDrift.level === 2,
- "Detects both medium immediate and medium long-term drift"
- );
-
- nrmseHigh = config.thresholds.NRMSE_LOW - 0.01;
- ltNRMSD = config.thresholds.LONG_TERM_LOW - 0.01;
- result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
- this.assert(
- result.ImmDrift.level === 0 &&
- result.LongTermDrift.level === 0,
- "No significant drift detected when under thresholds"
- );
- }
-
- testAssessDrift() {
- console.log("\nTesting assessDrift function...");
-
- // Reset accumulation for testing
- this.errorMetrics.cumNRMSD = 0;
- this.errorMetrics.cumCount = 0;
-
- const predicted = [100, 101, 102, 103];
- const measured = [90, 91, 92, 93];
- const processMin = 90, processMax = 110;
-
- let result = this.errorMetrics.assessDrift(predicted, measured, processMin, processMax);
-
- this.assert(
- result !== null &&
- typeof result.nrmse === 'number' &&
- typeof result.longTermNRMSD === 'number' &&
- typeof result.immediateLevel === 'number' &&
- typeof result.immediateFeedback === 'string' &&
- typeof result.longTermLevel === 'number' &&
- typeof result.longTermFeedback === 'string',
- "assessDrift returns complete result structure"
- );
-
- this.assert(
- result.immediateLevel > 0,
- "assessDrift detects immediate drift with significant difference"
- );
-
- // Test with identical values
- result = this.errorMetrics.assessDrift(predicted, predicted, processMin, processMax);
- this.assert(
- result.nrmse === 0 &&
- result.immediateLevel === 0,
- "assessDrift indicates no immediate drift when predicted equals measured"
- );
-
- // Test with slight drift
- const measuredSlight = [100, 100.5, 101, 101.5];
- result = this.errorMetrics.assessDrift(predicted, measuredSlight, processMin, processMax);
-
- this.assert(
- result !== null &&
- result.nrmse < 0.05 &&
- (result.immediateLevel < 2),
- "assessDrift returns appropriate levels for slight drift"
- );
-
- // Test long-term drift accumulation
- for (let i = 0; i < 100; i++) {
- this.errorMetrics.assessDrift(
- predicted,
- measured.map(m => m + (Math.random() * 2 - 1)), // Add small random fluctuation
- processMin,
- processMax
- );
- }
-
- result = this.errorMetrics.assessDrift(predicted, measured, processMin, processMax);
- this.assert(
- result.longTermNRMSD !== 0,
- "Long-term drift accumulates over multiple assessments"
- );
- }
-
- async runAllTests() {
- console.log("\nStarting Error Metrics Tests...\n");
- this.testMeanSquaredError();
- this.testRootMeanSquaredError();
- this.testNormalizedRMSE();
- this.testNormalizeUsingRealtime();
- this.testLongTermNRMSD();
- this.testDetectImmediateDrift();
- this.testDetectLongTermDrift();
- this.testDriftDetection();
- this.testAssessDrift();
-
- console.log("\nTest Summary:");
- console.log(`Total Tests: ${this.totalTests}`);
- console.log(`Passed: ${this.passedTests}`);
- console.log(`Failed: ${this.failedTests}`);
-
- process.exit(this.failedTests > 0 ? 1 : 0);
- }
-}
-
-// Run all tests
-const tester = new ErrorMetricsTester();
-tester.runAllTests().catch(console.error);
diff --git a/helper/nrmse/errorMetrics.js b/helper/nrmse/errorMetrics.js
deleted file mode 100644
index c567ec4..0000000
--- a/helper/nrmse/errorMetrics.js
+++ /dev/null
@@ -1,154 +0,0 @@
-//load local dependencies
-const EventEmitter = require('events');
-
-//load all config modules
-const defaultConfig = require('./nrmseConfig.json');
-const ConfigUtils = require('../configUtils');
-
-class ErrorMetrics {
- constructor(config = {}, logger) {
-
- this.emitter = new EventEmitter(); // Own EventEmitter
- this.configUtils = new ConfigUtils(defaultConfig);
- this.config = this.configUtils.initConfig(config);
-
- // Init after config is set
- this.logger = logger;
-
- // For long-term NRMSD accumulation
- this.cumNRMSD = 0;
- this.cumCount = 0;
- }
-
- //INCLUDE timestamps in the next update OLIFANT
- meanSquaredError(predicted, measured) {
- if (predicted.length !== measured.length) {
- this.logger.error("Comparing MSE Arrays must have the same length.");
- return 0;
- }
-
-
- let sumSqError = 0;
- for (let i = 0; i < predicted.length; i++) {
- const err = predicted[i] - measured[i];
- sumSqError += err * err;
- }
- return sumSqError / predicted.length;
- }
-
- rootMeanSquaredError(predicted, measured) {
- return Math.sqrt(this.meanSquaredError(predicted, measured));
- }
-
- normalizedRootMeanSquaredError(predicted, measured, processMin, processMax) {
- const range = processMax - processMin;
- if (range <= 0) {
- this.logger.error("Invalid process range: processMax must be greater than processMin.");
- }
- const rmse = this.rootMeanSquaredError(predicted, measured);
- return rmse / range;
- }
-
- longTermNRMSD(input) {
-
- const storedNRMSD = this.cumNRMSD;
- const storedCount = this.cumCount;
- const newCount = storedCount + 1;
-
- // Update cumulative values
- this.cumCount = newCount;
-
- // Calculate new running average
- if (storedCount === 0) {
- this.cumNRMSD = input; // First value
- } else {
- // Running average formula: newAvg = oldAvg + (newValue - oldAvg) / newCount
- this.cumNRMSD = storedNRMSD + (input - storedNRMSD) / newCount;
- }
-
- if(newCount >= 100) {
- // Return the current NRMSD value, not just the contribution from this sample
- return this.cumNRMSD;
- }
- return 0;
- }
-
- normalizeUsingRealtime(predicted, measured) {
- const realtimeMin = Math.min(Math.min(...predicted), Math.min(...measured));
- const realtimeMax = Math.max(Math.max(...predicted), Math.max(...measured));
- const range = realtimeMax - realtimeMin;
- if (range <= 0) {
- throw new Error("Invalid process range: processMax must be greater than processMin.");
- }
- const rmse = this.rootMeanSquaredError(predicted, measured);
- return rmse / range;
- }
-
- detectImmediateDrift(nrmse) {
- let ImmDrift = {};
- this.logger.debug(`checking immediate drift with thresholds : ${this.config.thresholds.NRMSE_HIGH} ${this.config.thresholds.NRMSE_MEDIUM} ${this.config.thresholds.NRMSE_LOW}`);
- switch (true) {
- case( nrmse > this.config.thresholds.NRMSE_HIGH ) :
- ImmDrift = {level : 3 , feedback : "High immediate drift detected"};
- break;
- case( nrmse > this.config.thresholds.NRMSE_MEDIUM ) :
- ImmDrift = {level : 2 , feedback : "Medium immediate drift detected"};
- break;
- case(nrmse > this.config.thresholds.NRMSE_LOW ):
- ImmDrift = {level : 1 , feedback : "Low immediate drift detected"};
- break;
- default:
- ImmDrift = {level : 0 , feedback : "No drift detected"};
- }
- return ImmDrift;
- }
-
- detectLongTermDrift(longTermNRMSD) {
- let LongTermDrift = {};
- this.logger.debug(`checking longterm drift with thresholds : ${this.config.thresholds.LONG_TERM_HIGH} ${this.config.thresholds.LONG_TERM_MEDIUM} ${this.config.thresholds.LONG_TERM_LOW}`);
- switch (true) {
- case(Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_HIGH) :
- LongTermDrift = {level : 3 , feedback : "High long-term drift detected"};
- break;
- case (Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_MEDIUM) :
- LongTermDrift = {level : 2 , feedback : "Medium long-term drift detected"};
- break;
- case ( Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_LOW ) :
- LongTermDrift = {level : 1 , feedback : "Low long-term drift detected"};
- break;
- default:
- LongTermDrift = {level : 0 , feedback : "No drift detected"};
- }
- return LongTermDrift;
- }
-
- detectDrift(nrmse, longTermNRMSD) {
- const ImmDrift = this.detectImmediateDrift(nrmse);
- const LongTermDrift = this.detectLongTermDrift(longTermNRMSD);
- return { ImmDrift, LongTermDrift };
- }
-
- // asses the drift
- assessDrift(predicted, measured, processMin, processMax) {
- // Compute NRMSE and check for immediate drift
- const nrmse = this.normalizedRootMeanSquaredError(predicted, measured, processMin, processMax);
- this.logger.debug(`NRMSE: ${nrmse}`);
- // cmopute long-term NRMSD and add result to cumalitve NRMSD
- const longTermNRMSD = this.longTermNRMSD(nrmse);
- // return the drift
- // Return the drift assessment object
- const driftAssessment = this.detectDrift(nrmse, longTermNRMSD);
- return {
- nrmse,
- longTermNRMSD,
- immediateLevel: driftAssessment.ImmDrift.level,
- immediateFeedback: driftAssessment.ImmDrift.feedback,
- longTermLevel: driftAssessment.LongTermDrift.level,
- longTermFeedback: driftAssessment.LongTermDrift.feedback
- };
- }
-
-
-}
-
-module.exports = ErrorMetrics;
diff --git a/helper/nrmse/nrmseConfig.json b/helper/nrmse/nrmseConfig.json
deleted file mode 100644
index b8eeb9a..0000000
--- a/helper/nrmse/nrmseConfig.json
+++ /dev/null
@@ -1,138 +0,0 @@
-{
- "general": {
- "name": {
- "default": "ErrorMetrics",
- "rules": {
- "type": "string",
- "description": "A human-readable name for the configuration."
- }
- },
- "id": {
- "default": null,
- "rules": {
- "type": "string",
- "nullable": true,
- "description": "A unique identifier for this configuration, assigned dynamically when needed."
- }
- },
- "unit": {
- "default": "unitless",
- "rules": {
- "type": "string",
- "description": "The unit used for the state values (e.g., 'meters', 'seconds', 'unitless')."
- }
- },
- "logging": {
- "logLevel": {
- "default": "info",
- "rules": {
- "type": "enum",
- "values": [
- {
- "value": "debug",
- "description": "Log messages are printed for debugging purposes."
- },
- {
- "value": "info",
- "description": "Informational messages are printed."
- },
- {
- "value": "warn",
- "description": "Warning messages are printed."
- },
- {
- "value": "error",
- "description": "Error messages are printed."
- }
- ]
- }
- },
- "enabled": {
- "default": true,
- "rules": {
- "type": "boolean",
- "description": "Indicates whether logging is active. If true, log messages will be generated."
- }
- }
- }
- },
- "functionality": {
- "softwareType": {
- "default": "errorMetrics",
- "rules": {
- "type": "string",
- "description": "Logical name identifying the software type."
- }
- },
- "role": {
- "default": "error calculation",
- "rules": {
- "type": "string",
- "description": "Functional role within the system."
- }
- }
- },
- "mode": {
- "current": {
- "default": "active",
- "rules": {
- "type": "enum",
- "values": [
- {
- "value": "active",
- "description": "The error metrics calculation is active."
- },
- {
- "value": "inactive",
- "description": "The error metrics calculation is inactive."
- }
- ],
- "description": "The operational mode of the error metrics calculation."
- }
- }
- },
- "thresholds": {
- "NRMSE_LOW": {
- "default": 0.05,
- "rules": {
- "type": "number",
- "description": "Low threshold for normalized root mean squared error."
- }
- },
- "NRMSE_MEDIUM": {
- "default": 0.10,
- "rules": {
- "type": "number",
- "description": "Medium threshold for normalized root mean squared error."
- }
- },
- "NRMSE_HIGH": {
- "default": 0.15,
- "rules": {
- "type": "number",
- "description": "High threshold for normalized root mean squared error."
- }
- },
- "LONG_TERM_LOW": {
- "default": 0.02,
- "rules": {
- "type": "number",
- "description": "Low threshold for long-term normalized root mean squared deviation."
- }
- },
- "LONG_TERM_MEDIUM": {
- "default": 0.04,
- "rules": {
- "type": "number",
- "description": "Medium threshold for long-term normalized root mean squared deviation."
- }
- },
- "LONG_TERM_HIGH": {
- "default": 0.06,
- "rules": {
- "type": "number",
- "description": "High threshold for long-term normalized root mean squared deviation."
- }
- }
- }
-}
diff --git a/helper/outliers/outlierDetection.js b/helper/outliers/outlierDetection.js
deleted file mode 100644
index 2cdca81..0000000
--- a/helper/outliers/outlierDetection.js
+++ /dev/null
@@ -1,89 +0,0 @@
-class DynamicClusterDeviation {
- constructor() {
- this.clusters = []; // Stores clusters as { center, spread, count }
- }
-
- update(value) {
- console.log(`\nProcessing value: ${value}`);
-
- // If no clusters exist, create the first one
- if (this.clusters.length === 0) {
- this.clusters.push({ center: value, spread: 0, count: 1 });
- console.log(` → First cluster created at ${value}`);
- return { value, isOutlier: false };
- }
-
- // Step 1: Find the closest cluster
- let bestMatch = null;
- let minDistance = Infinity;
-
- for (const cluster of this.clusters) {
- const distance = Math.abs(value - cluster.center);
- console.log(` Checking against cluster at ${cluster.center} (spread: ${cluster.spread}, count: ${cluster.count}) → distance: ${distance}`);
-
- if (distance < minDistance) {
- bestMatch = cluster;
- minDistance = distance;
- }
- }
-
- console.log(` Closest cluster found at ${bestMatch.center} with distance: ${minDistance}`);
-
- // Step 2: Compute dynamic threshold
- const dynamicThreshold = 1 + 5 / Math.sqrt(bestMatch.count + 1);
- const allowedDeviation = dynamicThreshold * (bestMatch.spread || 1);
-
- console.log(` Dynamic threshold: ${dynamicThreshold.toFixed(2)}, Allowed deviation: ${allowedDeviation.toFixed(2)}`);
-
- // Step 3: Check if value fits within the dynamically adjusted cluster spread
- if (minDistance <= allowedDeviation) {
- // Update cluster dynamically
- const newCenter = (bestMatch.center * bestMatch.count + value) / (bestMatch.count + 1);
- const newSpread = Math.max(bestMatch.spread, minDistance);
- bestMatch.center = newCenter;
- bestMatch.spread = newSpread;
- bestMatch.count += 1;
-
- console.log(` ✅ Value fits in cluster! Updating cluster:`);
- console.log(` → New center: ${newCenter.toFixed(2)}`);
- console.log(` → New spread: ${newSpread.toFixed(2)}`);
- console.log(` → New count: ${bestMatch.count}`);
-
- return { value, isOutlier: false };
- } else {
- // If too far, create a new cluster
- this.clusters.push({ center: value, spread: 0, count: 1 });
-
- console.log(` ❌ Outlier detected! New cluster created at ${value}`);
-
- return { value, isOutlier: true };
- }
- }
-}
-
-// Rolling window simulation with outlier detection
-/*
-const detector = new DynamicClusterDeviation();
-const dataStream = [10, 10.2, 10.5, 9.8, 11, 50, 10.3, 200, 201, 200.1, 205, 202, 250, 260, 270, 280, 290, 300];
-
-// Define the number of elements per rolling window chunk.
-const windowSize = 5;
-let rollingWindow = [];
-
-dataStream.forEach((value, index) => {
- console.log(`\n=== Processing value ${index + 1} ===`);
- rollingWindow.push(value);
- const result = detector.update(value);
- console.log(`Current rolling window: [${rollingWindow.join(', ')}]`);
- console.log(`Result: value=${result.value} (${result.isOutlier ? 'Outlier' : 'Inlier'})`);
-
- // Once the window size is reached, show current cluster states and reset the window for the next chunk.
- if (rollingWindow.length === windowSize) {
- console.log("\n--- Rolling window chunk finished ---");
- console.log("Detector cluster states:", JSON.stringify(detector.clusters, null, 2));
- rollingWindow = [];
- }
-});
-
-console.log("\nFinal detector cluster states:", JSON.stringify(detector.clusters, null, 2));
-*/
\ No newline at end of file
diff --git a/helper/outputUtils.js b/helper/outputUtils.js
deleted file mode 100644
index bd6fb8d..0000000
--- a/helper/outputUtils.js
+++ /dev/null
@@ -1,132 +0,0 @@
-//this class will handle the output events for the node red node
-class OutputUtils {
- constructor() {
- this.output ={};
- this.output['influxdb'] = {};
- this.output['process'] = {};
- }
-
- checkForChanges(output, format) {
- const changedFields = {};
- for (const key in output) {
- if (output.hasOwnProperty(key) && output[key] !== this.output[format][key]) {
- let value = output[key];
- // For fields: if the value is an object (and not a Date), stringify it.
- if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
- changedFields[key] = JSON.stringify(value);
- } else {
- changedFields[key] = value;
- }
- }
- }
-
- // Update the saved output state.
- this.output[format] = { ...this.output[format], ...changedFields };
-
- return changedFields;
- }
-
- formatMsg(output, config, format) {
-
- //define emtpy message
- let msg = {};
-
- // Compare output with last output and only include changed values
- const changedFields = this.checkForChanges(output,format);
-
- if (Object.keys(changedFields).length > 0) {
-
- switch (format) {
- case 'influxdb':
- // Extract the relevant config properties.
- const relevantConfig = this.extractRelevantConfig(config);
- // Flatten the tags so that no nested objects are passed on.
- const flatTags = this.flattenTags(relevantConfig);
- msg = this.influxDBFormat(changedFields, config, flatTags);
-
- break;
-
- case 'process':
-
- // Compare output with last output and only include changed values
- msg = this.processFormat(changedFields,config);
- //console.log(msg);
- break;
-
- default:
- console.log('Unknown format in output utils');
- break;
- }
- return msg;
- }
- }
-
-
- influxDBFormat(changedFields, config , flatTags) {
- // Create the measurement and topic using softwareType and name config.functionality.softwareType + .
- const measurement = config.general.name;
- const payload = {
- measurement: measurement,
- fields: changedFields,
- tags: flatTags,
- timestamp: new Date(),
- };
-
- const topic = measurement;
- const msg = { topic: topic, payload: payload };
- return msg;
- }
-
- flattenTags(obj) {
- const result = {};
- for (const key in obj) {
- if (obj.hasOwnProperty(key)) {
- const value = obj[key];
- if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
- // Recursively flatten the nested object.
- const flatChild = this.flattenTags(value);
- for (const childKey in flatChild) {
- if (flatChild.hasOwnProperty(childKey)) {
- result[`${key}_${childKey}`] = String(flatChild[childKey]);
- }
- }
- } else {
- // InfluxDB tags must be strings.
- result[key] = String(value);
- }
- }
- }
- return result;
- }
-
- extractRelevantConfig(config) {
-
- return {
- // general properties
- id: config.general?.id,
- name: config.general?.name,
- unit: config.general?.unit,
- // functionality properties
- softwareType: config.functionality?.softwareType,
- role: config.functionality?.role,
- // asset properties (exclude machineCurve)
- uuid: config.asset?.uuid,
- geoLocation: config.asset?.geoLocation,
- supplier: config.asset?.supplier,
- type: config.asset?.type,
- subType: config.asset?.subType,
- model: config.asset?.model,
- };
- }
-
- processFormat(changedFields,config) {
- // Create the measurement and topic using softwareType and name config.functionality.softwareType + .
- const measurement = config.general.name;
- const payload = changedFields;
- const topic = measurement;
- const msg = { topic: topic, payload: payload };
- return msg;
- }
-}
-
-module.exports = OutputUtils;
diff --git a/helper/state/movementManager.js b/helper/state/movementManager.js
deleted file mode 100644
index 8688cd4..0000000
--- a/helper/state/movementManager.js
+++ /dev/null
@@ -1,277 +0,0 @@
-//const EventEmitter = require('events');
-
-class movementManager {
- constructor(config, logger, emitter) {
- this.emitter = emitter; //new EventEmitter(); //state class emitter
-
- const { min, max, initial } = config.position;
- const { speed, maxSpeed, interval } = config.movement;
-
- this.minPosition = min;
- this.maxPosition = max;
- this.currentPosition = initial;
-
- this.speed = speed;
- this.maxSpeed = maxSpeed;
- this.interval = interval;
- this.timeleft = 0; // timeleft of current movement
-
- this.logger = logger;
- this.movementMode = config.movement.mode;
- }
-
- getCurrentPosition() {
- return this.currentPosition;
- }
-
- async moveTo(targetPosition, signal) {
- // Constrain target position if necessary
- if (
- targetPosition < this.minPosition ||
- targetPosition > this.maxPosition
- ) {
- targetPosition = this.constrain(targetPosition);
- this.logger.warn(
- `New target position=${targetPosition} is constrained to fit between min=${this.minPosition} and max=${this.maxPosition}`
- );
- }
-
- this.logger.info(
- `Starting movement to position ${targetPosition} in ${this.movementMode} with avg speed=${this.speed}%/s.`
- );
-
- if (signal && signal.aborted) {
- this.logger.debug("Movement aborted.");
- return;
- }
-
- try {
- // Execute the movement logic based on the mode
- switch (this.movementMode) {
- case "staticspeed":
- const movelinFeedback = await this.moveLinear(targetPosition,signal);
- this.logger.info(`Linear move: ${movelinFeedback} `);
- break;
-
- case "dynspeed":
- const moveDynFeedback = await this.moveEaseInOut(targetPosition,signal);
- this.logger.info(`Dynamic move : ${moveDynFeedback}`);
- break;
-
- default:
- throw new Error(`Unsupported movement mode: ${this.movementMode}`);
- }
- } catch (error) {
- this.logger.error(error);
- }
- }
-
- moveLinear(targetPosition, signal) {
- return new Promise((resolve, reject) => {
- // Immediate abort if already signalled
- if (signal?.aborted) {
- return reject(new Error("Movement aborted"));
- }
-
- // Clamp the final target into [minPosition, maxPosition]
- targetPosition = this.constrain(targetPosition);
-
- // Compute direction and remaining distance
- const direction = targetPosition > this.currentPosition ? 1 : -1;
- const distance = Math.abs(targetPosition - this.currentPosition);
-
- // Speed is a fraction [0,1] of full-range per second
- this.speed = Math.min(Math.max(this.speed, 0), 1);
- const fullRange = this.maxPosition - this.minPosition;
- const velocity = this.speed * fullRange; // units per second
- if (velocity === 0) {
- return reject(new Error("Movement aborted: zero speed"));
- }
-
- // Duration and bookkeeping
- const duration = distance / velocity; // seconds to go the remaining distance
- this.timeleft = duration;
- this.logger.debug(
- `Linear move: dir=${direction}, dist=${distance}, vel=${velocity.toFixed(2)} u/s, dur=${duration.toFixed(2)}s`
- );
-
- // Compute how much to move each tick
- const intervalMs = this.interval;
- const intervalSec = intervalMs / 1000;
- const stepSize = direction * velocity * intervalSec;
-
- const startTime = Date.now();
-
- // Kick off the loop
- const intervalId = setInterval(() => {
- // 7a) Abort check
- if (signal?.aborted) {
- clearInterval(intervalId);
- return reject(new Error("Movement aborted"));
- }
-
- // Advance position and clamp
- this.currentPosition += stepSize;
- this.currentPosition = this.constrain(this.currentPosition);
- this.emitPos(this.currentPosition);
-
- // Update timeleft
- const elapsed = (Date.now() - startTime) / 1000;
- this.timeleft = Math.max(0, duration - elapsed);
-
- this.logger.debug(
- `pos=${this.currentPosition.toFixed(2)}, timeleft=${this.timeleft.toFixed(2)}`
- );
-
- // Completed the move?
- if (
- (direction > 0 && this.currentPosition >= targetPosition) ||
- (direction < 0 && this.currentPosition <= targetPosition)
- ) {
- clearInterval(intervalId);
- this.currentPosition = targetPosition;
- this.emitPos(this.currentPosition);
- return resolve("Reached target move.");
- }
- }, intervalMs);
-
- // 8) Also catch aborts that happen before the first tick
- signal?.addEventListener("abort", () => {
- clearInterval(intervalId);
- reject(new Error("Movement aborted"));
- });
- });
-}
-
- moveLinearinTime(targetPosition,signal) {
- return new Promise((resolve, reject) => {
- // Abort immediately if already signalled
- if (signal?.aborted) {
- return reject(new Error("Movement aborted"));
- }
-
- const direction = targetPosition > this.currentPosition ? 1 : -1;
- const distance = Math.abs(targetPosition - this.currentPosition);
-
- // Ensure speed is a percentage [0, 1]
- this.speed = Math.min(Math.max(this.speed, 0), 1);
-
- // Calculate duration based on percentage of distance per second
- const duration = 1 / this.speed; // 1 second for 100% of the distance
-
- this.timeleft = duration; //set this so other classes can use it
- this.logger.debug(
- `Linear movement: Direction=${direction}, Distance=${distance}, Duration=${duration}s`
- );
-
- let elapsedTime = 0;
- const interval = this.interval; // Update every x ms
- const totalSteps = Math.ceil((duration * 1000) / interval);
- const stepSize = direction * (distance / totalSteps);
-
- // 2) Set up the abort listener once
- const intervalId = setInterval(() => {
- // 3) Check for abort on each tick
- if (signal?.aborted) {
- clearInterval(intervalId);
- return reject(new Error("Movement aborted"));
- }
- // Update elapsed time
- elapsedTime += interval / 1000;
-
- this.timeleft = duration - elapsedTime; //set this so other classes can use it
-
- // Update the position incrementally
- this.currentPosition += stepSize;
- this.emitPos(this.currentPosition);
- this.logger.debug(
- `Using ${this.movementMode} => Current position ${this.currentPosition}`
- );
-
- // Check if the target position has been reached
- if (
- (direction > 0 && this.currentPosition >= targetPosition) ||
- (direction < 0 && this.currentPosition <= targetPosition)
- ) {
- clearInterval(intervalId);
- this.currentPosition = targetPosition;
- resolve(`Reached target move.`);
- }
- }, interval);
- // Also attach abort outside the interval in case it fires before the first tick:
- signal?.addEventListener("abort", () => {
- clearInterval(intervalId);
- reject(new Error("Movement aborted"));
- });
- });
- }
-
- moveEaseInOut(targetPosition, signal) {
- return new Promise((resolve, reject) => {
- // 1) Bail immediately if already aborted
- if (signal?.aborted) {
- return reject(new Error("Movement aborted"));
- }
-
- const direction = targetPosition > this.currentPosition ? 1 : -1;
- const totalDistance = Math.abs(targetPosition - this.currentPosition);
- const startPosition = this.currentPosition;
- this.speed = Math.min(Math.max(this.speed, 0), 1);
-
- const easeFunction = (t) =>
- t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
-
- let elapsedTime = 0;
- const duration = totalDistance / this.speed;
- this.timeleft = duration;
- const interval = this.interval;
-
- // 2) Start the moving loop
- const intervalId = setInterval(() => {
- // 3) Check for abort on each tick
- if (signal?.aborted) {
- clearInterval(intervalId);
- return reject(new Error("Movement aborted"));
- }
-
- elapsedTime += interval / 1000;
- const progress = Math.min(elapsedTime / duration, 1);
- this.timeleft = duration - elapsedTime;
- const easedProgress = easeFunction(progress);
- const newPosition =
- startPosition + (targetPosition - startPosition) * easedProgress;
-
- this.emitPos(newPosition);
- this.logger.debug(
- `Using ${this.movementMode} => Progress=${progress.toFixed(
- 2
- )}, Eased=${easedProgress.toFixed(2)}`
- );
-
- if (progress >= 1) {
- clearInterval(intervalId);
- this.currentPosition = targetPosition;
- resolve(`Reached target move.`);
- } else {
- this.currentPosition = newPosition;
- }
- }, interval);
-
- // 4) Also listen once for abort before first tick
- signal?.addEventListener("abort", () => {
- clearInterval(intervalId);
- reject(new Error("Movement aborted"));
- });
- });
- }
-
- emitPos(newPosition) {
- this.emitter.emit("positionChange", newPosition);
- }
-
- constrain(value) {
- return Math.min(Math.max(value, this.minPosition), this.maxPosition);
- }
-}
-
-module.exports = movementManager;
diff --git a/helper/state/state.js b/helper/state/state.js
deleted file mode 100644
index a2ee626..0000000
--- a/helper/state/state.js
+++ /dev/null
@@ -1,131 +0,0 @@
-//load local dependencies
-const EventEmitter = require('events');
-const StateManager = require('./stateManager');
-const MovementManager = require('./movementManager');
-
-//load all config modules
-const defaultConfig = require('./stateConfig.json');
-const ConfigUtils = require('../../../generalFunctions/helper/configUtils');
-
-class state{
- constructor(config = {}, logger) {
-
- this.emitter = new EventEmitter(); // Own EventEmitter
- this.configUtils = new ConfigUtils(defaultConfig);
- this.config = this.configUtils.initConfig(config);
- this.abortController = null; // new abort controller for aborting async tasks
- // Init after config is set
- this.logger = logger;
-
- // Initialize StateManager for state handling
- this.stateManager = new StateManager(this.config,this.logger);
- this.movementManager = new MovementManager(this.config, this.logger, this.emitter);
-
- this.delayedMove = null;
- this.mode = this.config.mode.current;
-
- // Log initialization
- this.logger.info("State class initialized.");
-
- }
-
- // -------- Delegate State Management -------- //
-
- getMoveTimeLeft() {
- return this.movementManager.timeleft;
- }
-
- getCurrentState() {
- return this.stateManager.currentState;
- }
-
- getStateDescription() {
- return this.stateManager.getStateDescription();
- }
-
- // -------- Movement Methods -------- //
- getCurrentPosition() {
- return this.movementManager.getCurrentPosition();
- }
-
- getRunTimeHours() {
- return this.stateManager.getRunTimeHours();
- }
-
- async moveTo(targetPosition) {
-
- // Check for invalid conditions and throw errors
- if (targetPosition === this.getCurrentPosition()) {
- this.logger.warn(`Target position=${targetPosition} is the same as the current position ${this.getCurrentPosition()}. Not executing move.`);
- return;
- }
-
- if (this.stateManager.getCurrentState() !== "operational") {
- if (this.config.mode.current === "auto") {
- this.delayedMove = targetPosition;
- this.logger.warn(`Saving setpoint=${targetPosition} to execute once back in 'operational' state.`);
- }
- else{
- this.logger.warn(`Not able to accept setpoint=${targetPosition} while not in ${this.stateManager.getCurrentState()} state`);
- }
- //return early
- return;
- }
- this.abortController = new AbortController();
- const { signal } = this.abortController;
- try {
- const newState = targetPosition < this.getCurrentPosition() ? "decelerating" : "accelerating";
- await this.transitionToState(newState,signal); // awaits transition
- await this.movementManager.moveTo(targetPosition,signal); // awaits moving
- this.emitter.emit("movementComplete", { position: targetPosition });
- await this.transitionToState("operational");
- } catch (error) {
- this.logger.error(error);
- }
- }
-
- // -------- State Transition Methods -------- //
-
- async transitionToState(targetState, signal) {
-
- const fromState = this.getCurrentState();
- const position = this.getCurrentPosition();
-
- try {
-
- this.logger.debug(`Starting transition from ${fromState} to ${targetState}.`);
- const feedback = await this.stateManager.transitionTo(targetState,signal);
- this.logger.info(`Statemanager: ${feedback}`);
-
- /* -- Auto pick setpoints in auto mode when operational--*/
- if (
- targetState === "operational" &&
- this.config.mode.current === "auto" &&
- this.delayedMove !== position &&
- this.delayedMove
- ) {
- this.logger.info(`Automatically picking up on last requested setpoint ${this.delayedMove}`);
- //trigger move
- await this.moveTo(this.delayedMove,signal);
- this.delayedMove = null;
-
- this.logger.info(`moveTo : ${feedback} `);
- }
-
- this.logger.info(`State change to ${targetState} completed.`);
- this.emitter.emit('stateChange', targetState); // <-- Implement Here
- } catch (error) {
- if (
- error.message === "Transition aborted" ||
- error.message === "Movement aborted"
- ) {
- throw error;
- }
- this.logger.error(error);
- }
- }
-
-}
-
-module.exports = state;
-
diff --git a/helper/state/stateConfig.json b/helper/state/stateConfig.json
deleted file mode 100644
index 8846c61..0000000
--- a/helper/state/stateConfig.json
+++ /dev/null
@@ -1,331 +0,0 @@
-{
- "general": {
- "name": {
- "default": "State Configuration",
- "rules": {
- "type": "string",
- "description": "A human-readable name for the state configuration."
- }
- },
- "id": {
- "default": null,
- "rules": {
- "type": "string",
- "nullable": true,
- "description": "A unique identifier for this configuration, assigned dynamically when needed."
- }
- },
- "unit": {
- "default": "unitless",
- "rules": {
- "type": "string",
- "description": "The unit used for the state values (e.g., 'meters', 'seconds', 'unitless')."
- }
- },
- "logging": {
- "logLevel": {
- "default": "info",
- "rules": {
- "type": "enum",
- "values": [
- {
- "value": "debug",
- "description": "Log messages are printed for debugging purposes."
- },
- {
- "value": "info",
- "description": "Informational messages are printed."
- },
- {
- "value": "warn",
- "description": "Warning messages are printed."
- },
- {
- "value": "error",
- "description": "Error messages are printed."
- }
- ]
- }
- },
- "enabled": {
- "default": true,
- "rules": {
- "type": "boolean",
- "description": "Indicates whether logging is active. If true, log messages will be generated."
- }
- }
- }
- },
- "functionality": {
- "softwareType": {
- "default": "state class",
- "rules": {
- "type": "string",
- "description": "Logical name identifying the software type."
- }
- },
- "role": {
- "default": "StateController",
- "rules": {
- "type": "string",
- "description": "Functional role within the system."
- }
- }
- },
- "time": {
- "starting": {
- "default": 10,
- "rules": {
- "type": "number",
- "description": "Time in seconds for the starting phase."
- }
- },
- "warmingup": {
- "default": 5,
- "rules": {
- "type": "number",
- "description": "Time in seconds for the warming-up phase."
- }
- },
- "stopping": {
- "default": 5,
- "rules": {
- "type": "number",
- "description": "Time in seconds for the stopping phase."
- }
- },
- "coolingdown": {
- "default": 10,
- "rules": {
- "type": "number",
- "description": "Time in seconds for the cooling-down phase."
- }
- }
- },
- "movement": {
- "mode": {
- "default": "dynspeed",
- "rules": {
- "type": "enum",
- "values": [
- {
- "value": "staticspeed",
- "description": "Linear movement to setpoint."
- },
- {
- "value": "dynspeed",
- "description": "Ease-in and ease-out to setpoint."
- }
- ]
- }
- },
- "speed": {
- "default": 1,
- "rules": {
- "type": "number",
- "description": "Current speed setting."
- }
- },
- "maxSpeed": {
- "default": 10,
- "rules": {
- "type": "number",
- "description": "Maximum speed setting."
- }
- },
- "interval": {
- "default": 1000,
- "rules": {
- "type": "number",
- "description": "Feedback interval in milliseconds."
- }
- }
- },
- "position": {
- "min": {
- "default": 0,
- "rules": {
- "type": "number",
- "description": "Minimum position value."
- }
- },
- "max": {
- "default": 100,
- "rules": {
- "type": "number",
- "description": "Maximum position value."
- }
- },
- "initial": {
- "default": 0,
- "rules": {
- "type": "number",
- "description": "Initial position value."
- }
- }
- },
- "state": {
- "current": {
- "default": "idle",
- "rules": {
- "type": "enum",
- "values": [
- {
- "value": "idle",
- "description": "Machine is idle."
- },
- {
- "value": "starting",
- "description": "Machine is starting up."
- },
- {
- "value": "warmingup",
- "description": "Machine is warming up."
- },
- {
- "value": "operational",
- "description": "Machine is running."
- },
- {
- "value": "accelerating",
- "description": "Machine is accelerating."
- },
- {
- "value": "decelerating",
- "description": "Machine is decelerating."
- },
- {
- "value": "stopping",
- "description": "Machine is stopping."
- },
- {
- "value": "coolingdown",
- "description": "Machine is cooling down."
- },
- {
- "value": "off",
- "description": "Machine is off."
- }
- ],
- "description": "Current state of the machine."
- }
- },
- "allowedTransitions":{
- "default": {},
- "rules": {
- "type": "object",
- "schema": {
- "idle": {
- "default": ["starting", "off","emergencystop"],
- "rules":{
- "type": "set",
- "itemType": "string",
- "description": "Allowed transitions from idle state."
- }
- },
- "starting": {
- "default": ["starting","warmingup","emergencystop"],
- "rules":{
- "type": "set",
- "itemType": "string",
- "description": "Allowed transitions from starting state."
- }
- },
- "warmingup": {
- "default": ["operational","emergencystop"],
- "rules":{
- "type": "set",
- "itemType": "string",
- "description": "Allowed transitions from warmingup state."
- }
- },
- "operational": {
- "default": ["accelerating", "decelerating", "stopping","emergencystop"],
- "rules":{
- "type": "set",
- "itemType": "string",
- "description": "Allowed transitions from operational state."
- }
- },
- "accelerating": {
- "default": ["operational","emergencystop"],
- "rules":{
- "type": "set",
- "itemType": "string",
- "description": "Allowed transitions from accelerating state."
- }
- },
- "decelerating": {
- "default": ["operational","emergencystop"],
- "rules":{
- "type": "set",
- "itemType": "string",
- "description": "Allowed transitions from decelerating state."
- }
- },
- "stopping": {
- "default": ["idle","coolingdown","emergencystop"],
- "rules":{
- "type": "set",
- "itemType": "string",
- "description": "Allowed transitions from stopping state."
- }
- },
- "coolingdown": {
- "default": ["idle","off","emergencystop"],
- "rules":{
- "type": "set",
- "itemType": "string",
- "description": "Allowed transitions from coolingDown state."
- }
- },
- "off": {
- "default": ["idle","emergencystop"],
- "rules":{
- "type": "set",
- "itemType": "string",
- "description": "Allowed transitions from off state."
- }
- },
- "emergencystop": {
- "default": ["idle","off"],
- "rules":{
- "type": "set",
- "itemType": "string",
- "description": "Allowed transitions from emergency stop state."
- }
- }
- },
- "description": "Allowed transitions between states."
- }
- },
- "activeStates":{
- "default": ["operational", "starting", "warmingup", "accelerating", "decelerating"],
- "rules": {
- "type": "set",
- "itemType": "string",
- "description": "Active states."
- }
- }
- },
- "mode": {
- "current": {
- "default": "auto",
- "rules": {
- "type": "enum",
- "values": [
- {
- "value": "auto",
- "description": "Automatically tracks and handles delayed commands for setpoints > 0."
- },
- {
- "value": "manual",
- "description": "Requires explicit commands to start."
- }
- ],
- "description": "Current mode of the machine."
- }
- }
- }
- }
-
\ No newline at end of file
diff --git a/helper/state/stateManager.js b/helper/state/stateManager.js
deleted file mode 100644
index 4549308..0000000
--- a/helper/state/stateManager.js
+++ /dev/null
@@ -1,164 +0,0 @@
-/**
- * @file stateManager.js
- *
- * Permission is hereby granted to any person obtaining a copy of this software
- * and associated documentation files (the "Software"), to use it for personal
- * or non-commercial purposes, with the following restrictions:
- *
- * 1. **No Copying or Redistribution**: The Software or any of its parts may not
- * be copied, merged, distributed, sublicensed, or sold without explicit
- * prior written permission from the author.
- *
- * 2. **Commercial Use**: Any use of the Software for commercial purposes requires
- * a valid license, obtainable only with the explicit consent of the author.
- *
- * 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.
- *
- * Ownership of this code remains solely with the original author. Unauthorized
- * use of this Software is strictly prohibited.
- *
- * @summary Class for managing state transitions and state descriptions.
- * @description Class for managing state transitions and state descriptions.
- * @module stateManager
- * @exports stateManager
- * @version 0.1.0
- * @since 0.1.0
- *
- * Author:
- * - Rene De Ren
- * Email:
- * - rene@thegoldenbasket.nl
- */
-
-class stateManager {
- constructor(config, logger) {
- this.currentState = config.state.current;
- this.availableStates = config.state.available;
- this.descriptions = config.state.descriptions;
- this.logger = logger;
- this.transitionTimeleft = 0;
- this.transitionTimes = config.time;
-
- // Define valid transitions (can be extended dynamically if needed)
- this.validTransitions = config.state.allowedTransitions;
-
- // NEW: Initialize runtime tracking
- this.runTimeHours = 0; // cumulative runtime in hours
- this.runTimeStart = null; // timestamp when active state began
-
- // Define active states (runtime counts only in these states)
- this.activeStates = config.state.activeStates;
- }
-
- getCurrentState() {
- return this.currentState;
- }
-
- transitionTo(newState,signal) {
- return new Promise((resolve, reject) => {
- if (signal && signal.aborted) {
- this.logger.debug("Transition aborted.");
- return reject("Transition aborted.");
- }
-
- if (!this.isValidTransition(newState)) {
- return reject(
- `Invalid transition from ${this.currentState} to ${newState}. Transition not executed.`
- ); //go back early and reject promise
- }
-
- // NEW: Handle runtime tracking based on active states
- this.handleRuntimeTracking(newState);
-
- const transitionDuration = this.transitionTimes[this.currentState] || 0; // Default to 0 if no transition time
- this.logger.debug(
- `Transition from ${this.currentState} to ${newState} will take ${transitionDuration}s.`
- );
-
- if (transitionDuration > 0) {
- const timeoutId = setTimeout(() => {
- this.currentState = newState;
- resolve(`Transition from ${this.currentState} to ${newState} completed in ${transitionDuration}s.`);
- }, transitionDuration * 1000);
- if (signal) {
- signal.addEventListener('abort', () => {
- clearTimeout(timeoutId);
- reject(new Error('Transition aborted'));
- });
- }
- } else {
- this.currentState = newState;
- resolve(`Immediate transition to ${this.currentState} completed.`);
- }
- });
- }
-
- handleRuntimeTracking(newState) {
- // NEW: Handle runtime tracking based on active states
- const wasActive = this.activeStates.has(this.currentState);
- const willBeActive = this.activeStates.has(newState);
- if (wasActive && !willBeActive && this.runTimeStart) {
- // stop runtime timer and accumulate elapsed time
- const elapsed = (Date.now() - this.runTimeStart) / 3600000; // hours
- this.runTimeHours += elapsed;
- this.runTimeStart = null;
- this.logger.debug(
- `Runtime timer stopped; elapsed=${elapsed.toFixed(
- 3
- )}h, total=${this.runTimeHours.toFixed(3)}h.`
- );
- } else if (!wasActive && willBeActive && !this.runTimeStart) {
- // starting new runtime
- this.runTimeStart = Date.now();
- this.logger.debug("Runtime timer started.");
- }
- }
-
- isValidTransition(newState) {
- this.logger.debug(
- `Check 1 Transition valid ? From ${
- this.currentState
- } To ${newState} => ${this.validTransitions[this.currentState]?.has(
- newState
- )} `
- );
- this.logger.debug(
- `Check 2 Transition valid ? ${
- this.currentState
- } is not equal to ${newState} => ${this.currentState !== newState}`
- );
- // check if transition is valid and not the same as before
- const valid =
- this.validTransitions[this.currentState]?.has(newState) &&
- this.currentState !== newState;
-
- //if not valid
- if (!valid) {
- return false;
- } else {
- return true;
- }
- }
-
- getStateDescription(state = this.currentState) {
- return this.descriptions[state] || "No description available.";
- }
-
- // NEW: Getter to retrieve current cumulative runtime (active time) in hours.
- getRunTimeHours() {
- // If currently active add the ongoing duration.
- let currentElapsed = 0;
- if (this.runTimeStart) {
- currentElapsed = (Date.now() - this.runTimeStart) / 3600000;
- }
- return this.runTimeHours + currentElapsed;
- }
-}
-
-module.exports = stateManager;
diff --git a/helper/validationUtils.js b/helper/validationUtils.js
deleted file mode 100644
index 08f31e6..0000000
--- a/helper/validationUtils.js
+++ /dev/null
@@ -1,528 +0,0 @@
-/**
- * @file validation.js
- *
- * Permission is hereby granted to any person obtaining a copy of this software
- * and associated documentation files (the "Software"), to use it for personal
- * or non-commercial purposes, with the following restrictions:
- *
- * 1. **No Copying or Redistribution**: The Software or any of its parts may not
- * be copied, merged, distributed, sublicensed, or sold without explicit
- * prior written permission from the author.
- *
- * 2. **Commercial Use**: Any use of the Software for commercial purposes requires
- * a valid license, obtainable only with the explicit consent of the author.
- *
- * 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.
- *
- * Ownership of this code remains solely with the original author. Unauthorized
- * use of this Software is strictly prohibited.
-
- * @summary Validation utility for validating and constraining configuration values.
- * @description Validation utility for validating and constraining configuration values.
- * @module ValidationUtils
- * @requires Logger
- * @exports ValidationUtils
- * @version 0.1.0
- * @since 0.1.0
-*/
-
-const Logger = require("./logger");
-
-class ValidationUtils {
- constructor(IloggerEnabled, IloggerLevel) {
- const loggerEnabled = IloggerEnabled || true;
- const loggerLevel = IloggerLevel || "warn";
- this.logger = new Logger(loggerEnabled, loggerLevel, 'ValidationUtils');
- }
-
- constrain(value, min, max) {
- if (typeof value !== "number") {
- this.logger?.warn(`Value '${value}' is not a number. Defaulting to ${min}.`);
- return min;
- }
- return Math.min(Math.max(value, min), max);
- }
-
- validateSchema(config, schema, name) {
-
- const validatedConfig = {};
- let configValue;
-
- // 1. Remove any unknown keys (keys not defined in the schema).
- // Log a warning and omit them from the final config.
- for (const key of Object.keys(config)) {
- if (!(key in schema)) {
- this.logger.warn(
- `[${name}] Unknown key '${key}' found in config. Removing it.`
- );
- delete config[key];
- }
- }
-
- // Validate each key in the schema and loop over wildcards if they are not in schema
- for ( const key in schema ) {
-
- if (key === "rules" || key === "description" || key === "schema") {
- continue;
- }
-
- const fieldSchema = schema[key];
- const { rules = {} } = fieldSchema;
-
- // Default to the schema's default value if the key is missing
- if (config[key] === undefined) {
- if (fieldSchema.default === undefined) {
- // If there's a nested schema, go deeper with an empty object rather than logging "no rule"
- if (rules.schema) {
- this.logger.warn(`${name}.${key} has no default, but has a nested schema.`);
- validatedConfig[key] = this.validateSchema({}, rules.schema, `${name}.${key}`);
- }
- else {
- this.logger.info(
- `There is no rule for ${name}.${key} and no default value. ` +
- `Using full schema value but validating deeper levels first...`
- );
-
- const SubObject = this.validateSchema({}, fieldSchema, `${name}.${key}`);
-
- validatedConfig[key] = SubObject;
-
- continue;
- }
- } else {
- this.logger.info(`There is no value provided for ${name}.${key}. Using default value.`);
- configValue = fieldSchema.default;
- }
- //continue;
- } else {
- // Use the provided value if it exists, otherwise use the default value
- configValue = config[key] !== undefined ? config[key] : fieldSchema.default;
- }
-
- // Attempt to parse the value to the expected type if possible
- switch (rules.type) {
-
- case "number":
- configValue = this.validateNumber(configValue, rules, fieldSchema, name, key);
- break;
- case "boolean":
- configValue = this.validateBoolean(configValue, name, key);
- break;
-
- case "string":
- configValue = this.validateString(configValue,rules,fieldSchema, name, key);
- break;
-
- case "array":
- configValue = this.validateArray(configValue, rules, fieldSchema, name, key);
- break;
-
- case "set":
- configValue = this.validateSet(configValue, rules, fieldSchema, name, key);
- break;
-
- case "object":
- configValue = this.validateObject(configValue, rules, fieldSchema, name, key);
- break;
-
- case "enum":
- configValue = this.validateEnum(configValue, rules, fieldSchema, name, key);
- break;
-
- case "curve":
- validatedConfig[key] = this.validateCurve(configValue,fieldSchema.default);
- continue;
-
- case "machineCurve":
- validatedConfig[key] = this.validateMachineCurve(configValue,fieldSchema.default);
- continue;
-
- case "integer":
- validatedConfig[key] = this.validateInteger(configValue, rules, fieldSchema, name, key);
- continue;
-
- case undefined:
- // If we see 'rules.schema' but no 'rules.type', treat it like an object:
- if (rules.schema && !rules.type) {
- // Log a warning and skip the extra pass for nested schema
- this.logger.warn(
- `${name}.${key} has a nested schema but no type. ` +
- `Treating it as type="object" to skip extra pass.`
- );
- } else {
- // Otherwise, fallback to your existing "validateUndefined" logic
- validatedConfig[key] = this.validateUndefined(configValue, fieldSchema, name, key);
- }
- continue;
-
- default:
- this.logger.warn(`${name}.${key} has an unknown validation type: ${rules.type}. Skipping validation.`);
- validatedConfig[key] = fieldSchema.default;
- continue;
- }
-
- // Assign the validated or converted value
- validatedConfig[key] = configValue;
- }
-
- // Ignore unknown keys by not processing them at all
- this.logger.info(`Validation completed for ${name}.`);
-
- return validatedConfig;
- }
-
- removeUnwantedKeys(obj) {
-
- if (Array.isArray(obj)) {
- return obj.map((item) => this.removeUnwantedKeys(item));
- }
- if (obj && typeof obj === "object") {
- const newObj = {};
- for (const [k, v] of Object.entries(obj)) {
-
- // Skip or remove keys like 'default', 'rules', 'description', etc.
- if (["rules", "description"].includes(k)) {
- continue;
- }
-
- if("default" in v){
- //put the default value in the object
- newObj[k] = v.default;
- continue;
- }
-
- newObj[k] = this.removeUnwantedKeys(v);
- }
- return newObj;
- }
- return obj;
- }
-
- validateMachineCurve(curve, defaultCurve) {
- if (!curve || typeof curve !== "object" || Object.keys(curve).length === 0) {
- this.logger.warn("Curve is missing or invalid. Defaulting to basic curve.");
- return defaultCurve;
- }
-
- // Validate that nq and np exist and are objects
- const { nq, np } = curve;
- if (!nq || typeof nq !== "object" || !np || typeof np !== "object") {
- this.logger.warn("Curve must contain valid 'nq' and 'np' objects. Defaulting to basic curve.");
- return defaultCurve;
- }
-
- // Validate that each dimension key points to a valid object with x and y arrays
- const validatedNq = this.validateDimensionStructure(nq, "nq");
- const validatedNp = this.validateDimensionStructure(np, "np");
-
- if (!validatedNq || !validatedNp) {
- return defaultCurve;
- }
-
- return { nq: validatedNq, np: validatedNp }; // Return the validated curve
- }
-
- validateCurve(curve, defaultCurve) {
- if (!curve || typeof curve !== "object" || Object.keys(curve).length === 0) {
- this.logger.warn("Curve is missing or invalid. Defaulting to basic curve.");
- return defaultCurve;
- }
-
- // Validate that each dimension key points to a valid object with x and y arrays
- const validatedCurve = this.validateDimensionStructure(curve, "curve");
- if (!validatedCurve) {
- return defaultCurve;
- }
-
- return validatedCurve; // Return the validated curve
- }
-
- validateDimensionStructure(dimension, name) {
- const validatedDimension = {};
-
- for (const [key, value] of Object.entries(dimension)) {
- // Validate that each key points to an object with x and y arrays
- if (typeof value !== "object") {
- this.logger.warn(`Dimension '${name}' key '${key}' is not valid. Returning to default.`);
- return false;
- }
- // Validate that x and y are arrays
- else if (!Array.isArray(value.x) || !Array.isArray(value.y)) {
- this.logger.warn(`Dimension '${name}' key '${key}' is missing x or y arrays. Converting to arrays.`);
- // Try to convert to arrays first
- value.x = Object.values(value.x);
- value.y = Object.values(value.y);
-
- // If still not arrays return false
- if (!Array.isArray(value.x) || !Array.isArray(value.y)) {
- this.logger.warn(`Dimension '${name}' key '${key}' is not valid. Returning to default.`);
- return false;
- }
- }
- // Validate that x and y arrays are the same length
- else if (value.x.length !== value.y.length) {
- this.logger.warn(`Dimension '${name}' key '${key}' has mismatched x and y lengths. Ignoring this key.`);
- return false;
- }
- // Validate that x values are in ascending order
- else if (!this.isSorted(value.x)) {
- this.logger.warn(`Dimension '${name}' key '${key}' has unsorted x values. Sorting...`);
- return false;
- }
- // Validate that x values are unique
- else if (!this.isUnique(value.x)) {
- this.logger.warn(`Dimension '${name}' key '${key}' has duplicate x values. Removing duplicates...`);
- return false;
- }
- // Validate that y values are numbers
- else if (!this.areNumbers(value.y)) {
- this.logger.warn(`Dimension '${name}' key '${key}' has non-numeric y values. Ignoring this key.`);
- return false;
- }
-
- validatedDimension[key] = value;
- }
- return validatedDimension;
- }
-
- isSorted(arr) {
- return arr.every((_, i) => i === 0 || arr[i] >= arr[i - 1]);
- }
-
- isUnique(arr) {
- return new Set(arr).size === arr.length;
- }
-
- areNumbers(arr) {
- return arr.every((x) => typeof x === "number");
- }
-
- validateNumber(configValue, rules, fieldSchema, name, key) {
-
- if (typeof configValue !== "number") {
- const parsedValue = parseFloat(configValue);
- if (!isNaN(parsedValue)) {
- this.logger.warn(`${name}.${key} was parsed to a number: ${configValue} -> ${parsedValue}`);
- configValue = parsedValue;
- }
- }
-
- if (rules.min !== undefined && configValue < rules.min) {
- this.logger.warn(
- `${name}.${key} is below the minimum (${rules.min}). Using default value.`
- );
- return fieldSchema.default;
- }
- if (rules.max !== undefined && configValue > rules.max) {
- this.logger.warn(
- `${name}.${key} exceeds the maximum (${rules.max}). Using default value.`
- );
- return fieldSchema.default;
- }
-
- this.logger.debug(`${name}.${key} is a valid number: ${configValue}`);
-
- return configValue;
- }
-
-
- validateInteger(configValue, rules, fieldSchema, name, key) {
- if (typeof configValue !== "number" || !Number.isInteger(configValue)) {
- const parsedValue = parseInt(configValue, 10);
- if (!isNaN(parsedValue) && Number.isInteger(parsedValue)) {
- this.logger.warn(`${name}.${key} was parsed to an integer: ${configValue} -> ${parsedValue}`);
- configValue = parsedValue;
- } else {
- this.logger.warn(`${name}.${key} is not a valid integer. Using default value.`);
- return fieldSchema.default;
- }
- }
-
- if (rules.min !== undefined && configValue < rules.min) {
- this.logger.warn(`${name}.${key} is below the minimum integer value (${rules.min}). Using default value.`);
- return fieldSchema.default;
- }
-
- if (rules.max !== undefined && configValue > rules.max) {
- this.logger.warn(`${name}.${key} exceeds the maximum integer value (${rules.max}). Using default value.`);
- return fieldSchema.default;
- }
-
- this.logger.debug(`${name}.${key} is a valid integer: ${configValue}`);
- return configValue;
- }
-
- validateBoolean(configValue, name, key) {
- if (typeof configValue !== "boolean") {
- if (configValue === "true" || configValue === "false") {
- const parsedValue = configValue === "true";
- this.logger.debug(`${name}.${key} was parsed to a boolean: ${configValue} -> ${parsedValue}`);
- configValue = parsedValue;
- }
- }
- return configValue;
- }
-
- validateString(configValue, rules, fieldSchema, name, key) {
- let newConfigValue = configValue;
-
- if (typeof configValue !== "string") {
- //check if the value is nullable
- if(rules.nullable){
- if(configValue === null){
- return null;
- }
- }
-
- this.logger.warn(`${name}.${key} is not a string. Trying to convert to string.`);
- newConfigValue = String(configValue); // Coerce to string if not already
- }
-
- //check if the string is a valid string after conversion
- if (typeof newConfigValue !== "string") {
- this.logger.warn(`${name}.${key} is not a valid string. Using default value.`);
- return fieldSchema.default;
- }
-
- return newConfigValue;
- }
-
- validateSet(configValue, rules, fieldSchema, name, key) {
- // 1. Ensure we have a Set. If not, use default.
- if (!(configValue instanceof Set)) {
- this.logger.info(`${name}.${key} is not a Set. Converting to one using default value.`);
- return new Set(fieldSchema.default);
- }
-
- // 2. Convert the Set to an array for easier filtering.
- const validatedArray = [...configValue]
- .filter((item) => {
- // 3. Filter based on `rules.itemType`.
- switch (rules.itemType) {
- case "number":
- return typeof item === "number";
- case "string":
- return typeof item === "string";
- case "null":
- // "null" might mean no type restriction (your usage may vary).
- return true;
- default:
- // Fallback if itemType is something else
- return typeof item === rules.itemType;
- }
- })
- .slice(0, rules.maxLength || Infinity);
-
- // 4. Check if the filtered array meets the minimum length.
- if (validatedArray.length < (rules.minLength || 1)) {
- this.logger.warn(
- `${name}.${key} contains fewer items than allowed (${rules.minLength}). Using default value.`
- );
- return new Set(fieldSchema.default);
- }
-
- // 5. Return a new Set containing only the valid items.
- return new Set(validatedArray);
- }
-
- validateArray(configValue, rules, fieldSchema, name, key) {
- if (!Array.isArray(configValue)) {
- this.logger.info(`${name}.${key} is not an array. Using default value.`);
- return fieldSchema.default;
- }
-
- // Validate individual items in the array
- const validatedArray = configValue
- .filter((item) => {
- switch (rules.itemType) {
- case "number":
- return typeof item === "number";
- case "string":
- return typeof item === "string";
- case "null":
- // anything goes
- return true;
- default:
- return typeof item === rules.itemType;
- }
- })
- .slice(0, rules.maxLength || Infinity);
-
- if (validatedArray.length < (rules.minLength || 1)) {
- this.logger.warn(
- `${name}.${key} contains fewer items than allowed (${rules.minLength}). Using default value.`
- );
- return fieldSchema.default;
- }
-
- return validatedArray;
- }
-
- validateObject(configValue, rules, fieldSchema, name, key) {
- if (typeof configValue !== "object" || Array.isArray(configValue)) {
- this.logger.warn(`${name}.${key} is not a valid object. Using default value.`);
- return fieldSchema.default;
- }
-
- if (rules.schema) {
- // Recursively validate nested objects if a schema is defined
- return this.validateSchema(configValue || {}, rules.schema, `${name}.${key}`);
- } else {
- // If no schema is defined, log a warning and use the default
- this.logger.warn(`${name}.${key} is an object with no schema. Using default value.`);
- return fieldSchema.default;
- }
- }
-
- validateEnum(configValue, rules, fieldSchema, name, key) {
-
- if (Array.isArray(rules.values)) {
-
- //if value is null take default
- if(configValue === null){
- this.logger.warn(`${name}.${key} is null. Using default value.`);
- return fieldSchema.default;
- }
-
- const validValues = rules.values.map(e => e.value.toLowerCase());
-
- //remove caps
- configValue = configValue.toLowerCase();
-
- if (!validValues.includes(configValue)) {
- this.logger.warn(
- `${name}.${key} has an invalid value : ${configValue}. Allowed values: [${validValues.join(", ")}]. Using default value.`
- );
- return fieldSchema.default;
- }
- } else {
- this.logger.warn(
- `${name}.${key} is an enum with no 'values' array. Using default value.`
- );
- return fieldSchema.default;
- }
- return configValue;
- }
-
- validateUndefined(configValue, fieldSchema, name, key) {
- if (typeof configValue === "object" && !Array.isArray(configValue)) {
-
- this.logger.debug(`${name}.${key} has no defined rules but is an object going 1 level deeper.`);
-
- // Recursively validate the nested object
- return this.validateSchema( configValue || {}, fieldSchema, `${name}.${key}`);
- }
- else {
- this.logger.warn(`${name}.${key} has no defined rules. Using default value.`);
- return fieldSchema.default;
- }
- }
-}
-
-module.exports = ValidationUtils;
diff --git a/index.js b/index.js
index ac75563..97425f8 100644
--- a/index.js
+++ b/index.js
@@ -7,13 +7,15 @@
// Core helper modules
const menuUtils = require('./src/helper/menuUtils.js');
+const outputUtils = require('./src/helper/outputUtils.js');
const logger = require('./src/helper/logger.js');
const validation = require('./src/helper/validationUtils.js');
+const configUtils = require('./src/helper/configUtils.js');
// Domain-specific modules
const measurements = require('./src/measurements/index.js');
-const nrmse = require('./src/nrmse/index.js');
-const state = require('./src/state/index.js');
+const nrmse = require('./src/nrmse/ErrorMetrics.js');
+const state = require('./src/state/state.js');
// Configuration loader with error handling
function loadConfig(path) {
@@ -59,6 +61,8 @@ function loadAssetDatasets() {
// Export everything
module.exports = {
menuUtils,
+ outputUtils,
+ configUtils,
logger,
validation,
measurements,
diff --git a/src/convert/definitions/angle.js b/src/convert/definitions/angle.js
new file mode 100644
index 0000000..a2f091f
--- /dev/null
+++ b/src/convert/definitions/angle.js
@@ -0,0 +1,49 @@
+var angle;
+
+angle = {
+ rad: {
+ name: {
+ singular: 'radian'
+ , plural: 'radians'
+ }
+ , to_anchor: 180/Math.PI
+ }
+, deg: {
+ name: {
+ singular: 'degree'
+ , plural: 'degrees'
+ }
+ , to_anchor: 1
+ }
+, grad: {
+ name: {
+ singular: 'gradian'
+ , plural: 'gradians'
+ }
+ , to_anchor: 9/10
+ }
+, arcmin: {
+ name: {
+ singular: 'arcminute'
+ , plural: 'arcminutes'
+ }
+ , to_anchor: 1/60
+ }
+, arcsec: {
+ name: {
+ singular: 'arcsecond'
+ , plural: 'arcseconds'
+ }
+ , to_anchor: 1/3600
+ }
+};
+
+module.exports = {
+ metric: angle
+, _anchors: {
+ metric: {
+ unit: 'deg'
+ , ratio: 1
+ }
+ }
+};
diff --git a/src/convert/definitions/apparentPower.js b/src/convert/definitions/apparentPower.js
new file mode 100644
index 0000000..58d35c6
--- /dev/null
+++ b/src/convert/definitions/apparentPower.js
@@ -0,0 +1,49 @@
+var apparentPower;
+
+apparentPower = {
+ VA: {
+ name: {
+ singular: 'Volt-Ampere'
+ , plural: 'Volt-Amperes'
+ }
+ , to_anchor: 1
+ }
+, mVA: {
+ name: {
+ singular: 'Millivolt-Ampere'
+ , plural: 'Millivolt-Amperes'
+ }
+ , to_anchor: .001
+ }
+, kVA: {
+ name: {
+ singular: 'Kilovolt-Ampere'
+ , plural: 'Kilovolt-Amperes'
+ }
+ , to_anchor: 1000
+ }
+, MVA: {
+ name: {
+ singular: 'Megavolt-Ampere'
+ , plural: 'Megavolt-Amperes'
+ }
+ , to_anchor: 1000000
+ }
+, GVA: {
+ name: {
+ singular: 'Gigavolt-Ampere'
+ , plural: 'Gigavolt-Amperes'
+ }
+ , to_anchor: 1000000000
+ }
+};
+
+module.exports = {
+ metric: apparentPower
+, _anchors: {
+ metric: {
+ unit: 'VA'
+ , ratio: 1
+ }
+ }
+};
diff --git a/src/convert/definitions/area.js b/src/convert/definitions/area.js
new file mode 100644
index 0000000..20b374d
--- /dev/null
+++ b/src/convert/definitions/area.js
@@ -0,0 +1,93 @@
+var metric
+ , imperial;
+
+metric = {
+ mm2: {
+ name: {
+ singular: 'Square Millimeter'
+ , plural: 'Square Millimeters'
+ }
+ , to_anchor: 1/1000000
+ }
+, cm2: {
+ name: {
+ singular: 'Centimeter'
+ , plural: 'Centimeters'
+ }
+ , to_anchor: 1/10000
+ }
+, m2: {
+ name: {
+ singular: 'Square Meter'
+ , plural: 'Square Meters'
+ }
+ , to_anchor: 1
+ }
+, ha: {
+ name: {
+ singular: 'Hectare'
+ , plural: 'Hectares'
+ }
+ , to_anchor: 10000
+ }
+, km2: {
+ name: {
+ singular: 'Square Kilometer'
+ , plural: 'Square Kilometers'
+ }
+ , to_anchor: 1000000
+ }
+};
+
+imperial = {
+ 'in2': {
+ name: {
+ singular: 'Square Inch'
+ , plural: 'Square Inches'
+ }
+ , to_anchor: 1/144
+ }
+, yd2: {
+ name: {
+ singular: 'Square Yard'
+ , plural: 'Square Yards'
+ }
+ , to_anchor: 9
+ }
+, ft2: {
+ name: {
+ singular: 'Square Foot'
+ , plural: 'Square Feet'
+ }
+ , to_anchor: 1
+ }
+, ac: {
+ name: {
+ singular: 'Acre'
+ , plural: 'Acres'
+ }
+ , to_anchor: 43560
+ }
+, mi2: {
+ name: {
+ singular: 'Square Mile'
+ , plural: 'Square Miles'
+ }
+ , to_anchor: 27878400
+ }
+};
+
+module.exports = {
+ metric: metric
+, imperial: imperial
+, _anchors: {
+ metric: {
+ unit: 'm2'
+ , ratio: 10.7639
+ }
+ , imperial: {
+ unit: 'ft2'
+ , ratio: 1/10.7639
+ }
+ }
+};
diff --git a/src/convert/definitions/current.js b/src/convert/definitions/current.js
new file mode 100644
index 0000000..05775df
--- /dev/null
+++ b/src/convert/definitions/current.js
@@ -0,0 +1,35 @@
+var current;
+
+current = {
+ A: {
+ name: {
+ singular: 'Ampere'
+ , plural: 'Amperes'
+ }
+ , to_anchor: 1
+ }
+, mA: {
+ name: {
+ singular: 'Milliampere'
+ , plural: 'Milliamperes'
+ }
+ , to_anchor: .001
+ }
+, kA: {
+ name: {
+ singular: 'Kiloampere'
+ , plural: 'Kiloamperes'
+ }
+ , to_anchor: 1000
+ }
+};
+
+module.exports = {
+ metric: current
+, _anchors: {
+ metric: {
+ unit: 'A'
+ , ratio: 1
+ }
+ }
+};
diff --git a/src/convert/definitions/digital.js b/src/convert/definitions/digital.js
new file mode 100644
index 0000000..e63a4a6
--- /dev/null
+++ b/src/convert/definitions/digital.js
@@ -0,0 +1,93 @@
+var bits
+ , bytes;
+
+bits = {
+ b: {
+ name: {
+ singular: 'Bit'
+ , plural: 'Bits'
+ }
+ , to_anchor: 1
+ }
+, Kb: {
+ name: {
+ singular: 'Kilobit'
+ , plural: 'Kilobits'
+ }
+ , to_anchor: 1024
+ }
+, Mb: {
+ name: {
+ singular: 'Megabit'
+ , plural: 'Megabits'
+ }
+ , to_anchor: 1048576
+ }
+, Gb: {
+ name: {
+ singular: 'Gigabit'
+ , plural: 'Gigabits'
+ }
+ , to_anchor: 1073741824
+ }
+, Tb: {
+ name: {
+ singular: 'Terabit'
+ , plural: 'Terabits'
+ }
+ , to_anchor: 1099511627776
+ }
+};
+
+bytes = {
+ B: {
+ name: {
+ singular: 'Byte'
+ , plural: 'Bytes'
+ }
+ , to_anchor: 1
+ }
+, KB: {
+ name: {
+ singular: 'Kilobyte'
+ , plural: 'Kilobytes'
+ }
+ , to_anchor: 1024
+ }
+, MB: {
+ name: {
+ singular: 'Megabyte'
+ , plural: 'Megabytes'
+ }
+ , to_anchor: 1048576
+ }
+, GB: {
+ name: {
+ singular: 'Gigabyte'
+ , plural: 'Gigabytes'
+ }
+ , to_anchor: 1073741824
+ }
+, TB: {
+ name: {
+ singular: 'Terabyte'
+ , plural: 'Terabytes'
+ }
+ , to_anchor: 1099511627776
+ }
+};
+
+module.exports = {
+ bits: bits
+, bytes: bytes
+, _anchors: {
+ bits: {
+ unit: 'b'
+ , ratio: 1/8
+ }
+ , bytes: {
+ unit: 'B'
+ , ratio: 8
+ }
+ }
+};
diff --git a/src/convert/definitions/each.js b/src/convert/definitions/each.js
new file mode 100644
index 0000000..0f4f331
--- /dev/null
+++ b/src/convert/definitions/each.js
@@ -0,0 +1,30 @@
+var metric
+ , imperial;
+
+metric = {
+ ea: {
+ name: {
+ singular: 'Each'
+ , plural: 'Each'
+ }
+ , to_anchor: 1
+ },
+ dz: {
+ name: {
+ singular: 'Dozen'
+ , plural: 'Dozens'
+ }
+ , to_anchor: 12
+ }
+};
+
+module.exports = {
+ metric: metric
+, imperial: {}
+, _anchors: {
+ metric: {
+ unit: 'ea'
+ , ratio: 1
+ }
+ }
+};
diff --git a/src/convert/definitions/energy.js b/src/convert/definitions/energy.js
new file mode 100644
index 0000000..f885df7
--- /dev/null
+++ b/src/convert/definitions/energy.js
@@ -0,0 +1,63 @@
+var energy;
+
+energy = {
+ Wh: {
+ name: {
+ singular: 'Watt-hour'
+ , plural: 'Watt-hours'
+ }
+ , to_anchor: 3600
+ }
+, mWh: {
+ name: {
+ singular: 'Milliwatt-hour'
+ , plural: 'Milliwatt-hours'
+ }
+ , to_anchor: 3.6
+ }
+, kWh: {
+ name: {
+ singular: 'Kilowatt-hour'
+ , plural: 'Kilowatt-hours'
+ }
+ , to_anchor: 3600000
+ }
+, MWh: {
+ name: {
+ singular: 'Megawatt-hour'
+ , plural: 'Megawatt-hours'
+ }
+ , to_anchor: 3600000000
+ }
+, GWh: {
+ name: {
+ singular: 'Gigawatt-hour'
+ , plural: 'Gigawatt-hours'
+ }
+ , to_anchor: 3600000000000
+ }
+, J: {
+ name: {
+ singular: 'Joule'
+ , plural: 'Joules'
+ }
+ , to_anchor: 1
+ }
+, kJ: {
+ name: {
+ singular: 'Kilojoule'
+ , plural: 'Kilojoules'
+ }
+ , to_anchor: 1000
+ }
+};
+
+module.exports = {
+ metric: energy
+, _anchors: {
+ metric: {
+ unit: 'J'
+ , ratio: 1
+ }
+ }
+};
diff --git a/src/convert/definitions/frequency.js b/src/convert/definitions/frequency.js
new file mode 100644
index 0000000..c292e8b
--- /dev/null
+++ b/src/convert/definitions/frequency.js
@@ -0,0 +1,78 @@
+var frequency;
+
+frequency = {
+ mHz: {
+ name: {
+ singular: 'millihertz'
+ , plural: 'millihertz'
+ }
+ , to_anchor: 1/1000
+ }
+, Hz: {
+ name: {
+ singular: 'hertz'
+ , plural: 'hertz'
+ }
+ , to_anchor: 1
+ }
+, kHz: {
+ name: {
+ singular: 'kilohertz'
+ , plural: 'kilohertz'
+ }
+ , to_anchor: 1000
+ }
+, MHz: {
+ name: {
+ singular: 'megahertz'
+ , plural: 'megahertz'
+ }
+ , to_anchor: 1000 * 1000
+ }
+, GHz: {
+ name: {
+ singular: 'gigahertz'
+ , plural: 'gigahertz'
+ }
+ , to_anchor: 1000 * 1000 * 1000
+ }
+, THz: {
+ name: {
+ singular: 'terahertz'
+ , plural: 'terahertz'
+ }
+ , to_anchor: 1000 * 1000 * 1000 * 1000
+ }
+, rpm: {
+ name: {
+ singular: 'rotation per minute'
+ , plural: 'rotations per minute'
+ }
+ , to_anchor: 1/60
+ }
+, "deg/s": {
+ name: {
+ singular: 'degree per second'
+ , plural: 'degrees per second'
+ }
+ , to_anchor: 1/360
+ }
+, "rad/s": {
+ name: {
+ singular: 'radian per second'
+ , plural: 'radians per second'
+ }
+ , to_anchor: 1/(Math.PI * 2)
+ }
+};
+
+
+module.exports = {
+ metric: frequency
+, _anchors: {
+ frequency: {
+ unit: 'hz'
+ , ratio: 1
+ }
+ }
+};
diff --git a/src/convert/definitions/illuminance.js b/src/convert/definitions/illuminance.js
new file mode 100644
index 0000000..eefb1f3
--- /dev/null
+++ b/src/convert/definitions/illuminance.js
@@ -0,0 +1,37 @@
+var metric,
+imperial;
+
+metric = {
+ 'lx': {
+ name: {
+ singular: 'Lux',
+ plural: 'Lux'
+ },
+ to_anchor: 1
+ }
+};
+
+imperial = {
+ 'ft-cd': {
+ name: {
+ singular: 'Foot-candle',
+ plural: 'Foot-candles'
+ },
+ to_anchor: 1
+ }
+};
+
+module.exports = {
+ metric: metric,
+ imperial: imperial,
+ _anchors: {
+ metric: {
+ unit: 'lx',
+ ratio: 1/10.76391
+ },
+ imperial: {
+ unit: 'ft-cd',
+ ratio: 10.76391
+ }
+ }
+};
diff --git a/src/convert/definitions/length.js b/src/convert/definitions/length.js
new file mode 100644
index 0000000..84a7adf
--- /dev/null
+++ b/src/convert/definitions/length.js
@@ -0,0 +1,86 @@
+var metric,
+imperial;
+
+metric = {
+ mm: {
+ name: {
+ singular: 'Millimeter',
+ plural: 'Millimeters'
+ },
+ to_anchor: 1/1000
+ },
+ cm: {
+ name: {
+ singular: 'Centimeter',
+ plural: 'Centimeters'
+ },
+ to_anchor: 1/100
+ },
+ m: {
+ name: {
+ singular: 'Meter',
+ plural: 'Meters'
+ },
+ to_anchor: 1
+ },
+ km: {
+ name: {
+ singular: 'Kilometer',
+ plural: 'Kilometers'
+ },
+ to_anchor: 1000
+ }
+};
+
+imperial = {
+ 'in': {
+ name: {
+ singular: 'Inch',
+ plural: 'Inches'
+ },
+ to_anchor: 1/12
+ },
+ yd: {
+ name: {
+ singular: 'Yard',
+ plural: 'Yards'
+ },
+ to_anchor: 3
+ },
+ 'ft-us': {
+ name: {
+ singular: 'US Survey Foot',
+ plural: 'US Survey Feet'
+ },
+ to_anchor: 1.000002
+ },
+ ft: {
+ name: {
+ singular: 'Foot',
+ plural: 'Feet'
+ },
+ to_anchor: 1
+ },
+ mi: {
+ name: {
+ singular: 'Mile',
+ plural: 'Miles'
+ },
+ to_anchor: 5280
+ }
+};
+
+module.exports = {
+ metric: metric,
+ imperial: imperial,
+ _anchors: {
+ metric: {
+ unit: 'm',
+ ratio: 3.28084
+ },
+ imperial: {
+ unit: 'ft',
+ ratio: 1/3.28084
+ }
+ }
+};
diff --git a/src/convert/definitions/mass.js b/src/convert/definitions/mass.js
new file mode 100644
index 0000000..7c60cb8
--- /dev/null
+++ b/src/convert/definitions/mass.js
@@ -0,0 +1,78 @@
+var metric
+ , imperial;
+
+metric = {
+ mcg: {
+ name: {
+ singular: 'Microgram'
+ , plural: 'Micrograms'
+ }
+ , to_anchor: 1/1000000
+ }
+, mg: {
+ name: {
+ singular: 'Milligram'
+ , plural: 'Milligrams'
+ }
+ , to_anchor: 1/1000
+ }
+, g: {
+ name: {
+ singular: 'Gram'
+ , plural: 'Grams'
+ }
+ , to_anchor: 1
+ }
+, kg: {
+ name: {
+ singular: 'Kilogram'
+ , plural: 'Kilograms'
+ }
+ , to_anchor: 1000
+}
+, mt: {
+ name: {
+ singular: 'Metric Tonne'
+ , plural: 'Metric Tonnes'
+ }
+ , to_anchor: 1000000
+ }
+};
+
+imperial = {
+ oz: {
+ name: {
+ singular: 'Ounce'
+ , plural: 'Ounces'
+ }
+ , to_anchor: 1/16
+ }
+, lb: {
+ name: {
+ singular: 'Pound'
+ , plural: 'Pounds'
+ }
+ , to_anchor: 1
+}, t: {
+ name: {
+ singular: 'Ton',
+ plural: 'Tons',
+ },
+ to_anchor: 2000,
+ },
+};
+
+module.exports = {
+ metric: metric
+, imperial: imperial
+, _anchors: {
+ metric: {
+ unit: 'g'
+ , ratio: 1/453.592
+ }
+ , imperial: {
+ unit: 'lb'
+ , ratio: 453.592
+ }
+ }
+};
diff --git a/src/convert/definitions/pace.js b/src/convert/definitions/pace.js
new file mode 100644
index 0000000..1b5cdfa
--- /dev/null
+++ b/src/convert/definitions/pace.js
@@ -0,0 +1,51 @@
+var metric
+ , imperial;
+
+metric = {
+ 'min/km': {
+ name: {
+ singular: 'Minute per kilometre'
+ , plural: 'Minutes per kilometre'
+ }
+ , to_anchor: 0.06
+ }
+ ,'s/m': {
+ name: {
+ singular: 'Second per metre'
+ , plural: 'Seconds per metre'
+ }
+ , to_anchor: 1
+ }
+}
+
+imperial = {
+ 'min/mi': {
+ name: {
+ singular: 'Minute per mile'
+ , plural: 'Minutes per mile'
+ }
+ , to_anchor: 0.0113636
+ }
+ , 's/ft': {
+ name: {
+ singular: 'Second per foot'
+ , plural: 'Seconds per foot'
+ }
+ , to_anchor: 1
+ }
+};
+
+module.exports = {
+ metric: metric
+, imperial: imperial
+, _anchors: {
+ metric: {
+ unit: 's/m'
+ , ratio: 0.3048
+ }
+ , imperial: {
+ unit: 's/ft'
+ , ratio: 1/0.3048
+ }
+ }
+};
diff --git a/src/convert/definitions/partsPer.js b/src/convert/definitions/partsPer.js
new file mode 100644
index 0000000..333a203
--- /dev/null
+++ b/src/convert/definitions/partsPer.js
@@ -0,0 +1,44 @@
+var metric
+ , imperial;
+
+metric = {
+ ppm: {
+ name: {
+ singular: 'Part-per Million'
+ , plural: 'Parts-per Million'
+ }
+ , to_anchor: 1
+ }
+ , ppb: {
+ name: {
+ singular: 'Part-per Billion'
+ , plural: 'Parts-per Billion'
+ }
+ , to_anchor: .001
+ }
+ , ppt: {
+ name: {
+ singular: 'Part-per Trillion'
+ , plural: 'Parts-per Trillion'
+ }
+ , to_anchor: .000001
+ }
+ , ppq: {
+ name: {
+ singular: 'Part-per Quadrillion'
+ , plural: 'Parts-per Quadrillion'
+ }
+ , to_anchor: .000000001
+ }
+};
+
+module.exports = {
+ metric: metric
+ , imperial: {}
+ , _anchors: {
+ metric: {
+ unit: 'ppm'
+ , ratio: .000001
+ }
+ }
+};
diff --git a/src/convert/definitions/power.js b/src/convert/definitions/power.js
new file mode 100644
index 0000000..36896cc
--- /dev/null
+++ b/src/convert/definitions/power.js
@@ -0,0 +1,49 @@
+var power;
+
+power = {
+ W: {
+ name: {
+ singular: 'Watt'
+ , plural: 'Watts'
+ }
+ , to_anchor: 1
+ }
+, mW: {
+ name: {
+ singular: 'Milliwatt'
+ , plural: 'Milliwatts'
+ }
+ , to_anchor: .001
+ }
+, kW: {
+ name: {
+ singular: 'Kilowatt'
+ , plural: 'Kilowatts'
+ }
+ , to_anchor: 1000
+ }
+, MW: {
+ name: {
+ singular: 'Megawatt'
+ , plural: 'Megawatts'
+ }
+ , to_anchor: 1000000
+ }
+, GW: {
+ name: {
+ singular: 'Gigawatt'
+ , plural: 'Gigawatts'
+ }
+ , to_anchor: 1000000000
+ }
+};
+
+module.exports = {
+ metric: power
+, _anchors: {
+ metric: {
+ unit: 'W'
+ , ratio: 1
+ }
+ }
+};
diff --git a/src/convert/definitions/pressure.js b/src/convert/definitions/pressure.js
new file mode 100644
index 0000000..91519ff
--- /dev/null
+++ b/src/convert/definitions/pressure.js
@@ -0,0 +1,86 @@
+var metric
+ , imperial;
+
+metric = {
+ Pa: {
+ name: {
+ singular: 'pascal'
+ , plural: 'pascals'
+ }
+ , to_anchor: 1/1000
+ }
+, kPa: {
+ name: {
+ singular: 'kilopascal'
+ , plural: 'kilopascals'
+ }
+ , to_anchor: 1
+ }
+, MPa: {
+ name: {
+ singular: 'megapascal'
+ , plural: 'megapascals'
+ }
+ , to_anchor: 1000
+ }
+, hPa: {
+ name: {
+ singular: 'hectopascal'
+ , plural: 'hectopascals'
+ }
+ , to_anchor: 1/10
+ }
+, bar: {
+ name: {
+ singular: 'bar'
+ , plural: 'bar'
+ }
+ , to_anchor: 100
+ }
+ , mbar: {
+ name: {
+ singular: 'mbar'
+ , plural: 'mbar'
+ }
+ , to_anchor: 1/10
+ }
+, torr: {
+ name: {
+ singular: 'torr'
+ , plural: 'torr'
+ }
+ , to_anchor: 101325/760000
+ }
+};
+
+imperial = {
+ psi: {
+ name: {
+ singular: 'pound per square inch'
+ , plural: 'pounds per square inch'
+ }
+ , to_anchor: 1/1000
+ }
+, ksi: {
+ name: {
+ singular: 'kilopound per square inch'
+ , plural: 'kilopound per square inch'
+ }
+ , to_anchor: 1
+ }
+};
+
+module.exports = {
+ metric: metric
+, imperial: imperial
+, _anchors: {
+ metric: {
+ unit: 'kPa'
+ , ratio: 0.00014503768078
+ }
+ , imperial: {
+ unit: 'psi'
+ , ratio: 1/0.00014503768078
+ }
+ }
+};
diff --git a/src/convert/definitions/reactiveEnergy.js b/src/convert/definitions/reactiveEnergy.js
new file mode 100644
index 0000000..3d7e1a8
--- /dev/null
+++ b/src/convert/definitions/reactiveEnergy.js
@@ -0,0 +1,49 @@
+var reactiveEnergy;
+
+reactiveEnergy = {
+ VARh: {
+ name: {
+ singular: 'Volt-Ampere Reactive Hour'
+ , plural: 'Volt-Amperes Reactive Hour'
+ }
+ , to_anchor: 1
+ }
+, mVARh: {
+ name: {
+ singular: 'Millivolt-Ampere Reactive Hour'
+ , plural: 'Millivolt-Amperes Reactive Hour'
+ }
+ , to_anchor: .001
+ }
+, kVARh: {
+ name: {
+ singular: 'Kilovolt-Ampere Reactive Hour'
+ , plural: 'Kilovolt-Amperes Reactive Hour'
+ }
+ , to_anchor: 1000
+ }
+, MVARh: {
+ name: {
+ singular: 'Megavolt-Ampere Reactive Hour'
+ , plural: 'Megavolt-Amperes Reactive Hour'
+ }
+ , to_anchor: 1000000
+ }
+, GVARh: {
+ name: {
+ singular: 'Gigavolt-Ampere Reactive Hour'
+ , plural: 'Gigavolt-Amperes Reactive Hour'
+ }
+ , to_anchor: 1000000000
+ }
+};
+
+module.exports = {
+ metric: reactiveEnergy
+, _anchors: {
+ metric: {
+ unit: 'VARh'
+ , ratio: 1
+ }
+ }
+};
diff --git a/src/convert/definitions/reactivePower.js b/src/convert/definitions/reactivePower.js
new file mode 100644
index 0000000..348da53
--- /dev/null
+++ b/src/convert/definitions/reactivePower.js
@@ -0,0 +1,49 @@
+var reactivePower;
+
+reactivePower = {
+ VAR: {
+ name: {
+ singular: 'Volt-Ampere Reactive'
+ , plural: 'Volt-Amperes Reactive'
+ }
+ , to_anchor: 1
+ }
+, mVAR: {
+ name: {
+ singular: 'Millivolt-Ampere Reactive'
+ , plural: 'Millivolt-Amperes Reactive'
+ }
+ , to_anchor: .001
+ }
+, kVAR: {
+ name: {
+ singular: 'Kilovolt-Ampere Reactive'
+ , plural: 'Kilovolt-Amperes Reactive'
+ }
+ , to_anchor: 1000
+ }
+, MVAR: {
+ name: {
+ singular: 'Megavolt-Ampere Reactive'
+ , plural: 'Megavolt-Amperes Reactive'
+ }
+ , to_anchor: 1000000
+ }
+, GVAR: {
+ name: {
+ singular: 'Gigavolt-Ampere Reactive'
+ , plural: 'Gigavolt-Amperes Reactive'
+ }
+ , to_anchor: 1000000000
+ }
+};
+
+module.exports = {
+ metric: reactivePower
+, _anchors: {
+ metric: {
+ unit: 'VAR'
+ , ratio: 1
+ }
+ }
+};
diff --git a/src/convert/definitions/speed.js b/src/convert/definitions/speed.js
new file mode 100644
index 0000000..81b3567
--- /dev/null
+++ b/src/convert/definitions/speed.js
@@ -0,0 +1,58 @@
+var metric
+ , imperial;
+
+metric = {
+ 'm/s': {
+ name: {
+ singular: 'Metre per second'
+ , plural: 'Metres per second'
+ }
+ , to_anchor: 3.6
+ }
+, 'km/h': {
+ name: {
+ singular: 'Kilometre per hour'
+ , plural: 'Kilometres per hour'
+ }
+ , to_anchor: 1
+ }
+}
+
+ imperial = {
+ 'm/h': {
+ name: {
+ singular: 'Mile per hour'
+ , plural: 'Miles per hour'
+ }
+ , to_anchor: 1
+ }
+ , knot: {
+ name: {
+ singular: 'Knot'
+ , plural: 'Knots'
+ }
+ , to_anchor: 1.150779
+ }
+ , 'ft/s': {
+ name: {
+ singular: 'Foot per second'
+ , plural: 'Feet per second'
+ }
+ , to_anchor: 0.681818
+ }
+};
+
+module.exports = {
+ metric: metric
+, imperial: imperial
+, _anchors: {
+ metric: {
+ unit: 'km/h'
+ , ratio: 1/1.609344
+ }
+ , imperial: {
+ unit: 'm/h'
+ , ratio: 1.609344
+ }
+ }
+};
diff --git a/src/convert/definitions/temperature.js b/src/convert/definitions/temperature.js
new file mode 100644
index 0000000..a118dbf
--- /dev/null
+++ b/src/convert/definitions/temperature.js
@@ -0,0 +1,55 @@
+var metric
+ , imperial;
+
+metric = {
+ C: {
+ name: {
+ singular: 'degree Celsius'
+ , plural: 'degrees Celsius'
+ }
+ , to_anchor: 1
+ , anchor_shift: 0
+ },
+ K: {
+ name: {
+ singular: 'degree Kelvin'
+ , plural: 'degrees Kelvin'
+ }
+ , to_anchor: 1
+ , anchor_shift: 273.15
+ }
+};
+
+imperial = {
+ F: {
+ name: {
+ singular: 'degree Fahrenheit'
+ , plural: 'degrees Fahrenheit'
+ }
+ , to_anchor: 1
+ },
+ R: {
+ name: {
+ singular: 'degree Rankine'
+ , plural: 'degrees Rankine'
+ }
+ , to_anchor: 1
+ , anchor_shift: 459.67
+ }
+};
+
+module.exports = {
+ metric: metric
+, imperial: imperial
+, _anchors: {
+ metric: {
+ unit: 'C'
+ , transform: function (C) { return C / (5/9) + 32 }
+ }
+ , imperial: {
+ unit: 'F'
+ , transform: function (F) { return (F - 32) * (5/9) }
+ }
+ }
+};
+
diff --git a/src/convert/definitions/time.js b/src/convert/definitions/time.js
new file mode 100644
index 0000000..fc1ad32
--- /dev/null
+++ b/src/convert/definitions/time.js
@@ -0,0 +1,86 @@
+var time;
+var daysInYear = 365.25;
+
+time = {
+ ns: {
+ name: {
+ singular: 'Nanosecond'
+ , plural: 'Nanoseconds'
+ }
+ , to_anchor: 1/1000000000
+ }
+, mu: {
+ name: {
+ singular: 'Microsecond'
+ , plural: 'Microseconds'
+ }
+ , to_anchor: 1/1000000
+ }
+, ms: {
+ name: {
+ singular: 'Millisecond'
+ , plural: 'Milliseconds'
+ }
+ , to_anchor: 1/1000
+ }
+, s: {
+ name: {
+ singular: 'Second'
+ , plural: 'Seconds'
+ }
+ , to_anchor: 1
+ }
+, min: {
+ name: {
+ singular: 'Minute'
+ , plural: 'Minutes'
+ }
+ , to_anchor: 60
+ }
+, h: {
+ name: {
+ singular: 'Hour'
+ , plural: 'Hours'
+ }
+ , to_anchor: 60 * 60
+ }
+, d: {
+ name: {
+ singular: 'Day'
+ , plural: 'Days'
+ }
+ , to_anchor: 60 * 60 * 24
+ }
+, week: {
+ name: {
+ singular: 'Week'
+ , plural: 'Weeks'
+ }
+ , to_anchor: 60 * 60 * 24 * 7
+ }
+, month: {
+ name: {
+ singular: 'Month'
+ , plural: 'Months'
+ }
+ , to_anchor: 60 * 60 * 24 * daysInYear / 12
+ }
+, year: {
+ name: {
+ singular: 'Year'
+ , plural: 'Years'
+ }
+ , to_anchor: 60 * 60 * 24 * daysInYear
+ }
+};
+
+
+module.exports = {
+ metric: time
+, _anchors: {
+ metric: {
+ unit: 's'
+ , ratio: 1
+ }
+ }
+};
diff --git a/src/convert/definitions/voltage.js b/src/convert/definitions/voltage.js
new file mode 100644
index 0000000..9234f42
--- /dev/null
+++ b/src/convert/definitions/voltage.js
@@ -0,0 +1,35 @@
+var voltage;
+
+voltage = {
+ V: {
+ name: {
+ singular: 'Volt'
+ , plural: 'Volts'
+ }
+ , to_anchor: 1
+ }
+, mV: {
+ name: {
+ singular: 'Millivolt'
+ , plural: 'Millivolts'
+ }
+ , to_anchor: .001
+ }
+, kV: {
+ name: {
+ singular: 'Kilovolt'
+ , plural: 'Kilovolts'
+ }
+ , to_anchor: 1000
+ }
+};
+
+module.exports = {
+ metric: voltage
+, _anchors: {
+ metric: {
+ unit: 'V'
+ , ratio: 1
+ }
+ }
+};
diff --git a/src/convert/definitions/volume.js b/src/convert/definitions/volume.js
new file mode 100644
index 0000000..ee35057
--- /dev/null
+++ b/src/convert/definitions/volume.js
@@ -0,0 +1,200 @@
+var metric
+ , imperial;
+
+metric = {
+ mm3: {
+ name: {
+ singular: 'Cubic Millimeter'
+ , plural: 'Cubic Millimeters'
+ }
+ , to_anchor: 1/1000000
+ }
+, cm3: {
+ name: {
+ singular: 'Cubic Centimeter'
+ , plural: 'Cubic Centimeters'
+ }
+ , to_anchor: 1/1000
+ }
+, ml: {
+ name: {
+ singular: 'Millilitre'
+ , plural: 'Millilitres'
+ }
+ , to_anchor: 1/1000
+ }
+, cl: {
+ name: {
+ singular: 'Centilitre'
+ , plural: 'Centilitres'
+ }
+ , to_anchor: 1/100
+ }
+, dl: {
+ name: {
+ singular: 'Decilitre'
+ , plural: 'Decilitres'
+ }
+ , to_anchor: 1/10
+ }
+, l: {
+ name: {
+ singular: 'Litre'
+ , plural: 'Litres'
+ }
+ , to_anchor: 1
+ }
+, kl: {
+ name: {
+ singular: 'Kilolitre'
+ , plural: 'Kilolitres'
+ }
+ , to_anchor: 1000
+ }
+, m3: {
+ name: {
+ singular: 'Cubic meter'
+ , plural: 'Cubic meters'
+ }
+ , to_anchor: 1000
+ }
+, km3: {
+ name: {
+ singular: 'Cubic kilometer'
+ , plural: 'Cubic kilometers'
+ }
+ , to_anchor: 1000000000000
+ }
+
+// Swedish units
+, krm: {
+ name: {
+ singular: 'Matsked'
+ , plural: 'Matskedar'
+ }
+ , to_anchor: 1/1000
+}
+, tsk: {
+ name: {
+ singular: 'Tesked'
+ , plural: 'Teskedar'
+ }
+ , to_anchor: 5/1000
+ }
+, msk: {
+ name: {
+ singular: 'Matsked'
+ , plural: 'Matskedar'
+ }
+ , to_anchor: 15/1000
+ }
+, kkp: {
+ name: {
+ singular: 'Kaffekopp'
+ , plural: 'Kaffekoppar'
+ }
+ , to_anchor: 150/1000
+ }
+, glas: {
+ name: {
+ singular: 'Glas'
+ , plural: 'Glas'
+ }
+ , to_anchor: 200/1000
+ }
+, kanna: {
+ name: {
+ singular: 'Kanna'
+ , plural: 'Kannor'
+ }
+ , to_anchor: 2.617
+ }
+};
+
+imperial = {
+ tsp: {
+ name: {
+ singular: 'Teaspoon'
+ , plural: 'Teaspoons'
+ }
+ , to_anchor: 1/6
+ }
+, Tbs: {
+ name: {
+ singular: 'Tablespoon'
+ , plural: 'Tablespoons'
+ }
+ , to_anchor: 1/2
+ }
+, in3: {
+ name: {
+ singular: 'Cubic inch'
+ , plural: 'Cubic inches'
+ }
+ , to_anchor: 0.55411
+ }
+, 'fl-oz': {
+ name: {
+ singular: 'Fluid Ounce'
+ , plural: 'Fluid Ounces'
+ }
+ , to_anchor: 1
+ }
+, cup: {
+ name: {
+ singular: 'Cup'
+ , plural: 'Cups'
+ }
+ , to_anchor: 8
+ }
+, pnt: {
+ name: {
+ singular: 'Pint'
+ , plural: 'Pints'
+ }
+ , to_anchor: 16
+ }
+, qt: {
+ name: {
+ singular: 'Quart'
+ , plural: 'Quarts'
+ }
+ , to_anchor: 32
+ }
+, gal: {
+ name: {
+ singular: 'Gallon'
+ , plural: 'Gallons'
+ }
+ , to_anchor: 128
+ }
+, ft3: {
+ name: {
+ singular: 'Cubic foot'
+ , plural: 'Cubic feet'
+ }
+ , to_anchor: 957.506
+ }
+, yd3: {
+ name: {
+ singular: 'Cubic yard'
+ , plural: 'Cubic yards'
+ }
+ , to_anchor: 25852.7
+ }
+};
+
+module.exports = {
+ metric: metric
+, imperial: imperial
+, _anchors: {
+ metric: {
+ unit: 'l'
+ , ratio: 33.8140226
+ }
+ , imperial: {
+ unit: 'fl-oz'
+ , ratio: 1/33.8140226
+ }
+ }
+};
diff --git a/src/convert/definitions/volumeFlowRate.js b/src/convert/definitions/volumeFlowRate.js
new file mode 100644
index 0000000..b6cd84f
--- /dev/null
+++ b/src/convert/definitions/volumeFlowRate.js
@@ -0,0 +1,282 @@
+var metric
+ , imperial;
+
+metric = {
+ 'mm3/s': {
+ name: {
+ singular: 'Cubic Millimeter per second'
+ , plural: 'Cubic Millimeters per second'
+ }
+ , to_anchor: 1/1000000
+ }
+, 'cm3/s': {
+ name: {
+ singular: 'Cubic Centimeter per second'
+ , plural: 'Cubic Centimeters per second'
+ }
+ , to_anchor: 1/1000
+ }
+, 'ml/s': {
+ name: {
+ singular: 'Millilitre per second'
+ , plural: 'Millilitres per second'
+ }
+ , to_anchor: 1/1000
+ }
+, 'cl/s': {
+ name: {
+ singular: 'Centilitre per second'
+ , plural: 'Centilitres per second'
+ }
+ , to_anchor: 1/100
+ }
+, 'dl/s': {
+ name: {
+ singular: 'Decilitre per second'
+ , plural: 'Decilitres per second'
+ }
+ , to_anchor: 1/10
+ }
+, 'l/s': {
+ name: {
+ singular: 'Litre per second'
+ , plural: 'Litres per second'
+ }
+ , to_anchor: 1
+ }
+, 'l/min': {
+ name: {
+ singular: 'Litre per minute'
+ , plural: 'Litres per minute'
+ }
+ , to_anchor: 1/60
+ }
+, 'l/h': {
+ name: {
+ singular: 'Litre per hour'
+ , plural: 'Litres per hour'
+ }
+ , to_anchor: 1/3600
+ }
+, 'kl/s': {
+ name: {
+ singular: 'Kilolitre per second'
+ , plural: 'Kilolitres per second'
+ }
+ , to_anchor: 1000
+ }
+, 'kl/min': {
+ name: {
+ singular: 'Kilolitre per minute'
+ , plural: 'Kilolitres per minute'
+ }
+ , to_anchor: 50/3
+ }
+, 'kl/h': {
+ name: {
+ singular: 'Kilolitre per hour'
+ , plural: 'Kilolitres per hour'
+ }
+ , to_anchor: 5/18
+ }
+, 'm3/s': {
+ name: {
+ singular: 'Cubic meter per second'
+ , plural: 'Cubic meters per second'
+ }
+ , to_anchor: 1000
+ }
+, 'm3/min': {
+ name: {
+ singular: 'Cubic meter per minute'
+ , plural: 'Cubic meters per minute'
+ }
+ , to_anchor: 50/3
+ }
+, 'm3/h': {
+ name: {
+ singular: 'Cubic meter per hour'
+ , plural: 'Cubic meters per hour'
+ }
+ , to_anchor: 5/18
+ }
+, 'km3/s': {
+ name: {
+ singular: 'Cubic kilometer per second'
+ , plural: 'Cubic kilometers per second'
+ }
+ , to_anchor: 1000000000000
+ }
+};
+
+imperial = {
+ 'tsp/s': {
+ name: {
+ singular: 'Teaspoon per second'
+ , plural: 'Teaspoons per second'
+ }
+ , to_anchor: 1/6
+ }
+, 'Tbs/s': {
+ name: {
+ singular: 'Tablespoon per second'
+ , plural: 'Tablespoons per second'
+ }
+ , to_anchor: 1/2
+ }
+, 'in3/s': {
+ name: {
+ singular: 'Cubic inch per second'
+ , plural: 'Cubic inches per second'
+ }
+ , to_anchor: 0.55411
+ }
+, 'in3/min': {
+ name: {
+ singular: 'Cubic inch per minute'
+ , plural: 'Cubic inches per minute'
+ }
+ , to_anchor: 0.55411/60
+ }
+, 'in3/h': {
+ name: {
+ singular: 'Cubic inch per hour'
+ , plural: 'Cubic inches per hour'
+ }
+ , to_anchor: 0.55411/3600
+ }
+, 'fl-oz/s': {
+ name: {
+ singular: 'Fluid Ounce per second'
+ , plural: 'Fluid Ounces per second'
+ }
+ , to_anchor: 1
+ }
+, 'fl-oz/min': {
+ name: {
+ singular: 'Fluid Ounce per minute'
+ , plural: 'Fluid Ounces per minute'
+ }
+ , to_anchor: 1/60
+ }
+, 'fl-oz/h': {
+ name: {
+ singular: 'Fluid Ounce per hour'
+ , plural: 'Fluid Ounces per hour'
+ }
+ , to_anchor: 1/3600
+ }
+, 'cup/s': {
+ name: {
+ singular: 'Cup per second'
+ , plural: 'Cups per second'
+ }
+ , to_anchor: 8
+ }
+, 'pnt/s': {
+ name: {
+ singular: 'Pint per second'
+ , plural: 'Pints per second'
+ }
+ , to_anchor: 16
+ }
+, 'pnt/min': {
+ name: {
+ singular: 'Pint per minute'
+ , plural: 'Pints per minute'
+ }
+ , to_anchor: 4/15
+ }
+, 'pnt/h': {
+ name: {
+ singular: 'Pint per hour'
+ , plural: 'Pints per hour'
+ }
+ , to_anchor: 1/225
+ }
+, 'qt/s': {
+ name: {
+ singular: 'Quart per second'
+ , plural: 'Quarts per second'
+ }
+ , to_anchor: 32
+ }
+, 'gal/s': {
+ name: {
+ singular: 'Gallon per second'
+ , plural: 'Gallons per second'
+ }
+ , to_anchor: 128
+ }
+, 'gal/min': {
+ name: {
+ singular: 'Gallon per minute'
+ , plural: 'Gallons per minute'
+ }
+ , to_anchor: 32/15
+ }
+, 'gal/h': {
+ name: {
+ singular: 'Gallon per hour'
+ , plural: 'Gallons per hour'
+ }
+ , to_anchor: 8/225
+ }
+, 'ft3/s': {
+ name: {
+ singular: 'Cubic foot per second'
+ , plural: 'Cubic feet per second'
+ }
+ , to_anchor: 957.506
+ }
+, 'ft3/min': {
+ name: {
+ singular: 'Cubic foot per minute'
+ , plural: 'Cubic feet per minute'
+ }
+ , to_anchor: 957.506/60
+ }
+, 'ft3/h': {
+ name: {
+ singular: 'Cubic foot per hour'
+ , plural: 'Cubic feet per hour'
+ }
+ , to_anchor: 957.506/3600
+ }
+, 'yd3/s': {
+ name: {
+ singular: 'Cubic yard per second'
+ , plural: 'Cubic yards per second'
+ }
+ , to_anchor: 25852.7
+ }
+, 'yd3/min': {
+ name: {
+ singular: 'Cubic yard per minute'
+ , plural: 'Cubic yards per minute'
+ }
+ , to_anchor: 25852.7/60
+ }
+, 'yd3/h': {
+ name: {
+ singular: 'Cubic yard per hour'
+ , plural: 'Cubic yards per hour'
+ }
+ , to_anchor: 25852.7/3600
+ }
+};
+
+module.exports = {
+ metric: metric
+, imperial: imperial
+, _anchors: {
+ metric: {
+ unit: 'l/s'
+ , ratio: 33.8140227
+ }
+ , imperial: {
+ unit: 'fl-oz/s'
+ , ratio: 1/33.8140227
+ }
+ }
+};
diff --git a/src/convert/fysics.js b/src/convert/fysics.js
new file mode 100644
index 0000000..be9b9bc
--- /dev/null
+++ b/src/convert/fysics.js
@@ -0,0 +1,160 @@
+/*
+Copyright:
+Year : (c) 2023
+Author : Rene De Ren
+Contact details : zn375ix3@gmail.com
+Location : The Netherlands
+
+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 author shall be notified of any and all improvements or adaptations this 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,
+OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+class Fysics{
+
+ constructor(){
+
+ //gasses
+ this.air_density = 0; // weight of air
+ this.atm_pressure = 1.01325 ; //atm pressure at sea level
+ this.earth_gravity = 9.80665 ; // acceleration of standard gravity on earth in m/s2
+ this.water_molar_mass = 18.01528 // g/mol
+ this.num_moles_water = 6.022 * 1023; // number of moles in water
+ this.water_density = 997 ; // this.water_molar_mass * this.num_moles_water // water density in kg/m3;
+
+ //load converter
+ this.convert = require('./index');
+
+ // o2 solubility curve based on pressure and temp
+ this.o2Solubility = {
+ 1: // abs bar
+ {
+ x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius
+ y:[14.6,12.8,11.3,10.1,9.1,8.3,7.6,7,6.5,6,5.6], // mg/l
+ },
+ 2: // abs bar
+ {
+ x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius
+ y:[29.2,25.5,22.6,20.2,18.2,16.5,15.2,14,12.9,12,11.3], // mg/l
+ },
+ 4: // abs bar
+ {
+ x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius
+ y:[58.4,51.1,45.1,40.3,36.4,33.1,30.3,27.9,25.9,24,22.7], // mg/l
+ },
+ }
+
+ }
+
+ /*------------------- functions -------------------*/
+ //use input to calculate air density in kg/m3 is valid up to 100 degrees
+ //pressure in bar RH in % and temp in degrees celcius
+ // Antoine Equation is, Log P = A - B / ( T + C )
+ // D8 = temp , d7 = RH , pressure in mbar = d6
+ //=1.2923*(273.15/(273.15+temp))*(((pressure*100000)-0.3783*((((MACHT(10,(8.07-(1730.63/(233.43+temp)))))*1000/760)*RH)*100))/(1.01325*100000))
+ /*
+ calc_air_dens(pressure,RH,temp){
+
+ let air_density =
+ 1.2923 *
+ (
+ 273.15 / ( 273.15 + temp )
+ )
+ *
+ (
+ (
+ (
+ pressure * 100000
+ )
+ - 0.3783 *
+ (
+ (
+ (
+ (
+ Math.pow( 10, ( 8.07 - ( 1730.63 / ( 233.43 + temp) ) ) )
+ )
+ *1000/760
+ )
+ *RH
+ )
+ *100
+ )
+ )
+ / (this.atm_pressure * 100000 )
+ );
+
+ return air_density ;
+ }
+ */
+
+ calc_air_dens(pressure, RH, temp) {
+ const Rd = 287.05; // Specific gas constant for dry air in J/(kg·K)
+ const Rv = 461.495; // Specific gas constant for water vapor in J/(kg·K)
+
+ // Convert temperature to Kelvin
+ const T = temp + 273.15;
+
+ // Antoine constants for water vapor
+ const A = 8.07131;
+ const B = 1730.63;
+ const C = 233.426;
+
+ // Calculate saturation vapor pressure (e_s) using the Antoine equation (in hPa)
+ const e_s = Math.pow(10, (A - (B / (C + temp))));
+
+ // Actual vapor pressure (e) in hPa
+ const e = RH * e_s / 100;
+
+ // Convert pressure to Pascals
+ const pressurePa = this.convert(pressure).from('mbar').to('Pa');
+
+ // Partial pressure of dry air (Pa)
+ const p_d = pressurePa - (e * 100);
+
+ // Air density (kg/m³)
+ const air_density = (p_d / (Rd * T)) + ((e * 100) / (Rv * T));
+
+ return air_density;
+ }
+
+ //convert height to pressure ( density => kg/m3 , height => meter) output is in bar
+ heigth_to_pressure(density,height){
+
+ //calc pressure
+ let pressure = density * this.earth_gravity * height;
+ //convert Pa to bar
+ pressure = this.convert(pressure).from('Pa').to('mbar');
+
+ return pressure;
+ }
+
+ //input is in meters !
+ calc_volume(height,width,length){
+ let result = 0;
+ result = height * width * length;
+ return result ;
+ }
+
+
+}
+
+/*
+let fysics = new Fysics();
+console.log("converted pressure : " + fysics.heigth_to_pressure(fysics.water_density,10) + " mbar ");
+console.log( "air density : " + fysics.calc_air_dens(1.012,0,0) + " kg / m3" );
+
+//*/
+
+module.exports = Fysics;
\ No newline at end of file
diff --git a/src/convert/index.js b/src/convert/index.js
new file mode 100644
index 0000000..0734221
--- /dev/null
+++ b/src/convert/index.js
@@ -0,0 +1,304 @@
+var convert
+ , keys = require('./lodash/lodash.keys')
+ , each = require('./lodash/lodash.foreach')
+ , measures = {
+ length: require('./definitions/length')
+ , area: require('./definitions/area')
+ , mass: require('./definitions/mass')
+ , volume: require('./definitions/volume')
+ , each: require('./definitions/each')
+ , temperature: require('./definitions/temperature')
+ , time: require('./definitions/time')
+ , digital: require('./definitions/digital')
+ , partsPer: require('./definitions/partsPer')
+ , speed: require('./definitions/speed')
+ , pace: require('./definitions/pace')
+ , pressure: require('./definitions/pressure')
+ , current: require('./definitions/current')
+ , voltage: require('./definitions/voltage')
+ , power: require('./definitions/power')
+ , reactivePower: require('./definitions/reactivePower')
+ , apparentPower: require('./definitions/apparentPower')
+ , energy: require('./definitions/energy')
+ , reactiveEnergy: require('./definitions/reactiveEnergy')
+ , volumeFlowRate: require('./definitions/volumeFlowRate')
+ , illuminance: require('./definitions/illuminance')
+ , frequency: require('./definitions/frequency')
+ , angle : require('./definitions/angle')
+ }
+ , Converter;
+
+Converter = function (numerator, denominator) {
+ if(denominator)
+ this.val = numerator / denominator;
+ else
+ this.val = numerator;
+};
+
+/**
+* Lets the converter know the source unit abbreviation
+*/
+Converter.prototype.from = function (from) {
+ if(this.destination)
+ throw new Error('.from must be called before .to');
+
+ this.origin = this.getUnit(from);
+
+ if(!this.origin) {
+ this.throwUnsupportedUnitError(from);
+ }
+
+ return this;
+};
+
+/**
+* Converts the unit and returns the value
+*/
+Converter.prototype.to = function (to) {
+ if(!this.origin)
+ throw new Error('.to must be called after .from');
+
+ this.destination = this.getUnit(to);
+
+ var result
+ , transform;
+
+ if(!this.destination) {
+ this.throwUnsupportedUnitError(to);
+ }
+
+ // Don't change the value if origin and destination are the same
+ if (this.origin.abbr === this.destination.abbr) {
+ return this.val;
+ }
+
+ // You can't go from liquid to mass, for example
+ if(this.destination.measure != this.origin.measure) {
+ throw new Error('Cannot convert incompatible measures of '
+ + this.destination.measure + ' and ' + this.origin.measure);
+ }
+
+ /**
+ * Convert from the source value to its anchor inside the system
+ */
+ result = this.val * this.origin.unit.to_anchor;
+
+ /**
+ * For some changes it's a simple shift (C to K)
+ * So we'll add it when convering into the unit (later)
+ * and subtract it when converting from the unit
+ */
+ if (this.origin.unit.anchor_shift) {
+ result -= this.origin.unit.anchor_shift
+ }
+
+ /**
+ * Convert from one system to another through the anchor ratio. Some conversions
+ * aren't ratio based or require more than a simple shift. We can provide a custom
+ * transform here to provide the direct result
+ */
+ if(this.origin.system != this.destination.system) {
+ transform = measures[this.origin.measure]._anchors[this.origin.system].transform;
+ if (typeof transform === 'function') {
+ result = transform(result)
+ }
+ else {
+ result *= measures[this.origin.measure]._anchors[this.origin.system].ratio;
+ }
+ }
+
+ /**
+ * This shift has to be done after the system conversion business
+ */
+ if (this.destination.unit.anchor_shift) {
+ result += this.destination.unit.anchor_shift;
+ }
+
+ /**
+ * Convert to another unit inside the destination system
+ */
+ return result / this.destination.unit.to_anchor;
+};
+
+/**
+* Converts the unit to the best available unit.
+*/
+Converter.prototype.toBest = function(options) {
+ if(!this.origin)
+ throw new Error('.toBest must be called after .from');
+
+ var options = Object.assign({
+ exclude: [],
+ cutOffNumber: 1,
+ }, options)
+
+ var best;
+ /**
+ Looks through every possibility for the 'best' available unit.
+ i.e. Where the value has the fewest numbers before the decimal point,
+ but is still higher than 1.
+ */
+ each(this.possibilities(), function(possibility) {
+ var unit = this.describe(possibility);
+ var isIncluded = options.exclude.indexOf(possibility) === -1;
+
+ if (isIncluded && unit.system === this.origin.system) {
+ var result = this.to(possibility);
+ if (!best || (result >= options.cutOffNumber && result < best.val)) {
+ best = {
+ val: result,
+ unit: possibility,
+ singular: unit.singular,
+ plural: unit.plural
+ };
+ }
+ }
+ }.bind(this));
+
+ return best;
+}
+
+/**
+* Finds the unit
+*/
+Converter.prototype.getUnit = function (abbr) {
+ var found;
+
+ each(measures, function (systems, measure) {
+ each(systems, function (units, system) {
+ if(system == '_anchors')
+ return false;
+
+ each(units, function (unit, testAbbr) {
+ if(testAbbr == abbr) {
+ found = {
+ abbr: abbr
+ , measure: measure
+ , system: system
+ , unit: unit
+ };
+ return false;
+ }
+ });
+
+ if(found)
+ return false;
+ });
+
+ if(found)
+ return false;
+ });
+
+ return found;
+};
+
+var describe = function(resp) {
+ return {
+ abbr: resp.abbr
+ , measure: resp.measure
+ , system: resp.system
+ , singular: resp.unit.name.singular
+ , plural: resp.unit.name.plural
+ };
+}
+
+/**
+* An alias for getUnit
+*/
+Converter.prototype.describe = function (abbr) {
+ var resp = Converter.prototype.getUnit(abbr);
+ var desc = null;
+
+ try {
+ desc = describe(resp);
+ } catch(err) {
+ this.throwUnsupportedUnitError(abbr);
+ }
+
+ return desc;
+};
+
+/**
+* Detailed list of all supported units
+*/
+Converter.prototype.list = function (measure) {
+ var list = [];
+
+ each(measures, function (systems, testMeasure) {
+ if(measure && measure !== testMeasure)
+ return;
+
+ each(systems, function (units, system) {
+ if(system == '_anchors')
+ return false;
+
+ each(units, function (unit, abbr) {
+ list = list.concat(describe({
+ abbr: abbr,
+ measure: testMeasure
+ , system: system
+ , unit: unit
+ }));
+ });
+ });
+ });
+
+ return list;
+};
+
+Converter.prototype.throwUnsupportedUnitError = function (what) {
+ var validUnits = [];
+
+ each(measures, function (systems, measure) {
+ each(systems, function (units, system) {
+ if(system == '_anchors')
+ return false;
+
+ validUnits = validUnits.concat(keys(units));
+ });
+ });
+
+ throw new Error('Unsupported unit ' + what + ', use one of: ' + validUnits.join(', '));
+}
+
+/**
+* Returns the abbreviated measures that the value can be
+* converted to.
+*/
+Converter.prototype.possibilities = function (measure) {
+ var possibilities = [];
+ if(!this.origin && !measure) {
+ each(keys(measures), function (measure){
+ each(measures[measure], function (units, system) {
+ if(system == '_anchors')
+ return false;
+
+ possibilities = possibilities.concat(keys(units));
+ });
+ });
+ } else {
+ measure = measure || this.origin.measure;
+ each(measures[measure], function (units, system) {
+ if(system == '_anchors')
+ return false;
+
+ possibilities = possibilities.concat(keys(units));
+ });
+ }
+
+ return possibilities;
+};
+
+/**
+* Returns the abbreviated measures that the value can be
+* converted to.
+*/
+Converter.prototype.measures = function () {
+ return keys(measures);
+};
+
+convert = function (value) {
+ return new Converter(value);
+};
+
+module.exports = convert;
diff --git a/src/convert/lodash/lodash._basebind/LICENSE.txt b/src/convert/lodash/lodash._basebind/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash._basebind/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._basebind/README.md b/src/convert/lodash/lodash._basebind/README.md
new file mode 100644
index 0000000..55d05fb
--- /dev/null
+++ b/src/convert/lodash/lodash._basebind/README.md
@@ -0,0 +1,15 @@
+# lodash._basebind v2.3.0
+
+The internal [Lo-Dash](http://lodash.com/) function `baseBind` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash._basebind/index.js b/src/convert/lodash/lodash._basebind/index.js
new file mode 100644
index 0000000..6cf782d
--- /dev/null
+++ b/src/convert/lodash/lodash._basebind/index.js
@@ -0,0 +1,58 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var baseCreate = require('./../lodash._basecreate'),
+ isObject = require('./../lodash.isobject'),
+ setBindData = require('./../lodash._setbinddata');
+
+/**
+ * Used for `Array` method references.
+ *
+ * Normally `Array.prototype` would suffice, however, using an array literal
+ * avoids issues in Narwhal.
+ */
+var arrayRef = [];
+
+/** Native method shortcuts */
+var push = arrayRef.push;
+
+/**
+ * The base implementation of `_.bind` that creates the bound function and
+ * sets its meta data.
+ *
+ * @private
+ * @param {Array} bindData The bind data array.
+ * @returns {Function} Returns the new bound function.
+ */
+function baseBind(bindData) {
+ var func = bindData[0],
+ partialArgs = bindData[2],
+ thisArg = bindData[4];
+
+ function bound() {
+ // `Function#bind` spec
+ // http://es5.github.io/#x15.3.4.5
+ if (partialArgs) {
+ var args = partialArgs.slice();
+ push.apply(args, arguments);
+ }
+ // mimic the constructor's `return` behavior
+ // http://es5.github.io/#x13.2.2
+ if (this instanceof bound) {
+ // ensure `new bound` is an instance of `func`
+ var thisBinding = baseCreate(func.prototype),
+ result = func.apply(thisBinding, args || arguments);
+ return isObject(result) ? result : thisBinding;
+ }
+ return func.apply(thisArg, args || arguments);
+ }
+ setBindData(bound, bindData);
+ return bound;
+}
+
+module.exports = baseBind;
diff --git a/src/convert/lodash/lodash._basebind/package.json b/src/convert/lodash/lodash._basebind/package.json
new file mode 100644
index 0000000..e452d30
--- /dev/null
+++ b/src/convert/lodash/lodash._basebind/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "lodash._basebind",
+ "version": "2.3.0",
+ "description": "The internal Lo-Dash function `baseBind` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash._basecreate": "~2.3.0",
+ "lodash.isobject": "~2.3.0",
+ "lodash._setbinddata": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._basecreate/LICENSE.txt b/src/convert/lodash/lodash._basecreate/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreate/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._basecreate/README.md b/src/convert/lodash/lodash._basecreate/README.md
new file mode 100644
index 0000000..6c039a8
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreate/README.md
@@ -0,0 +1,15 @@
+# lodash._basecreate v2.3.0
+
+The internal [Lo-Dash](http://lodash.com/) function `baseCreate` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash._basecreate/index.js b/src/convert/lodash/lodash._basecreate/index.js
new file mode 100644
index 0000000..c6fc38a
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreate/index.js
@@ -0,0 +1,42 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var isObject = require('./../lodash.isobject'),
+ noop = require('./../lodash.noop'),
+ reNative = require('./../lodash._renative');
+
+/* Native method shortcuts for methods with the same name as other `lodash` methods */
+var nativeCreate = reNative.test(nativeCreate = Object.create) && nativeCreate;
+
+/**
+ * The base implementation of `_.create` without support for assigning
+ * properties to the created object.
+ *
+ * @private
+ * @param {Object} prototype The object to inherit from.
+ * @returns {Object} Returns the new object.
+ */
+function baseCreate(prototype, properties) {
+ return isObject(prototype) ? nativeCreate(prototype) : {};
+}
+// fallback for browsers without `Object.create`
+if (!nativeCreate) {
+ baseCreate = (function() {
+ function Object() {}
+ return function(prototype) {
+ if (isObject(prototype)) {
+ Object.prototype = prototype;
+ var result = new Object;
+ Object.prototype = null;
+ }
+ return result || global.Object();
+ };
+ }());
+}
+
+module.exports = baseCreate;
diff --git a/src/convert/lodash/lodash._basecreate/package.json b/src/convert/lodash/lodash._basecreate/package.json
new file mode 100644
index 0000000..6a72ca2
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreate/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "lodash._basecreate",
+ "version": "2.3.0",
+ "description": "The internal Lo-Dash function `baseCreate` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash.isobject": "~2.3.0",
+ "lodash.noop": "~2.3.0",
+ "lodash._renative": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._basecreatecallback/LICENSE.txt b/src/convert/lodash/lodash._basecreatecallback/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreatecallback/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._basecreatecallback/README.md b/src/convert/lodash/lodash._basecreatecallback/README.md
new file mode 100644
index 0000000..ff8a676
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreatecallback/README.md
@@ -0,0 +1,15 @@
+# lodash._basecreatecallback v2.3.0
+
+The internal [Lo-Dash](http://lodash.com/) function `baseCreateCallback` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash._basecreatecallback/index.js b/src/convert/lodash/lodash._basecreatecallback/index.js
new file mode 100644
index 0000000..e60dc44
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreatecallback/index.js
@@ -0,0 +1,80 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var bind = require('./../lodash.bind'),
+ identity = require('./../lodash.identity'),
+ setBindData = require('./../lodash._setbinddata'),
+ support = require('./../lodash.support');
+
+/** Used to detected named functions */
+var reFuncName = /^\s*function[ \n\r\t]+\w/;
+
+/** Used to detect functions containing a `this` reference */
+var reThis = /\bthis\b/;
+
+/** Native method shortcuts */
+var fnToString = Function.prototype.toString;
+
+/**
+ * The base implementation of `_.createCallback` without support for creating
+ * "_.pluck" or "_.where" style callbacks.
+ *
+ * @private
+ * @param {*} [func=identity] The value to convert to a callback.
+ * @param {*} [thisArg] The `this` binding of the created callback.
+ * @param {number} [argCount] The number of arguments the callback accepts.
+ * @returns {Function} Returns a callback function.
+ */
+function baseCreateCallback(func, thisArg, argCount) {
+ if (typeof func != 'function') {
+ return identity;
+ }
+ // exit early for no `thisArg` or already bound by `Function#bind`
+ if (typeof thisArg == 'undefined' || !('prototype' in func)) {
+ return func;
+ }
+ var bindData = func.__bindData__;
+ if (typeof bindData == 'undefined') {
+ if (support.funcNames) {
+ bindData = !func.name;
+ }
+ bindData = bindData || !support.funcDecomp;
+ if (!bindData) {
+ var source = fnToString.call(func);
+ if (!support.funcNames) {
+ bindData = !reFuncName.test(source);
+ }
+ if (!bindData) {
+ // checks if `func` references the `this` keyword and stores the result
+ bindData = reThis.test(source);
+ setBindData(func, bindData);
+ }
+ }
+ }
+ // exit early if there are no `this` references or `func` is bound
+ if (bindData === false || (bindData !== true && bindData[1] & 1)) {
+ return func;
+ }
+ switch (argCount) {
+ case 1: return function(value) {
+ return func.call(thisArg, value);
+ };
+ case 2: return function(a, b) {
+ return func.call(thisArg, a, b);
+ };
+ case 3: return function(value, index, collection) {
+ return func.call(thisArg, value, index, collection);
+ };
+ case 4: return function(accumulator, value, index, collection) {
+ return func.call(thisArg, accumulator, value, index, collection);
+ };
+ }
+ return bind(func, thisArg);
+}
+
+module.exports = baseCreateCallback;
diff --git a/src/convert/lodash/lodash._basecreatecallback/package.json b/src/convert/lodash/lodash._basecreatecallback/package.json
new file mode 100644
index 0000000..c969bad
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreatecallback/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "lodash._basecreatecallback",
+ "version": "2.3.0",
+ "description": "The internal Lo-Dash function `baseCreateCallback` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash.bind": "~2.3.0",
+ "lodash.identity": "~2.3.0",
+ "lodash._setbinddata": "~2.3.0",
+ "lodash.support": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._basecreatewrapper/LICENSE.txt b/src/convert/lodash/lodash._basecreatewrapper/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreatewrapper/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._basecreatewrapper/README.md b/src/convert/lodash/lodash._basecreatewrapper/README.md
new file mode 100644
index 0000000..8400ff6
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreatewrapper/README.md
@@ -0,0 +1,15 @@
+# lodash._basecreatewrapper v2.3.0
+
+The internal [Lo-Dash](http://lodash.com/) function `baseCreateWrapper` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash._basecreatewrapper/index.js b/src/convert/lodash/lodash._basecreatewrapper/index.js
new file mode 100644
index 0000000..50309fb
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreatewrapper/index.js
@@ -0,0 +1,78 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var baseCreate = require('./../lodash._basecreate'),
+ isObject = require('./../lodash.isobject'),
+ setBindData = require('./../lodash._setbinddata'),
+ slice = require('./../lodash._slice');
+
+/**
+ * Used for `Array` method references.
+ *
+ * Normally `Array.prototype` would suffice, however, using an array literal
+ * avoids issues in Narwhal.
+ */
+var arrayRef = [];
+
+/** Native method shortcuts */
+var push = arrayRef.push;
+
+/**
+ * The base implementation of `createWrapper` that creates the wrapper and
+ * sets its meta data.
+ *
+ * @private
+ * @param {Array} bindData The bind data array.
+ * @returns {Function} Returns the new function.
+ */
+function baseCreateWrapper(bindData) {
+ var func = bindData[0],
+ bitmask = bindData[1],
+ partialArgs = bindData[2],
+ partialRightArgs = bindData[3],
+ thisArg = bindData[4],
+ arity = bindData[5];
+
+ var isBind = bitmask & 1,
+ isBindKey = bitmask & 2,
+ isCurry = bitmask & 4,
+ isCurryBound = bitmask & 8,
+ key = func;
+
+ function bound() {
+ var thisBinding = isBind ? thisArg : this;
+ if (partialArgs) {
+ var args = partialArgs.slice();
+ push.apply(args, arguments);
+ }
+ if (partialRightArgs || isCurry) {
+ args || (args = slice(arguments));
+ if (partialRightArgs) {
+ push.apply(args, partialRightArgs);
+ }
+ if (isCurry && args.length < arity) {
+ bitmask |= 16 & ~32;
+ return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
+ }
+ }
+ args || (args = arguments);
+ if (isBindKey) {
+ func = thisBinding[key];
+ }
+ if (this instanceof bound) {
+ thisBinding = baseCreate(func.prototype);
+ var result = func.apply(thisBinding, args);
+ return isObject(result) ? result : thisBinding;
+ }
+ return func.apply(thisBinding, args);
+ }
+ setBindData(bound, bindData);
+ return bound;
+}
+
+module.exports = baseCreateWrapper;
diff --git a/src/convert/lodash/lodash._basecreatewrapper/package.json b/src/convert/lodash/lodash._basecreatewrapper/package.json
new file mode 100644
index 0000000..13ad4a2
--- /dev/null
+++ b/src/convert/lodash/lodash._basecreatewrapper/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "lodash._basecreatewrapper",
+ "version": "2.3.0",
+ "description": "The internal Lo-Dash function `baseCreateWrapper` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash._basecreate": "~2.3.0",
+ "lodash.isobject": "~2.3.0",
+ "lodash._setbinddata": "~2.3.0",
+ "lodash._slice": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._createwrapper/LICENSE.txt b/src/convert/lodash/lodash._createwrapper/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash._createwrapper/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._createwrapper/README.md b/src/convert/lodash/lodash._createwrapper/README.md
new file mode 100644
index 0000000..649ef36
--- /dev/null
+++ b/src/convert/lodash/lodash._createwrapper/README.md
@@ -0,0 +1,15 @@
+# lodash._createwrapper v2.3.0
+
+The internal [Lo-Dash](http://lodash.com/) function `createWrapper` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash._createwrapper/index.js b/src/convert/lodash/lodash._createwrapper/index.js
new file mode 100644
index 0000000..b2b8de3
--- /dev/null
+++ b/src/convert/lodash/lodash._createwrapper/index.js
@@ -0,0 +1,98 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var baseBind = require('./../lodash._basebind'),
+ baseCreateWrapper = require('./../lodash._basecreatewrapper'),
+ isFunction = require('./../lodash.isfunction');
+
+/**
+ * Used for `Array` method references.
+ *
+ * Normally `Array.prototype` would suffice, however, using an array literal
+ * avoids issues in Narwhal.
+ */
+var arrayRef = [];
+
+/** Native method shortcuts */
+var push = arrayRef.push;
+
+/**
+ * Creates a function that, when called, either curries or invokes `func`
+ * with an optional `this` binding and partially applied arguments.
+ *
+ * @private
+ * @param {Function|string} func The function or method name to reference.
+ * @param {number} bitmask The bitmask of method flags to compose.
+ * The bitmask may be composed of the following flags:
+ * 1 - `_.bind`
+ * 2 - `_.bindKey`
+ * 4 - `_.curry`
+ * 8 - `_.curry` (bound)
+ * 16 - `_.partial`
+ * 32 - `_.partialRight`
+ * @param {Array} [partialArgs] An array of arguments to prepend to those
+ * provided to the new function.
+ * @param {Array} [partialRightArgs] An array of arguments to append to those
+ * provided to the new function.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new function.
+ */
+function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
+ var isBind = bitmask & 1,
+ isBindKey = bitmask & 2,
+ isCurry = bitmask & 4,
+ isCurryBound = bitmask & 8,
+ isPartial = bitmask & 16,
+ isPartialRight = bitmask & 32;
+
+ if (!isBindKey && !isFunction(func)) {
+ throw new TypeError;
+ }
+ if (isPartial && !partialArgs.length) {
+ bitmask &= ~16;
+ isPartial = partialArgs = false;
+ }
+ if (isPartialRight && !partialRightArgs.length) {
+ bitmask &= ~32;
+ isPartialRight = partialRightArgs = false;
+ }
+ var bindData = func && func.__bindData__;
+ if (bindData && bindData !== true) {
+ bindData = bindData.slice();
+
+ // set `thisBinding` is not previously bound
+ if (isBind && !(bindData[1] & 1)) {
+ bindData[4] = thisArg;
+ }
+ // set if previously bound but not currently (subsequent curried functions)
+ if (!isBind && bindData[1] & 1) {
+ bitmask |= 8;
+ }
+ // set curried arity if not yet set
+ if (isCurry && !(bindData[1] & 4)) {
+ bindData[5] = arity;
+ }
+ // append partial left arguments
+ if (isPartial) {
+ push.apply(bindData[2] || (bindData[2] = []), partialArgs);
+ }
+ // append partial right arguments
+ if (isPartialRight) {
+ push.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
+ }
+ // merge flags
+ bindData[1] |= bitmask;
+ return createWrapper.apply(null, bindData);
+ }
+ // fast path for `_.bind`
+ var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
+ return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
+}
+
+module.exports = createWrapper;
diff --git a/src/convert/lodash/lodash._createwrapper/package.json b/src/convert/lodash/lodash._createwrapper/package.json
new file mode 100644
index 0000000..509eccb
--- /dev/null
+++ b/src/convert/lodash/lodash._createwrapper/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "lodash._createwrapper",
+ "version": "2.3.0",
+ "description": "The internal Lo-Dash function `createWrapper` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash._basebind": "~2.3.0",
+ "lodash._basecreatewrapper": "~2.3.0",
+ "lodash.isfunction": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._objecttypes/LICENSE.txt b/src/convert/lodash/lodash._objecttypes/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash._objecttypes/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._objecttypes/README.md b/src/convert/lodash/lodash._objecttypes/README.md
new file mode 100644
index 0000000..58bd76e
--- /dev/null
+++ b/src/convert/lodash/lodash._objecttypes/README.md
@@ -0,0 +1,15 @@
+# lodash._objecttypes v2.3.0
+
+The internal [Lo-Dash](http://lodash.com/) variable `objectTypes` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash._objecttypes/index.js b/src/convert/lodash/lodash._objecttypes/index.js
new file mode 100644
index 0000000..f24fa98
--- /dev/null
+++ b/src/convert/lodash/lodash._objecttypes/index.js
@@ -0,0 +1,20 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+
+/** Used to determine if values are of the language type Object */
+var objectTypes = {
+ 'boolean': false,
+ 'function': true,
+ 'object': true,
+ 'number': false,
+ 'string': false,
+ 'undefined': false
+};
+
+module.exports = objectTypes;
diff --git a/src/convert/lodash/lodash._objecttypes/package.json b/src/convert/lodash/lodash._objecttypes/package.json
new file mode 100644
index 0000000..2dc2610
--- /dev/null
+++ b/src/convert/lodash/lodash._objecttypes/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "lodash._objecttypes",
+ "version": "2.3.0",
+ "description": "The internal Lo-Dash variable `objectTypes` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._renative/LICENSE.txt b/src/convert/lodash/lodash._renative/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash._renative/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._renative/README.md b/src/convert/lodash/lodash._renative/README.md
new file mode 100644
index 0000000..0d9a605
--- /dev/null
+++ b/src/convert/lodash/lodash._renative/README.md
@@ -0,0 +1,15 @@
+# lodash._renative v2.3.0
+
+The internal [Lo-Dash](http://lodash.com/) variable `reNative` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash._renative/index.js b/src/convert/lodash/lodash._renative/index.js
new file mode 100644
index 0000000..41c1476
--- /dev/null
+++ b/src/convert/lodash/lodash._renative/index.js
@@ -0,0 +1,23 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+
+/** Used for native method references */
+var objectProto = Object.prototype;
+
+/** Used to resolve the internal [[Class]] of values */
+var toString = objectProto.toString;
+
+/** Used to detect if a method is native */
+var reNative = RegExp('^' +
+ String(toString)
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ .replace(/toString| for [^\]]+/g, '.*?') + '$'
+);
+
+module.exports = reNative;
diff --git a/src/convert/lodash/lodash._renative/package.json b/src/convert/lodash/lodash._renative/package.json
new file mode 100644
index 0000000..24bdaaf
--- /dev/null
+++ b/src/convert/lodash/lodash._renative/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "lodash._renative",
+ "version": "2.3.0",
+ "description": "The internal Lo-Dash variable `reNative` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._setbinddata/LICENSE.txt b/src/convert/lodash/lodash._setbinddata/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash._setbinddata/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._setbinddata/README.md b/src/convert/lodash/lodash._setbinddata/README.md
new file mode 100644
index 0000000..e7ab45d
--- /dev/null
+++ b/src/convert/lodash/lodash._setbinddata/README.md
@@ -0,0 +1,15 @@
+# lodash._setbinddata v2.3.0
+
+The internal [Lo-Dash](http://lodash.com/) function `setBindData` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash._setbinddata/index.js b/src/convert/lodash/lodash._setbinddata/index.js
new file mode 100644
index 0000000..7e86d4c
--- /dev/null
+++ b/src/convert/lodash/lodash._setbinddata/index.js
@@ -0,0 +1,43 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var noop = require('./../lodash.noop'),
+ reNative = require('./../lodash._renative');
+
+/** Used as the property descriptor for `__bindData__` */
+var descriptor = {
+ 'configurable': false,
+ 'enumerable': false,
+ 'value': null,
+ 'writable': false
+};
+
+/** Used to set meta data on functions */
+var defineProperty = (function() {
+ // IE 8 only accepts DOM elements
+ try {
+ var o = {},
+ func = reNative.test(func = Object.defineProperty) && func,
+ result = func(o, o, o) && func;
+ } catch(e) { }
+ return result;
+}());
+
+/**
+ * Sets `this` binding data on a given function.
+ *
+ * @private
+ * @param {Function} func The function to set data on.
+ * @param {Array} value The data array to set.
+ */
+var setBindData = !defineProperty ? noop : function(func, value) {
+ descriptor.value = value;
+ defineProperty(func, '__bindData__', descriptor);
+};
+
+module.exports = setBindData;
diff --git a/src/convert/lodash/lodash._setbinddata/package.json b/src/convert/lodash/lodash._setbinddata/package.json
new file mode 100644
index 0000000..4185301
--- /dev/null
+++ b/src/convert/lodash/lodash._setbinddata/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "lodash._setbinddata",
+ "version": "2.3.0",
+ "description": "The internal Lo-Dash function `setBindData` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash.noop": "~2.3.0",
+ "lodash._renative": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._shimkeys/LICENSE.txt b/src/convert/lodash/lodash._shimkeys/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash._shimkeys/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._shimkeys/README.md b/src/convert/lodash/lodash._shimkeys/README.md
new file mode 100644
index 0000000..4ce09c9
--- /dev/null
+++ b/src/convert/lodash/lodash._shimkeys/README.md
@@ -0,0 +1,15 @@
+# lodash._shimkeys v2.3.0
+
+The internal [Lo-Dash](http://lodash.com/) function `shimKeys` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash._shimkeys/index.js b/src/convert/lodash/lodash._shimkeys/index.js
new file mode 100644
index 0000000..4f3e9eb
--- /dev/null
+++ b/src/convert/lodash/lodash._shimkeys/index.js
@@ -0,0 +1,38 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var objectTypes = require('./../lodash._objecttypes');
+
+/** Used for native method references */
+var objectProto = Object.prototype;
+
+/** Native method shortcuts */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * A fallback implementation of `Object.keys` which produces an array of the
+ * given object's own enumerable property names.
+ *
+ * @private
+ * @type Function
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property names.
+ */
+var shimKeys = function(object) {
+ var index, iterable = object, result = [];
+ if (!iterable) return result;
+ if (!(objectTypes[typeof object])) return result;
+ for (index in iterable) {
+ if (hasOwnProperty.call(iterable, index)) {
+ result.push(index);
+ }
+ }
+ return result
+};
+
+module.exports = shimKeys;
diff --git a/src/convert/lodash/lodash._shimkeys/package.json b/src/convert/lodash/lodash._shimkeys/package.json
new file mode 100644
index 0000000..99b6f68
--- /dev/null
+++ b/src/convert/lodash/lodash._shimkeys/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "lodash._shimkeys",
+ "version": "2.3.0",
+ "description": "The internal Lo-Dash function `shimKeys` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash._objecttypes": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._slice/LICENSE.txt b/src/convert/lodash/lodash._slice/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash._slice/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash._slice/README.md b/src/convert/lodash/lodash._slice/README.md
new file mode 100644
index 0000000..f771fbe
--- /dev/null
+++ b/src/convert/lodash/lodash._slice/README.md
@@ -0,0 +1,15 @@
+# lodash._slice v2.3.0
+
+The internal [Lo-Dash](http://lodash.com/) function `slice` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash._slice/index.js b/src/convert/lodash/lodash._slice/index.js
new file mode 100644
index 0000000..bb3f449
--- /dev/null
+++ b/src/convert/lodash/lodash._slice/index.js
@@ -0,0 +1,38 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+
+/**
+ * Slices the `collection` from the `start` index up to, but not including,
+ * the `end` index.
+ *
+ * Note: This function is used instead of `Array#slice` to support node lists
+ * in IE < 9 and to ensure dense arrays are returned.
+ *
+ * @private
+ * @param {Array|Object|string} collection The collection to slice.
+ * @param {number} start The start index.
+ * @param {number} end The end index.
+ * @returns {Array} Returns the new array.
+ */
+function slice(array, start, end) {
+ start || (start = 0);
+ if (typeof end == 'undefined') {
+ end = array ? array.length : 0;
+ }
+ var index = -1,
+ length = end - start || 0,
+ result = Array(length < 0 ? 0 : length);
+
+ while (++index < length) {
+ result[index] = array[start + index];
+ }
+ return result;
+}
+
+module.exports = slice;
diff --git a/src/convert/lodash/lodash._slice/package.json b/src/convert/lodash/lodash._slice/package.json
new file mode 100644
index 0000000..4ad7827
--- /dev/null
+++ b/src/convert/lodash/lodash._slice/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "lodash._slice",
+ "version": "2.3.0",
+ "description": "The internal Lo-Dash function `slice` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.bind/LICENSE.txt b/src/convert/lodash/lodash.bind/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash.bind/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.bind/README.md b/src/convert/lodash/lodash.bind/README.md
new file mode 100644
index 0000000..d2ef202
--- /dev/null
+++ b/src/convert/lodash/lodash.bind/README.md
@@ -0,0 +1,15 @@
+# lodash.bind v2.3.0
+
+The [Lo-Dash](http://lodash.com/) function [`_.bind`](http://lodash.com/docs#bind) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash.bind/index.js b/src/convert/lodash/lodash.bind/index.js
new file mode 100644
index 0000000..dc87bee
--- /dev/null
+++ b/src/convert/lodash/lodash.bind/index.js
@@ -0,0 +1,41 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var createWrapper = require('./../lodash._createwrapper'),
+ reNative = require('./../lodash._renative'),
+ slice = require('./../lodash._slice');
+
+/**
+ * Creates a function that, when called, invokes `func` with the `this`
+ * binding of `thisArg` and prepends any additional `bind` arguments to those
+ * provided to the bound function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to bind.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var func = function(greeting) {
+ * return greeting + ' ' + this.name;
+ * };
+ *
+ * func = _.bind(func, { 'name': 'fred' }, 'hi');
+ * func();
+ * // => 'hi fred'
+ */
+function bind(func, thisArg) {
+ return arguments.length > 2
+ ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
+ : createWrapper(func, 1, null, null, thisArg);
+}
+
+module.exports = bind;
diff --git a/src/convert/lodash/lodash.bind/package.json b/src/convert/lodash/lodash.bind/package.json
new file mode 100644
index 0000000..734669a
--- /dev/null
+++ b/src/convert/lodash/lodash.bind/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "lodash.bind",
+ "version": "2.3.0",
+ "description": "The Lo-Dash function `_.bind` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash._createwrapper": "~2.3.0",
+ "lodash._renative": "~2.3.0",
+ "lodash._slice": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.foreach/LICENSE.txt b/src/convert/lodash/lodash.foreach/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash.foreach/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.foreach/README.md b/src/convert/lodash/lodash.foreach/README.md
new file mode 100644
index 0000000..2fbd249
--- /dev/null
+++ b/src/convert/lodash/lodash.foreach/README.md
@@ -0,0 +1,15 @@
+# lodash.foreach v2.3.0
+
+The [Lo-Dash](http://lodash.com/) function [`_.forEach`](http://lodash.com/docs#forEach) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash.foreach/index.js b/src/convert/lodash/lodash.foreach/index.js
new file mode 100644
index 0000000..ac2818f
--- /dev/null
+++ b/src/convert/lodash/lodash.foreach/index.js
@@ -0,0 +1,55 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var baseCreateCallback = require('./../lodash._basecreatecallback'),
+ forOwn = require('./../lodash.forown');
+
+/**
+ * Iterates over elements of a collection, executing the callback for each
+ * element. The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection). Callbacks may exit iteration early by
+ * explicitly returning `false`.
+ *
+ * Note: As with other "Collections" methods, objects with a `length` property
+ * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
+ * may be used for object iteration.
+ *
+ * @static
+ * @memberOf _
+ * @alias each
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array|Object|string} Returns `collection`.
+ * @example
+ *
+ * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
+ * // => logs each number and returns '1,2,3'
+ *
+ * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
+ * // => logs each number and returns the object (property order is not guaranteed across environments)
+ */
+function forEach(collection, callback, thisArg) {
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+ if (typeof length == 'number') {
+ while (++index < length) {
+ if (callback(collection[index], index, collection) === false) {
+ break;
+ }
+ }
+ } else {
+ forOwn(collection, callback);
+ }
+ return collection;
+}
+
+module.exports = forEach;
diff --git a/src/convert/lodash/lodash.foreach/package.json b/src/convert/lodash/lodash.foreach/package.json
new file mode 100644
index 0000000..0a2f005
--- /dev/null
+++ b/src/convert/lodash/lodash.foreach/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "lodash.foreach",
+ "version": "2.3.0",
+ "description": "The Lo-Dash function `_.forEach` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash._basecreatecallback": "~2.3.0",
+ "lodash.forown": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.forown/LICENSE.txt b/src/convert/lodash/lodash.forown/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash.forown/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.forown/README.md b/src/convert/lodash/lodash.forown/README.md
new file mode 100644
index 0000000..dd2d34c
--- /dev/null
+++ b/src/convert/lodash/lodash.forown/README.md
@@ -0,0 +1,15 @@
+# lodash.forown v2.3.0
+
+The [Lo-Dash](http://lodash.com/) function [`_.forOwn`](http://lodash.com/docs#forOwn) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash.forown/index.js b/src/convert/lodash/lodash.forown/index.js
new file mode 100644
index 0000000..b76999e
--- /dev/null
+++ b/src/convert/lodash/lodash.forown/index.js
@@ -0,0 +1,50 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var baseCreateCallback = require('./../lodash._basecreatecallback'),
+ keys = require('./../lodash.keys'),
+ objectTypes = require('./../lodash._objecttypes');
+
+/**
+ * Iterates over own enumerable properties of an object, executing the callback
+ * for each property. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, key, object). Callbacks may exit iteration early by
+ * explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+ * console.log(key);
+ * });
+ * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
+ */
+var forOwn = function(collection, callback, thisArg) {
+ var index, iterable = collection, result = iterable;
+ if (!iterable) return result;
+ if (!objectTypes[typeof iterable]) return result;
+ callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+ var ownIndex = -1,
+ ownProps = objectTypes[typeof iterable] && keys(iterable),
+ length = ownProps ? ownProps.length : 0;
+
+ while (++ownIndex < length) {
+ index = ownProps[ownIndex];
+ if (callback(iterable[index], index, collection) === false) return result;
+ }
+ return result
+};
+
+module.exports = forOwn;
diff --git a/src/convert/lodash/lodash.forown/package.json b/src/convert/lodash/lodash.forown/package.json
new file mode 100644
index 0000000..079de45
--- /dev/null
+++ b/src/convert/lodash/lodash.forown/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "lodash.forown",
+ "version": "2.3.0",
+ "description": "The Lo-Dash function `_.forOwn` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash._basecreatecallback": "~2.3.0",
+ "lodash.keys": "~2.3.0",
+ "lodash._objecttypes": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.identity/LICENSE.txt b/src/convert/lodash/lodash.identity/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash.identity/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.identity/README.md b/src/convert/lodash/lodash.identity/README.md
new file mode 100644
index 0000000..8c57239
--- /dev/null
+++ b/src/convert/lodash/lodash.identity/README.md
@@ -0,0 +1,15 @@
+# lodash.identity v2.3.0
+
+The [Lo-Dash](http://lodash.com/) function [`_.identity`](http://lodash.com/docs#identity) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash.identity/index.js b/src/convert/lodash/lodash.identity/index.js
new file mode 100644
index 0000000..595a6e4
--- /dev/null
+++ b/src/convert/lodash/lodash.identity/index.js
@@ -0,0 +1,28 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+
+/**
+ * This method returns the first argument provided to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {*} value Any value.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * _.identity(object) === object;
+ * // => true
+ */
+function identity(value) {
+ return value;
+}
+
+module.exports = identity;
diff --git a/src/convert/lodash/lodash.identity/package.json b/src/convert/lodash/lodash.identity/package.json
new file mode 100644
index 0000000..1e09a5e
--- /dev/null
+++ b/src/convert/lodash/lodash.identity/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "lodash.identity",
+ "version": "2.3.0",
+ "description": "The Lo-Dash function `_.identity` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.isfunction/LICENSE.txt b/src/convert/lodash/lodash.isfunction/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash.isfunction/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.isfunction/README.md b/src/convert/lodash/lodash.isfunction/README.md
new file mode 100644
index 0000000..e16c6a3
--- /dev/null
+++ b/src/convert/lodash/lodash.isfunction/README.md
@@ -0,0 +1,15 @@
+# lodash.isfunction v2.3.0
+
+The [Lo-Dash](http://lodash.com/) function [`_.isFunction`](http://lodash.com/docs#isFunction) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash.isfunction/index.js b/src/convert/lodash/lodash.isfunction/index.js
new file mode 100644
index 0000000..2b333d3
--- /dev/null
+++ b/src/convert/lodash/lodash.isfunction/index.js
@@ -0,0 +1,27 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+
+/**
+ * Checks if `value` is a function.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ */
+function isFunction(value) {
+ return typeof value == 'function';
+}
+
+module.exports = isFunction;
diff --git a/src/convert/lodash/lodash.isfunction/package.json b/src/convert/lodash/lodash.isfunction/package.json
new file mode 100644
index 0000000..446ae6d
--- /dev/null
+++ b/src/convert/lodash/lodash.isfunction/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "lodash.isfunction",
+ "version": "2.3.0",
+ "description": "The Lo-Dash function `_.isFunction` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.isobject/LICENSE.txt b/src/convert/lodash/lodash.isobject/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash.isobject/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.isobject/README.md b/src/convert/lodash/lodash.isobject/README.md
new file mode 100644
index 0000000..897d749
--- /dev/null
+++ b/src/convert/lodash/lodash.isobject/README.md
@@ -0,0 +1,15 @@
+# lodash.isobject v2.3.0
+
+The [Lo-Dash](http://lodash.com/) function [`_.isObject`](http://lodash.com/docs#isObject) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash.isobject/index.js b/src/convert/lodash/lodash.isobject/index.js
new file mode 100644
index 0000000..fbd5c9c
--- /dev/null
+++ b/src/convert/lodash/lodash.isobject/index.js
@@ -0,0 +1,39 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var objectTypes = require('./../lodash._objecttypes');
+
+/**
+ * Checks if `value` is the language type of Object.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(1);
+ * // => false
+ */
+function isObject(value) {
+ // check if the value is the ECMAScript language type of Object
+ // http://es5.github.io/#x8
+ // and avoid a V8 bug
+ // http://code.google.com/p/v8/issues/detail?id=2291
+ return !!(value && objectTypes[typeof value]);
+}
+
+module.exports = isObject;
diff --git a/src/convert/lodash/lodash.isobject/package.json b/src/convert/lodash/lodash.isobject/package.json
new file mode 100644
index 0000000..e032a9a
--- /dev/null
+++ b/src/convert/lodash/lodash.isobject/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "lodash.isobject",
+ "version": "2.3.0",
+ "description": "The Lo-Dash function `_.isObject` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash._objecttypes": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.keys/LICENSE.txt b/src/convert/lodash/lodash.keys/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash.keys/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.keys/README.md b/src/convert/lodash/lodash.keys/README.md
new file mode 100644
index 0000000..5fc0154
--- /dev/null
+++ b/src/convert/lodash/lodash.keys/README.md
@@ -0,0 +1,15 @@
+# lodash.keys v2.3.0
+
+The [Lo-Dash](http://lodash.com/) function [`_.keys`](http://lodash.com/docs#keys) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash.keys/index.js b/src/convert/lodash/lodash.keys/index.js
new file mode 100644
index 0000000..cc6e7f1
--- /dev/null
+++ b/src/convert/lodash/lodash.keys/index.js
@@ -0,0 +1,36 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var isObject = require('./../lodash.isobject'),
+ reNative = require('./../lodash._renative'),
+ shimKeys = require('./../lodash._shimkeys');
+
+/* Native method shortcuts for methods with the same name as other `lodash` methods */
+var nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys;
+
+/**
+ * Creates an array composed of the own enumerable property names of an object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property names.
+ * @example
+ *
+ * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
+ */
+var keys = !nativeKeys ? shimKeys : function(object) {
+ if (!isObject(object)) {
+ return [];
+ }
+ return nativeKeys(object);
+};
+
+module.exports = keys;
diff --git a/src/convert/lodash/lodash.keys/package.json b/src/convert/lodash/lodash.keys/package.json
new file mode 100644
index 0000000..a6ad91b
--- /dev/null
+++ b/src/convert/lodash/lodash.keys/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "lodash.keys",
+ "version": "2.3.0",
+ "description": "The Lo-Dash function `_.keys` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash.isobject": "~2.3.0",
+ "lodash._renative": "~2.3.0",
+ "lodash._shimkeys": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.noop/LICENSE.txt b/src/convert/lodash/lodash.noop/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash.noop/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.noop/README.md b/src/convert/lodash/lodash.noop/README.md
new file mode 100644
index 0000000..c277049
--- /dev/null
+++ b/src/convert/lodash/lodash.noop/README.md
@@ -0,0 +1,15 @@
+# lodash.noop v2.3.0
+
+The [Lo-Dash](http://lodash.com/) function [`_.noop`](http://lodash.com/docs#noop) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash.noop/index.js b/src/convert/lodash/lodash.noop/index.js
new file mode 100644
index 0000000..4d43843
--- /dev/null
+++ b/src/convert/lodash/lodash.noop/index.js
@@ -0,0 +1,26 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+
+/**
+ * A no-operation function.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * _.noop(object) === undefined;
+ * // => true
+ */
+function noop() {
+ // no operation performed
+}
+
+module.exports = noop;
diff --git a/src/convert/lodash/lodash.noop/package.json b/src/convert/lodash/lodash.noop/package.json
new file mode 100644
index 0000000..2849bdd
--- /dev/null
+++ b/src/convert/lodash/lodash.noop/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "lodash.noop",
+ "version": "2.3.0",
+ "description": "The Lo-Dash function `_.noop` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" }
+}
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.support/LICENSE.txt b/src/convert/lodash/lodash.support/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/src/convert/lodash/lodash.support/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors
+
+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.
\ No newline at end of file
diff --git a/src/convert/lodash/lodash.support/README.md b/src/convert/lodash/lodash.support/README.md
new file mode 100644
index 0000000..905f3a7
--- /dev/null
+++ b/src/convert/lodash/lodash.support/README.md
@@ -0,0 +1,15 @@
+# lodash.support v2.3.0
+
+The [Lo-Dash](http://lodash.com/) object [`_.support`](http://lodash.com/docs#support) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
+
+## Author
+
+| [](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
diff --git a/src/convert/lodash/lodash.support/index.js b/src/convert/lodash/lodash.support/index.js
new file mode 100644
index 0000000..d4830bf
--- /dev/null
+++ b/src/convert/lodash/lodash.support/index.js
@@ -0,0 +1,40 @@
+/**
+ * Lo-Dash 2.3.0 (Custom Build)
+ * Build: `lodash modularize modern exports="npm" -o ./npm/`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+var reNative = require('./../lodash._renative');
+
+/** Used to detect functions containing a `this` reference */
+var reThis = /\bthis\b/;
+
+/**
+ * An object used to flag environments features.
+ *
+ * @static
+ * @memberOf _
+ * @type Object
+ */
+var support = {};
+
+/**
+ * Detect if functions can be decompiled by `Function#toString`
+ * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+support.funcDecomp = !reNative.test(global.WinRTError) && reThis.test(function() { return this; });
+
+/**
+ * Detect if `Function#name` is supported (all but IE).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+support.funcNames = typeof Function.name == 'string';
+
+module.exports = support;
diff --git a/src/convert/lodash/lodash.support/package.json b/src/convert/lodash/lodash.support/package.json
new file mode 100644
index 0000000..86bacf4
--- /dev/null
+++ b/src/convert/lodash/lodash.support/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "lodash.support",
+ "version": "2.3.0",
+ "description": "The Lo-Dash object `_.support` as a Node.js module generated by lodash-cli.",
+ "homepage": "http://lodash.com/custom-builds",
+ "license": "MIT",
+ "keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
+ "author": "John-David Dalton (http://allyoucanleet.com/)",
+ "contributors": [
+ "John-David Dalton (http://allyoucanleet.com/)",
+ "Blaine Bublitz (http://www.iceddev.com/)",
+ "Kit Cambridge (http://kitcambridge.be/)",
+ "Mathias Bynens (http://mathiasbynens.be/)"
+ ],
+ "bugs": "https://github.com/lodash/lodash-cli/issues",
+ "repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
+ "dependencies": {
+ "lodash._renative": "~2.3.0"
+ }
+}
\ No newline at end of file
diff --git a/src/convert/percentError.js b/src/convert/percentError.js
new file mode 100644
index 0000000..8ee2a3a
--- /dev/null
+++ b/src/convert/percentError.js
@@ -0,0 +1,3 @@
+module.exports = function (expected, actual) {
+ return Math.abs((expected - actual) / actual);
+}
diff --git a/src/helper/menuUtils.js b/src/helper/menuUtils.js
index 3e9d474..9d0352a 100644
--- a/src/helper/menuUtils.js
+++ b/src/helper/menuUtils.js
@@ -1,4 +1,6 @@
-export function initBasicToggles(elements) {
+class MenuUtils {
+
+initBasicToggles(elements) {
// Toggle visibility for log level
elements.logCheckbox.addEventListener("change", function () {
elements.rowLogLevel.style.display = this.checked ? "block" : "none";
@@ -9,7 +11,7 @@ export function initBasicToggles(elements) {
}
// Define the initialize toggles function within scope
-export function initMeasurementToggles(elements) {
+initMeasurementToggles(elements) {
// Toggle visibility for scaling inputs
elements.scalingCheckbox.addEventListener("change", function () {
elements.rowInputMin.style.display = this.checked ? "block" : "none";
@@ -25,7 +27,7 @@ export function initMeasurementToggles(elements) {
: "none";
}
-export function initTensionToggles(elements, node) {
+initTensionToggles(elements, node) {
const currentMethod = node.interpolationMethod;
elements.rowTension.style.display =
currentMethod === "monotone_cubic_spline" ? "block" : "none";
@@ -46,14 +48,14 @@ export function initTensionToggles(elements, node) {
});
}
// Define the smoothing methods population function within scope
-export function populateSmoothingMethods(configUrls, elements, node) {
- fetchData(configUrls.cloud.config, configUrls.local.config)
+populateSmoothingMethods(configUrls, elements, node) {
+ this.fetchData(configUrls.cloud.config, configUrls.local.config)
.then((configData) => {
const smoothingMethods =
configData.smoothing?.smoothMethod?.rules?.values?.map(
(o) => o.value
) || [];
- populateDropdown(
+ this.populateDropdown(
elements.smoothMethod,
smoothingMethods,
node,
@@ -65,13 +67,13 @@ export function populateSmoothingMethods(configUrls, elements, node) {
});
}
-export function populateInterpolationMethods(configUrls, elements, node) {
- fetchData(configUrls.cloud.config, configUrls.local.config)
+populateInterpolationMethods(configUrls, elements, node) {
+ this.fetchData(configUrls.cloud.config, configUrls.local.config)
.then((configData) => {
const interpolationMethods =
configData?.interpolation?.type?.rules?.values.map((m) => m.value) ||
[];
- populateDropdown(
+ this.populateDropdown(
elements.interpolationMethodInput,
interpolationMethods,
node,
@@ -80,14 +82,14 @@ export function populateInterpolationMethods(configUrls, elements, node) {
// Find the selected method and use it to spawn 1 more field to fill in tension
//const selectedMethod = interpolationMethods.find(m => m === node.interpolationMethod);
- initTensionToggles(elements, node);
+ this.initTensionToggles(elements, node);
})
.catch((err) => {
console.error("Error loading interpolation methods", err);
});
}
-export function populateLogLevelOptions(logLevelSelect, configData, node) {
+populateLogLevelOptions(logLevelSelect, configData, node) {
// debug log level
//console.log("Displaying configData => ", configData) ;
@@ -99,32 +101,31 @@ export function populateLogLevelOptions(logLevelSelect, configData, node) {
//console.log("Displaying logLevels => ", logLevels);
// Reuse your existing generic populateDropdown helper
- populateDropdown(logLevelSelect, logLevels, node.logLevel);
+ this.populateDropdown(logLevelSelect, logLevels, node.logLevel);
}
//cascade dropdowns for asset type, supplier, subType, model, unit
-export function fetchAndPopulateDropdowns(configUrls, elements, node) {
-
- fetchData(configUrls.cloud.config, configUrls.local.config)
+fetchAndPopulateDropdowns(configUrls, elements, node) {
+ this.fetchData(configUrls.cloud.config, configUrls.local.config)
.then((configData) => {
const assetType = configData.asset?.type?.default;
- const localSuppliersUrl = constructUrl(configUrls.local.taggcodeAPI,`${assetType}s`,"suppliers.json");
- const cloudSuppliersUrl = constructCloudURL(configUrls.cloud.taggcodeAPI, "/vendor/get_vendors.php");
+ const localSuppliersUrl = this.constructUrl(configUrls.local.taggcodeAPI,`${assetType}s`,"suppliers.json");
+ const cloudSuppliersUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/vendor/get_vendors.php");
- return fetchData(cloudSuppliersUrl, localSuppliersUrl)
+ return this.fetchData(cloudSuppliersUrl, localSuppliersUrl)
.then((supplierData) => {
const suppliers = supplierData.map((supplier) => supplier.name);
// Populate suppliers dropdown and set up its change handler
- return populateDropdown(
+ return this.populateDropdown(
elements.supplier,
suppliers,
node,
"supplier",
function (selectedSupplier) {
if (selectedSupplier) {
- populateSubTypes(configUrls, elements, node, selectedSupplier);
+ this.populateSubTypes(configUrls, elements, node, selectedSupplier);
}
}
);
@@ -132,7 +133,7 @@ export function fetchAndPopulateDropdowns(configUrls, elements, node) {
.then(() => {
// If we have a saved supplier, trigger subTypes population
if (node.supplier) {
- populateSubTypes(configUrls, elements, node, node.supplier);
+ this.populateSubTypes(configUrls, elements, node, node.supplier);
}
});
})
@@ -141,17 +142,17 @@ export function fetchAndPopulateDropdowns(configUrls, elements, node) {
});
}
- export function getSpecificConfigUrl(nodeName,cloudAPI) {
+getSpecificConfigUrl(nodeName,cloudAPI) {
const cloudConfigURL = cloudAPI + "/config/" + nodeName + ".json";
const localConfigURL = "http://localhost:1880/"+ nodeName + "/dependencies/"+ nodeName + "/" + nodeName + "Config.json";
return { cloudConfigURL, localConfigURL };
- }
+}
// Save changes to API
-export async function apiCall(node) {
+async apiCall(node) {
try{
// OLFIANT when a browser refreshes the tag code is lost!!! fix this later!!!!!
// FIX UUID ALSO LATER
@@ -218,7 +219,7 @@ export async function apiCall(node) {
}
-export async function fetchData(url, fallbackUrl) {
+async fetchData(url, fallbackUrl) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
@@ -253,7 +254,7 @@ export async function fetchData(url, fallbackUrl) {
}
}
-export async function fetchProjectData(url) {
+async fetchProjectData(url) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
@@ -265,14 +266,14 @@ export async function fetchProjectData(url) {
}
}
-async function populateDropdown(
+async populateDropdown(
htmlElement,
options,
node,
property,
callback
) {
- generateHtml(htmlElement, options, node[property]);
+ this.generateHtml(htmlElement, options, node[property]);
htmlElement.addEventListener("change", async (e) => {
const newValue = e.target.value;
@@ -285,7 +286,7 @@ async function populateDropdown(
}
// Helper function to construct a URL from a base and path internal
-function constructUrl(base, ...paths) {
+constructUrl(base, ...paths) {
// Remove trailing slash from base and leading slashes from paths
const sanitizedBase = (base || "").replace(/\/+$/, "");
@@ -300,7 +301,7 @@ function constructUrl(base, ...paths) {
}
//Adjust for API Gateway
-function constructCloudURL(base, ...paths) {
+constructCloudURL(base, ...paths) {
// Remove trailing slash from base and leading slashes from paths
const sanitizedBase = base.replace(/\/+$/, "");
const sanitizedPaths = paths.map((path) => path.replace(/^\/+|\/+$/g, ""));
@@ -309,21 +310,21 @@ function constructCloudURL(base, ...paths) {
return url;
}
-function populateSubTypes(configUrls, elements, node, selectedSupplier) {
+populateSubTypes(configUrls, elements, node, selectedSupplier) {
- fetchData(configUrls.cloud.config, configUrls.local.config)
+ this.fetchData(configUrls.cloud.config, configUrls.local.config)
.then((configData) => {
const assetType = configData.asset?.type?.default;
- const supplierFolder = constructUrl( configUrls.local.taggcodeAPI, `${assetType}s`, selectedSupplier );
+ const supplierFolder = this.constructUrl( configUrls.local.taggcodeAPI, `${assetType}s`, selectedSupplier );
- const localSubTypesUrl = constructUrl(supplierFolder, "subtypes.json");
- const cloudSubTypesUrl = constructCloudURL(configUrls.cloud.taggcodeAPI, "/product/get_subtypesFromVendor.php?vendor_name=" + selectedSupplier);
+ const localSubTypesUrl = this.constructUrl(supplierFolder, "subtypes.json");
+ const cloudSubTypesUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/product/get_subtypesFromVendor.php?vendor_name=" + selectedSupplier);
- return fetchData(cloudSubTypesUrl, localSubTypesUrl)
+ return this.fetchData(cloudSubTypesUrl, localSubTypesUrl)
.then((subTypeData) => {
const subTypes = subTypeData.map((subType) => subType.name);
- return populateDropdown(
+ return this.populateDropdown(
elements.subType,
subTypes,
node,
@@ -331,14 +332,14 @@ function populateSubTypes(configUrls, elements, node, selectedSupplier) {
function (selectedSubType) {
if (selectedSubType) {
// When subType changes, update both models and units
- populateModels(
+ this.populateModels(
configUrls,
elements,
node,
selectedSupplier,
selectedSubType
);
- populateUnitsForSubType(
+ this.populateUnitsForSubType(
configUrls,
elements,
node,
@@ -351,14 +352,14 @@ function populateSubTypes(configUrls, elements, node, selectedSupplier) {
.then(() => {
// If we have a saved subType, trigger both models and units population
if (node.subType) {
- populateModels(
+ this.populateModels(
configUrls,
elements,
node,
selectedSupplier,
node.subType
);
- populateUnitsForSubType(configUrls, elements, node, node.subType);
+ this.populateUnitsForSubType(configUrls, elements, node, node.subType);
}
//console.log("In fetch part of subtypes ");
// Store all data from selected model
@@ -373,9 +374,9 @@ function populateSubTypes(configUrls, elements, node, selectedSupplier) {
});
}
-function populateUnitsForSubType(configUrls, elements, node, selectedSubType) {
+populateUnitsForSubType(configUrls, elements, node, selectedSubType) {
// Fetch the units data
- fetchData(configUrls.cloud.units, configUrls.local.units)
+ this.fetchData(configUrls.cloud.units, configUrls.local.units)
.then((unitsData) => {
// Find the category that matches the subType name
const categoryData = unitsData.units.find(
@@ -397,7 +398,7 @@ function populateUnitsForSubType(configUrls, elements, node, selectedSubType) {
}));
// Populate the units dropdown
- populateDropdown(
+ this.populateDropdown(
elements.unit,
options.map((opt) => opt.value),
node,
@@ -412,7 +413,7 @@ function populateUnitsForSubType(configUrls, elements, node, selectedSubType) {
} else {
// If no matching category is found, provide a default % option
const defaultUnits = [{ value: "%", description: "Percentage" }];
- populateDropdown(
+ this.populateDropdown(
elements.unit,
defaultUnits.map((unit) => unit.value),
node,
@@ -428,7 +429,7 @@ function populateUnitsForSubType(configUrls, elements, node, selectedSubType) {
});
}
-function populateModels(
+populateModels(
configUrls,
elements,
node,
@@ -436,18 +437,18 @@ function populateModels(
selectedSubType
) {
- fetchData(configUrls.cloud.config, configUrls.local.config)
+ this.fetchData(configUrls.cloud.config, configUrls.local.config)
.then((configData) => {
const assetType = configData.asset?.type?.default;
// save assetType to fetch later
node.assetType = assetType;
- const supplierFolder = constructUrl( configUrls.local.taggcodeAPI,`${assetType}s`,selectedSupplier);
- const subTypeFolder = constructUrl(supplierFolder, selectedSubType);
- const localModelsUrl = constructUrl(subTypeFolder, "models.json");
- const cloudModelsUrl = constructCloudURL(configUrls.cloud.taggcodeAPI, "/product/get_product_models.php?vendor_name=" + selectedSupplier + "&product_subtype_name=" + selectedSubType);
+ const supplierFolder = this.constructUrl( configUrls.local.taggcodeAPI,`${assetType}s`,selectedSupplier);
+ const subTypeFolder = this.constructUrl(supplierFolder, selectedSubType);
+ const localModelsUrl = this.constructUrl(subTypeFolder, "models.json");
+ const cloudModelsUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/product/get_product_models.php?vendor_name=" + selectedSupplier + "&product_subtype_name=" + selectedSubType);
- return fetchData(cloudModelsUrl, localModelsUrl).then((modelData) => {
+ return this.fetchData(cloudModelsUrl, localModelsUrl).then((modelData) => {
const models = modelData.map((model) => model.name); // use this to populate the dropdown
// If a model is already selected, store its metadata immediately
@@ -455,7 +456,7 @@ function populateModels(
node["modelMetadata"] = modelData.find((model) => model.name === node.model);
}
- populateDropdown(elements.model, models, node, "model", (selectedModel) => {
+ this.populateDropdown(elements.model, models, node, "model", (selectedModel) => {
// Store only the metadata for the selected model
node["modelMetadata"] = modelData.find((model) => model.name === selectedModel);
});
@@ -471,7 +472,7 @@ function populateModels(
});
}
-function generateHtml(htmlElement, options, savedValue) {
+generateHtml(htmlElement, options, savedValue) {
htmlElement.innerHTML = options.length
? `${options
.map((opt) => ``)
@@ -482,3 +483,60 @@ function generateHtml(htmlElement, options, savedValue) {
htmlElement.value = savedValue;
}
}
+
+createMenuUtilsEndpoint(RED, nodeName, customHelpers = {}) {
+ RED.httpAdmin.get(`/${nodeName}/resources/menuUtils.js`, function(req, res) {
+ console.log(`Serving menuUtils.js for ${nodeName} node`);
+ res.set('Content-Type', 'application/javascript');
+
+ const browserCode = this.generateMenuUtilsCode(nodeName, customHelpers);
+ res.send(browserCode);
+ }.bind(this));
+ }
+
+generateMenuUtilsCode(nodeName, customHelpers = {}) {
+ const defaultHelpers = {
+ validateRequired: `function(value) {
+ return value && value.toString().trim() !== '';
+ }`,
+ formatDisplayValue: `function(value, unit) {
+ return \`\${value} \${unit || ''}\`.trim();
+ }`
+ };
+
+ const allHelpers = { ...defaultHelpers, ...customHelpers };
+
+ const helpersCode = Object.entries(allHelpers)
+ .map(([name, func]) => ` ${name}: ${func}`)
+ .join(',\n');
+
+ const classCode = MenuUtils.toString(); // <-- this gives full class MenuUtils {...}
+
+ return `
+ // Create EVOLV namespace structure
+ window.EVOLV = window.EVOLV || {};
+ window.EVOLV.nodes = window.EVOLV.nodes || {};
+ window.EVOLV.nodes.${nodeName} = window.EVOLV.nodes.${nodeName} || {};
+
+ // Inject MenuUtils class
+ ${classCode}
+
+ // Expose MenuUtils instance to namespace
+ window.EVOLV.nodes.${nodeName}.utils = {
+ menuUtils: new MenuUtils(),
+
+ helpers: {
+${helpersCode}
+ }
+ };
+
+ // Optionally expose globally
+ window.MenuUtils = MenuUtils;
+
+ console.log('${nodeName} utilities loaded in namespace');
+ `;
+}
+
+}
+
+module.exports = MenuUtils;
\ No newline at end of file
diff --git a/src/measurements/Measurement.js b/src/measurements/Measurement.js
index f9882b5..121e3ac 100644
--- a/src/measurements/Measurement.js
+++ b/src/measurements/Measurement.js
@@ -1,5 +1,5 @@
// Add unit conversion support
-const convertModule = require('../../../convert/dependencies/index');
+const convertModule = require('../convert/index');
class Measurement {
constructor(type, variant, position, windowSize) {
diff --git a/src/nrmse/errorMetrics.js b/src/nrmse/errorMetrics.js
index c567ec4..07b7765 100644
--- a/src/nrmse/errorMetrics.js
+++ b/src/nrmse/errorMetrics.js
@@ -3,7 +3,7 @@ const EventEmitter = require('events');
//load all config modules
const defaultConfig = require('./nrmseConfig.json');
-const ConfigUtils = require('../configUtils');
+const ConfigUtils = require('../helper/configUtils');
class ErrorMetrics {
constructor(config = {}, logger) {
diff --git a/src/state/state.js b/src/state/state.js
index a2ee626..af33641 100644
--- a/src/state/state.js
+++ b/src/state/state.js
@@ -5,7 +5,7 @@ const MovementManager = require('./movementManager');
//load all config modules
const defaultConfig = require('./stateConfig.json');
-const ConfigUtils = require('../../../generalFunctions/helper/configUtils');
+const ConfigUtils = require('../helper/configUtils');
class state{
constructor(config = {}, logger) {