52
indexer/IndexContent.js
Normal file
52
indexer/IndexContent.js
Normal file
@@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
class IndexContent
|
||||
{
|
||||
constructor(presetFilesArray, settings)
|
||||
{
|
||||
this.majorVersion = 1;
|
||||
this.minorVersion = 0;
|
||||
this.settings = settings;
|
||||
this.uniqueValues = {};
|
||||
this.presets = presetFilesArray;
|
||||
|
||||
this.uniqueValues.firmware_version = this._getUniqueValues(presetFilesArray, "firmware_version");
|
||||
this.uniqueValues.category = this._getUniqueValues(presetFilesArray, "category");
|
||||
this.uniqueValues.author = this._getUniqueValues(presetFilesArray, "author");
|
||||
this.uniqueValues.keywords = this._getUniqueValues(presetFilesArray, "keywords");
|
||||
this.uniqueValues.board_name = this._getUniqueValues(presetFilesArray, "board_name");
|
||||
}
|
||||
|
||||
_getUniqueValues(presetFilesArray, property)
|
||||
{
|
||||
let result = new Set();
|
||||
let resultLowerCase = new Set();
|
||||
|
||||
function addValue(value) {
|
||||
const valueLowCase = value.toLowerCase();
|
||||
if (!resultLowerCase.has(valueLowCase)) {
|
||||
result.add(value);
|
||||
resultLowerCase.add(valueLowCase);
|
||||
}
|
||||
}
|
||||
|
||||
for (let preset of presetFilesArray) {
|
||||
if (property in preset) {
|
||||
if (Array.isArray(preset[property])) {
|
||||
for (let value of preset[property]) {
|
||||
addValue(value);
|
||||
}
|
||||
} else {
|
||||
addValue(preset[property]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = [...result];
|
||||
result.sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = IndexContent;
|
||||
473
indexer/PresetsFile.js
Normal file
473
indexer/PresetsFile.js
Normal file
@@ -0,0 +1,473 @@
|
||||
'use strict';
|
||||
|
||||
const readline = require('readline');
|
||||
const fs = require("fs");
|
||||
const crypto = require('crypto');
|
||||
|
||||
class PresetsFile
|
||||
{
|
||||
constructor(fullPath, settings, errors)
|
||||
{
|
||||
this.fullPath = fullPath;
|
||||
this.hash = "";
|
||||
this._presetsFileMetadata = settings.presetsFileMetadata;
|
||||
this._errors = errors;
|
||||
this._settings = settings;
|
||||
this.options = [];
|
||||
this.optionGroups = [];
|
||||
this._currentOption = undefined;
|
||||
this._currentOptionGroup = undefined;
|
||||
|
||||
const binaryFileContent = fs.readFileSync(this.fullPath);
|
||||
let sum = crypto.createHash('sha256');
|
||||
sum.update(binaryFileContent);
|
||||
this.hash = sum.digest('hex');
|
||||
|
||||
this._processLines(binaryFileContent, settings.presetsFileEncoding);
|
||||
this._checkProperties();
|
||||
|
||||
if (undefined === this.priority) {
|
||||
this.priority = this._settings.PresetCategoriesPriorities[this.category];
|
||||
}
|
||||
|
||||
this._clearProperties();
|
||||
}
|
||||
|
||||
_clearProperties()
|
||||
{
|
||||
delete this._presetsFileMetadata;
|
||||
delete this._errors;
|
||||
delete this._settings;
|
||||
delete this._currentOption;
|
||||
delete this._currentOptionGroup;
|
||||
delete this.options;
|
||||
delete this.optionGroups;
|
||||
delete this.description;
|
||||
delete this.include;
|
||||
delete this.discussion;
|
||||
delete this.warning;
|
||||
delete this.disclaimer;
|
||||
delete this.include_warning;
|
||||
delete this.include_disclaimer;
|
||||
delete this.parser;
|
||||
}
|
||||
|
||||
_checkProperties()
|
||||
{
|
||||
for (const [property, value] of Object.entries(this._presetsFileMetadata)) {
|
||||
if (!value.optional) {
|
||||
if (this._isEmptyProperty(this[property]))
|
||||
{
|
||||
this._addError(`missing or empty property '${property}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (undefined !== this._currentOption) {
|
||||
this._addError(`Missing ${this._settings.OptionsDirectives.END_OPTION_DIRECTIVE} for ${this._currentOption.name}`);
|
||||
}
|
||||
|
||||
if (undefined !== this._currentOptionGroup) {
|
||||
this._addError(`Missing ${this._settings.OptionsDirectives.END_OPTION_GROUP_DIRECTIVE} for ${this._currentOptionGroup.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
_isEmptyProperty(property)
|
||||
{
|
||||
return (property === undefined || property.length === 0);
|
||||
}
|
||||
|
||||
_processLines(binaryFileContent, presetsFileEncoding)
|
||||
{
|
||||
const fileContent = binaryFileContent.toString(presetsFileEncoding);
|
||||
const lines = fileContent.split('\n');
|
||||
|
||||
this._currentLine = 1;
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
if (line.startsWith(this._settings.MetapropertyDirective)) {
|
||||
this._processMetapropertyLine(line);
|
||||
}
|
||||
|
||||
this._currentLine++;
|
||||
}
|
||||
|
||||
delete this._currentLine;
|
||||
}
|
||||
|
||||
_processMetapropertyLine(line)
|
||||
{
|
||||
line = line.slice(this._settings.MetapropertyDirective.length).trim(); // (#$ Title: foo) -> (Title: foo)
|
||||
const lowCaseLine = line.toLowerCase();
|
||||
let isProperty = false;
|
||||
let isPropertyMissingSemicolon = false;
|
||||
let isOptionDirective = false;
|
||||
|
||||
for (const [property, value] of Object.entries(this._presetsFileMetadata)) {
|
||||
const lineBeginning = `${property.toLowerCase()}:`; // "TITLE:"
|
||||
const wrongLineBeginning = `${property.toLowerCase()}`; // "TITLE"
|
||||
|
||||
if (lowCaseLine.startsWith(lineBeginning)) {
|
||||
line = line.slice(lineBeginning.length).trim(); // (Title: foo) -> (foo)
|
||||
this._processProperty(property, line);
|
||||
isProperty = true;
|
||||
} else if (lowCaseLine.startsWith(wrongLineBeginning)) {
|
||||
isPropertyMissingSemicolon = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isProperty && lowCaseLine.startsWith(this._settings.OptionsDirectives.OPTION_DIRECTIVE)) {
|
||||
this._processOptionDirective(line);
|
||||
isOptionDirective = true;
|
||||
}
|
||||
|
||||
if (!isProperty && !isOptionDirective) {
|
||||
if (isPropertyMissingSemicolon) {
|
||||
this._addError(`line ${this._currentLine}, property missing ":"`);
|
||||
} else {
|
||||
this._addError(`line ${this._currentLine}, unknown preset directive: '${line}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_processOptionDirective(line)
|
||||
{
|
||||
const lowCaseLine = line.toLowerCase();
|
||||
|
||||
if (lowCaseLine.startsWith(this._settings.OptionsDirectives.BEGIN_OPTION_DIRECTIVE)) {
|
||||
this._processOptionBeginDirective(line, lowCaseLine);
|
||||
} else if (lowCaseLine.startsWith(this._settings.OptionsDirectives.END_OPTION_DIRECTIVE)) {
|
||||
this._processOptionEndDirective(line, lowCaseLine);
|
||||
} else if (lowCaseLine.startsWith(this._settings.OptionsDirectives.BEGIN_OPTION_GROUP_DIRECTIVE)) {
|
||||
this._processOptionGroupBeginDirective(line, lowCaseLine);
|
||||
} else if (lowCaseLine.startsWith(this._settings.OptionsDirectives.END_OPTION_GROUP_DIRECTIVE)) {
|
||||
this._processOptionGroupEndDirective(line, lowCaseLine);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_processOptionGroupBeginDirective(line, lowCaseLine)
|
||||
{
|
||||
const optionGroup = this._getOptionGroup(line);
|
||||
|
||||
if ("" === optionGroup.name) {
|
||||
this._addError(`line ${this._currentLine}, empty optionGroup name`);
|
||||
} else if (undefined !== this._currentOptionGroup) {
|
||||
this._addError(`line ${this._currentLine}, nested #$ option groups are not allowed`);
|
||||
} else {
|
||||
this._currentOptionGroup = optionGroup;
|
||||
}
|
||||
}
|
||||
|
||||
_processOptionGroupEndDirective(line, lowCaseLine)
|
||||
{
|
||||
if (undefined === this._currentOptionGroup) {
|
||||
this._addError(`line ${this._currentLine}, end Option Group directive found but no Option Group to close`);
|
||||
} else {
|
||||
const lowCaseOptionGroupName = this._currentOptionGroup.name.toLowerCase();
|
||||
|
||||
const indexOfOption = this.optionGroups.findIndex(item => lowCaseOptionGroupName === item.name.toLowerCase());
|
||||
|
||||
if (-1 === indexOfOption) {
|
||||
this.optionGroups.push(this._currentOptionGroup);
|
||||
}
|
||||
|
||||
this._currentOptionGroup = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_processOptionBeginDirective(line, lowCaseLine)
|
||||
{
|
||||
const Option = this._getOption(line);
|
||||
const lowCaseOptionName = Option.name.toLowerCase();
|
||||
|
||||
if ("" === Option.name) {
|
||||
this._addError(`line ${this._currentLine}, empty Option name`);
|
||||
} else if (undefined !== this._currentOption) {
|
||||
this._addError(`line ${this._currentLine}, nested #$ options are not allowed`);
|
||||
} else {
|
||||
this._currentOption = Option;
|
||||
}
|
||||
}
|
||||
|
||||
_processOptionEndDirective(line, lowCaseLine)
|
||||
{
|
||||
if (undefined === this._currentOption) {
|
||||
this._addError(`line ${this._currentLine}, end Option directive found but no Option to close`);
|
||||
} else {
|
||||
const lowCaseOptionName = this._currentOption.name.toLowerCase();
|
||||
|
||||
const indexOfOption = this.options.findIndex(item => lowCaseOptionName === item.name.toLowerCase());
|
||||
|
||||
if (-1 === indexOfOption) {
|
||||
this.options.push(this._currentOption);
|
||||
}
|
||||
|
||||
this._currentOption = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_escapeRegex(string)
|
||||
{
|
||||
return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
}
|
||||
|
||||
_getOptionGroup(line)
|
||||
{
|
||||
const directiveRemoved = line.slice(this._settings.OptionsDirectives.BEGIN_OPTION_GROUP_DIRECTIVE.length).trim();
|
||||
const isExclusiveGroup = this._isExclusiveGroup(directiveRemoved.toLowerCase());
|
||||
|
||||
const exclusiveOptionGroupRegex = new RegExp(this._escapeRegex(this._settings.OptionsDirectives.EXCLUSIVE_OPTION_GROUP), 'gi');
|
||||
|
||||
const optionGroupName = directiveRemoved.replace(exclusiveOptionGroupRegex, "");
|
||||
|
||||
if (0 == optionGroupName.length || optionGroupName[0] != ":") {
|
||||
this._addError(`line ${this._currentLine}, OPTION_GROUP BEGIN directive should be followed by ":". Example: #$ OPTION_GROUP BEGIN: My Group Name or if its exclusive: #$ OPTION_GROUP BEGIN: (EXCLUSIVE) My Exclusive Group`);
|
||||
}
|
||||
|
||||
let optionGroup = {
|
||||
name: optionGroupName.slice(1).trim(),
|
||||
exclusive: isExclusiveGroup,
|
||||
}
|
||||
|
||||
return optionGroup;
|
||||
}
|
||||
|
||||
_isExclusiveGroup(lowercaseLine)
|
||||
{
|
||||
return lowercaseLine.includes(this._settings.OptionsDirectives.EXCLUSIVE_OPTION_GROUP)
|
||||
}
|
||||
|
||||
_getOption(line)
|
||||
{
|
||||
const directiveRemoved = line.slice(this._settings.OptionsDirectives.BEGIN_OPTION_DIRECTIVE.length).trim();
|
||||
const directiveRemovedLowCase = directiveRemoved.toLowerCase();
|
||||
const optionChecked = this._isOptionChecked(directiveRemovedLowCase);
|
||||
|
||||
const regExpRemoveChecked = new RegExp(this._escapeRegex(this._settings.OptionsDirectives.OPTION_CHECKED), 'gi');
|
||||
const regExpRemoveUnchecked = new RegExp(this._escapeRegex(this._settings.OptionsDirectives.OPTION_UNCHECKED), 'gi');
|
||||
let optionName = directiveRemoved.replace(regExpRemoveChecked, "");
|
||||
optionName = optionName.replace(regExpRemoveUnchecked, "").trim();
|
||||
if (0 == optionName.length || optionName[0] != ":") {
|
||||
this._addError(`line ${this._currentLine}, OPTION BEGIN directive should be followed by ":". Example: #$ OPTION BEGIN (UNCHECKED): My Option Name`);
|
||||
}
|
||||
|
||||
let option = {
|
||||
name: optionName.slice(1).trim(),
|
||||
checked: optionChecked
|
||||
}
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
_isOptionChecked(lowCaseLine)
|
||||
{
|
||||
let OptionChecked = false;
|
||||
let OptionUnchecked = false;
|
||||
|
||||
if (lowCaseLine.includes(this._settings.OptionsDirectives.OPTION_CHECKED)) {
|
||||
OptionChecked = true;
|
||||
}
|
||||
if (lowCaseLine.includes(this._settings.OptionsDirectives.OPTION_UNCHECKED)) {
|
||||
OptionUnchecked = true;
|
||||
}
|
||||
|
||||
if (OptionChecked && OptionUnchecked) {
|
||||
this._addError(`line ${this._currentLine}, Option can't be checked and unchecked at the same time`);
|
||||
} else if (!OptionChecked && !OptionUnchecked) {
|
||||
this._addError(`line ${this._currentLine}, Every option must specify whether it is ${this._settings.OptionsDirectives.OPTION_CHECKED.toUpperCase()} or ${this._settings.OptionsDirectives.OPTION_UNCHECKED.toUpperCase()}`);
|
||||
} else {
|
||||
OptionChecked = OptionChecked;
|
||||
}
|
||||
|
||||
return OptionChecked;
|
||||
}
|
||||
|
||||
_processProperty(property, line)
|
||||
{
|
||||
switch(this._presetsFileMetadata[property].type) {
|
||||
case this._settings.MetadataTypes.STRING_ARRAY:
|
||||
this._processArrayProperty(property, line);
|
||||
break;
|
||||
case this._settings.MetadataTypes.STRING:
|
||||
this._processStringProperty(property, line);
|
||||
break;
|
||||
case this._settings.MetadataTypes.PRESET_CATEGORY:
|
||||
this._processPresetCategoryProperty(property, line);
|
||||
break;
|
||||
case this._settings.MetadataTypes.FILE_PATH:
|
||||
this._processFilePathProperty(property, line);
|
||||
break;
|
||||
case this._settings.MetadataTypes.FILE_PATH_ARRAY:
|
||||
this._processFilePathArrayProperty(property, line);
|
||||
break;
|
||||
case this._settings.MetadataTypes.BOOLEAN:
|
||||
this._processBooleanProperty(property, line);
|
||||
break;
|
||||
case this._settings.MetadataTypes.WORDS_ARRAY:
|
||||
this._processWordsArrayProperty(property, line);
|
||||
break;
|
||||
case this._settings.MetadataTypes.PRESET_STATUS:
|
||||
this._processPresetStatusProperty(property, line);
|
||||
break;
|
||||
case this._settings.MetadataTypes.PRIORITY:
|
||||
this._processPriorityProperty(property, line);
|
||||
break;
|
||||
case this._settings.MetadataTypes.PARSER:
|
||||
this._processParserProperty(property, line);
|
||||
break;
|
||||
default:
|
||||
this._addError(`line ${this._currentLine}, unknown property type '${this._presetsFileMetadata[property].type}' for the property '${property}'`);
|
||||
}
|
||||
}
|
||||
|
||||
_processPresetStatusProperty(property, line)
|
||||
{
|
||||
this._checkPropertyDublicated(property);
|
||||
|
||||
if (this._settings.PresetStatusEnum.includes(line)) {
|
||||
this[property] = line;
|
||||
} else {
|
||||
this._addError(`line ${this._currentLine}, unknown ${property} value: '${line}'; available values: ${this._settings.PresetStatusEnum}`);
|
||||
}
|
||||
}
|
||||
|
||||
_processParserProperty(property, line)
|
||||
{
|
||||
this._checkPropertyDublicated(property);
|
||||
|
||||
if (this._settings.ParserEnum.includes(line)) {
|
||||
this[property] = line;
|
||||
} else {
|
||||
this._addError(`line ${this._currentLine}, unknown ${property} value: '${line}'; available values: ${this._settings.ParserEnum}`);
|
||||
}
|
||||
}
|
||||
|
||||
_processWordsArrayProperty(property, line)
|
||||
{
|
||||
this._checkPropertyDublicated(property);
|
||||
|
||||
let words = line.split(",");
|
||||
words = words.map(word => word.trim());
|
||||
words = words.filter(word => word);
|
||||
this[property] = words;
|
||||
}
|
||||
|
||||
|
||||
_processBooleanProperty(property, line)
|
||||
{
|
||||
this._checkPropertyDublicated(property);
|
||||
|
||||
const trueValues = ["true", "yes"];
|
||||
const falseValues = ["false", "no"];
|
||||
|
||||
const lineLowCase = line.toLowerCase();
|
||||
|
||||
let result = false;
|
||||
|
||||
if (trueValues.includes(lineLowCase)) {
|
||||
result = true;
|
||||
} else if (falseValues.includes(lineLowCase)) {
|
||||
result = false;
|
||||
} else {
|
||||
this._addError(`line ${this._currentLine}, boolean property '${property}'' has a wrong value: '${line}'`);
|
||||
}
|
||||
|
||||
this[property] = result;
|
||||
}
|
||||
|
||||
_checkPropertyDublicated(property)
|
||||
{
|
||||
if (undefined !== this[property]) {
|
||||
this._addError(`line ${this._currentLine}, duplicated property '${property}'`);
|
||||
}
|
||||
}
|
||||
|
||||
_processFilePathProperty(property, line)
|
||||
{
|
||||
this._checkPropertyDublicated(property);
|
||||
const stat = fs.statSync(line);
|
||||
if (!stat || stat.isDirectory()) {
|
||||
this._addError(`line ${this._currentLine}, can't find file '${line}'`);
|
||||
} else {
|
||||
this[property] = line;
|
||||
}
|
||||
}
|
||||
|
||||
_processFilePathArrayProperty(property, line)
|
||||
{
|
||||
if (!this[property]) {
|
||||
this[property] = [];
|
||||
}
|
||||
|
||||
if (fs.existsSync(line)) {
|
||||
// still could be a folder, so have to filter out folders
|
||||
const stat = fs.statSync(line);
|
||||
if (!stat || stat.isDirectory()) {
|
||||
this._addError(`line ${this._currentLine}, a folder is specified instead of a file: '${line}'`);
|
||||
} else {
|
||||
this[property].push(line);
|
||||
}
|
||||
} else {
|
||||
this._addError(`line ${this._currentLine}, can't find file '${line}'`);
|
||||
}
|
||||
}
|
||||
|
||||
_processPresetCategoryProperty(property, line)
|
||||
{
|
||||
this._checkPropertyDublicated(property);
|
||||
line = line.toLowerCase();
|
||||
let presetTypeValid = false;
|
||||
|
||||
for (const [key, value] of Object.entries(this._settings.PresetCategories)) {
|
||||
if (key.toLowerCase() === line) {
|
||||
presetTypeValid = true;
|
||||
this[property] = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (!presetTypeValid) {
|
||||
this._addError(`line ${this._currentLine}, unknown preset category: '${line}'`);
|
||||
}
|
||||
}
|
||||
|
||||
_processArrayProperty(property, line)
|
||||
{
|
||||
if (!this[property]) {
|
||||
this[property] = [];
|
||||
}
|
||||
|
||||
this[property].push(line);
|
||||
}
|
||||
|
||||
_processStringProperty(property, line)
|
||||
{
|
||||
this._checkPropertyDublicated(property);
|
||||
this[property] = line;
|
||||
}
|
||||
|
||||
_processPriorityProperty(property, line)
|
||||
{
|
||||
this._checkPropertyDublicated(property);
|
||||
const value = parseInt(line);
|
||||
const value2 = Number(line);
|
||||
|
||||
if (NaN === value || value !== value2) {
|
||||
this._addError(`line ${this._currentLine}, PRIORITY value must be integer. Instead it is: '${line}'`);
|
||||
}
|
||||
|
||||
this[property] = value;
|
||||
}
|
||||
|
||||
_addError(error)
|
||||
{
|
||||
const fullError = `${this.fullPath}: ${error}`;
|
||||
this._errors.push(fullError);
|
||||
console.error(fullError);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PresetsFile;
|
||||
79
indexer/PresetsFolder.js
Normal file
79
indexer/PresetsFolder.js
Normal file
@@ -0,0 +1,79 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const PresetsFile = require('./PresetsFile');
|
||||
|
||||
class PresetsFolder
|
||||
{
|
||||
constructor(fullPath, settings, presetFilesArray, errors)
|
||||
{
|
||||
this.subFolders = [];
|
||||
this.files = [];
|
||||
this.fullPath = fullPath;
|
||||
this.name = "";
|
||||
|
||||
let list = fs.readdirSync(fullPath);
|
||||
|
||||
list.forEach((fileName) => {
|
||||
const fullFileName = fullPath + '/' + fileName;
|
||||
const stat = fs.statSync(fullFileName);
|
||||
if (!fileName.startsWith(".")) {
|
||||
if (stat && stat.isDirectory()) {
|
||||
let subdir = new PresetsFolder(fullFileName, settings, presetFilesArray, errors);
|
||||
subdir.name = fileName;
|
||||
this.subFolders.push(subdir);
|
||||
} else if (fileName.toLowerCase().endsWith(".txt")) {
|
||||
let presetsFile = new PresetsFile(fullFileName, settings, errors);
|
||||
presetFilesArray.push(presetsFile);
|
||||
this.files.push(presetsFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static checkForIncludeLoops(presetFilesArray, errors)
|
||||
{
|
||||
const filesDb = PresetsFolder._createFilesDictionary(presetFilesArray);
|
||||
const fileNameErrors = {};
|
||||
|
||||
presetFilesArray.forEach((file) => {
|
||||
const parents = [];
|
||||
PresetsFolder._checkFileForIncludeLoops(file, filesDb, parents, fileNameErrors);
|
||||
});
|
||||
|
||||
for (const fileNameError in fileNameErrors) {
|
||||
const errorText = `File ${fileNameError}, takes part in the #$ INCLUDE loop'`;
|
||||
errors.push(errorText);
|
||||
console.error(errorText);
|
||||
}
|
||||
}
|
||||
|
||||
static _checkFileForIncludeLoops(file, filesDb, parents, fileNameErrors)
|
||||
{
|
||||
if (parents.includes(file.fullPath)) {
|
||||
fileNameErrors[file.fullPath] = true;
|
||||
} else {
|
||||
parents.push(file.fullPath);
|
||||
if (file.hasOwnProperty("include")) {
|
||||
file.include.forEach((includedFileName) => {
|
||||
const clonedParents = [...parents];
|
||||
PresetsFolder._checkFileForIncludeLoops(filesDb[includedFileName], filesDb, clonedParents, fileNameErrors);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static _createFilesDictionary(presetFilesArray)
|
||||
{
|
||||
let result = {};
|
||||
|
||||
presetFilesArray.forEach((file) => {
|
||||
result[file.fullPath] = file;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PresetsFolder;
|
||||
86
indexer/Settings.js
Normal file
86
indexer/Settings.js
Normal file
@@ -0,0 +1,86 @@
|
||||
'use strict';
|
||||
|
||||
const MetadataTypes = {
|
||||
STRING: "STRING",
|
||||
STRING_ARRAY: "STRING_ARRAY", // strings from multiple comment lines
|
||||
PRESET_CATEGORY: "PRESET_CATEGORY", // TUNE/RATES/OSD etc
|
||||
FILE_PATH: "FILE_PATH", // path/to/file.ext and check if file exists
|
||||
BOOLEAN: "BOOLEAN", // true/false
|
||||
WORDS_ARRAY: "WORDS_ARRAY", // "word1, word2, word3"
|
||||
FILE_PATH_ARRAY: "FILE_PATH_ARRAY", // array of path/to/file.ext and check if files exist
|
||||
PRESET_STATUS: "PRESET_STATUS", // official/community/experimental
|
||||
PRIORITY: "PRIORITY", // 0..99
|
||||
PARSER: "PARSER", // TEXT, MARKED
|
||||
}
|
||||
|
||||
const PresetStatusEnum = ["OFFICIAL", "COMMUNITY", "EXPERIMENTAL"];
|
||||
const ParserEnum = ["TEXT", "MARKED"];
|
||||
|
||||
const PresetCategories = {
|
||||
PROFILE: "PROFILE",
|
||||
RATEPROFILE: "RATEPROFILE",
|
||||
FILTERS: "FILTERS",
|
||||
REMAPPING: "REMAPPING",
|
||||
BNF: "BNF",
|
||||
OTHER: "OTHER",
|
||||
}
|
||||
|
||||
const PresetCategoriesPriorities = {
|
||||
PROFILE: 10**12,
|
||||
RATEPROFILE: 10**4,
|
||||
REMAPPING: 0,
|
||||
FILTERS: 10**2,
|
||||
BNF: 0,
|
||||
OTHER: 0,
|
||||
}
|
||||
|
||||
const OptionsDirectives = {
|
||||
OPTION_DIRECTIVE: "option",
|
||||
BEGIN_OPTION_DIRECTIVE: "option begin",
|
||||
END_OPTION_DIRECTIVE: "option end",
|
||||
OPTION_CHECKED: "(checked)",
|
||||
OPTION_UNCHECKED: "(unchecked)",
|
||||
BEGIN_OPTION_GROUP_DIRECTIVE: "option_group begin",
|
||||
END_OPTION_GROUP_DIRECTIVE: "option_group end",
|
||||
EXCLUSIVE_OPTION_GROUP: "(exclusive)",
|
||||
}
|
||||
|
||||
const settings = {
|
||||
MetapropertyDirective: "#$",
|
||||
|
||||
PresetCategories: Object.freeze(PresetCategories),
|
||||
PresetCategoriesPriorities: Object.freeze(PresetCategoriesPriorities),
|
||||
|
||||
MetadataTypes: Object.freeze(MetadataTypes),
|
||||
|
||||
OptionsDirectives : Object.freeze(OptionsDirectives),
|
||||
|
||||
PresetStatusEnum : Object.freeze(PresetStatusEnum),
|
||||
ParserEnum : Object.freeze(ParserEnum),
|
||||
|
||||
presetsDir: "presets",
|
||||
presetsFileEncoding: "utf-8",
|
||||
|
||||
presetsFileMetadata: Object.freeze({
|
||||
title: {type: MetadataTypes.STRING, optional: false },
|
||||
firmware_version: {type: MetadataTypes.STRING_ARRAY, optional: false },
|
||||
category: {type: MetadataTypes.PRESET_CATEGORY, optional: false },
|
||||
status: {type: MetadataTypes.PRESET_STATUS, optional: false },
|
||||
board_name: {type: MetadataTypes.STRING_ARRAY, optional: true },
|
||||
author: {type: MetadataTypes.STRING, optional: true },
|
||||
description: {type: MetadataTypes.STRING_ARRAY, optional: true },
|
||||
include: {type: MetadataTypes.FILE_PATH_ARRAY, optional: true },
|
||||
keywords: {type: MetadataTypes.WORDS_ARRAY, optional: true },
|
||||
hidden: {type: MetadataTypes.BOOLEAN, optional: true },
|
||||
discussion: {type: MetadataTypes.STRING, optional: true },
|
||||
warning: {type: MetadataTypes.STRING, optional: true },
|
||||
disclaimer: {type: MetadataTypes.STRING, optional: true },
|
||||
include_warning: {type: MetadataTypes.FILE_PATH_ARRAY, optional: true },
|
||||
include_disclaimer: {type: MetadataTypes.FILE_PATH_ARRAY, optional: true },
|
||||
priority: {type: MetadataTypes.PRIORITY, optional: true },
|
||||
force_options_review: {type: MetadataTypes.BOOLEAN, optional: true },
|
||||
parser: {type: MetadataTypes.PARSER, optional: true },
|
||||
}),
|
||||
}
|
||||
|
||||
module.exports = Object.freeze(settings);
|
||||
6
indexer/check.js
Normal file
6
indexer/check.js
Normal file
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const child = require('child_process').fork('indexer/indexer.js', ["nosave"]);
|
||||
child.on('exit', (code) => {
|
||||
process.exitCode = code;
|
||||
});
|
||||
45
indexer/indexer.js
Normal file
45
indexer/indexer.js
Normal file
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const PresetsFolder = require('./PresetsFolder');
|
||||
const settings = require('./Settings');
|
||||
const IndexContent = require('./IndexContent');
|
||||
const crypto = require('crypto');
|
||||
const errors = [];
|
||||
process.exitCode = 100;
|
||||
|
||||
let writeIndexFile = true;
|
||||
|
||||
if (process.argv.length === 3) {
|
||||
if (process.argv[2] === "nosave") {
|
||||
writeIndexFile = false;
|
||||
}
|
||||
}
|
||||
|
||||
let presetFilesArray = [];
|
||||
let presetsFolder = new PresetsFolder(settings.presetsDir, settings, presetFilesArray, errors);
|
||||
PresetsFolder.checkForIncludeLoops(presetFilesArray, errors);
|
||||
|
||||
//console.log(getUniqueValues(presetFilesArray, "firmwareVersion"));
|
||||
|
||||
if (0 === errors.length) {
|
||||
console.log("OK");
|
||||
|
||||
if (writeIndexFile) {
|
||||
const indexContent = new IndexContent(presetFilesArray, settings);
|
||||
const jsonIndexContent = JSON.stringify(indexContent, null, 2);
|
||||
fs.writeFileSync("index.json", jsonIndexContent);
|
||||
console.log("index.json created");
|
||||
|
||||
const sum = crypto.createHash('sha256');
|
||||
sum.update(jsonIndexContent);
|
||||
const indexHash = sum.digest('hex');
|
||||
fs.writeFileSync("index_hash.txt", indexHash);
|
||||
console.log("index_hash.txt created");
|
||||
}
|
||||
|
||||
process.exitCode = 0;
|
||||
} else {
|
||||
console.log("Failed with errors");
|
||||
}
|
||||
Reference in New Issue
Block a user