Browse Source

WIP

feature/node-rewrite
Sven Slootweg 2 months ago
parent
commit
f85ab90be1
15 changed files with 350 additions and 319 deletions
  1. +1
    -0
      notes.txt
  2. +3
    -0
      package.json
  3. +3
    -0
      src/app.js
  4. +5
    -4
      src/packages/exec-binary/index.js
  5. +13
    -12
      src/packages/exec-lvm/index.js
  6. +15
    -8
      src/packages/exec-smartctl/index.js
  7. +0
    -249
      src/packages/exec-smartctl/parser.pegjs
  8. +88
    -0
      src/packages/exec-smartctl/parsers/commands/attributes.pegjs
  9. +106
    -0
      src/packages/exec-smartctl/parsers/commands/info.pegjs
  10. +11
    -0
      src/packages/exec-smartctl/parsers/commands/scan.pegjs
  11. +44
    -0
      src/packages/exec-smartctl/parsers/primitives.pegjs
  12. +6
    -0
      src/packages/exec-smartctl/parsers/shared.pegjs
  13. +46
    -44
      src/packages/text-parser-pegjs/index.js
  14. +2
    -2
      src/test-wrapper.js
  15. +7
    -0
      yarn.lock

+ 1
- 0
notes.txt View File

@@ -1,5 +1,6 @@
MARKER:
- Move to pegjs-import
- Replace all 'this is a bug' errors with @joepie91/unreachable
- LVM / mdraid support and tabs (+ complete refactoring LVM implementation)
- Switch hashing to argon2id
- Switch child_process to execa


+ 3
- 0
package.json View File

@@ -14,16 +14,19 @@
"license": "WTFPL",
"dependencies": {
"@babel/register": "^7.8.3",
"@invisible/pegjs-import": "^1.1.1",
"@joepie91/express-react-views": "^1.0.1",
"@joepie91/unreachable": "^1.0.0",
"@validatem/allow-extra-properties": "^0.1.0",
"@validatem/anything": "^0.1.0",
"@validatem/array-of": "^0.1.2",
"@validatem/core": "^0.3.15",
"@validatem/default-to": "^0.1.0",
"@validatem/dynamic": "^0.1.2",
"@validatem/either": "^0.1.9",
"@validatem/error": "^1.1.0",
"@validatem/is-array": "^0.1.1",
"@validatem/is-boolean": "^0.1.1",
"@validatem/is-function": "^0.1.0",
"@validatem/is-number": "^0.1.3",
"@validatem/is-plain-object": "^0.1.1",


+ 3
- 0
src/app.js View File

@@ -101,6 +101,9 @@ module.exports = function () {

// }

console.log(errorChain.getContext(sourceError));

res.render("error", {
error: err
});


+ 5
- 4
src/packages/exec-binary/index.js View File

@@ -7,7 +7,7 @@ const util = require("util");
const execFileAsync = util.promisify(require("child_process").execFile);
const debug = require("debug")("cvm:execBinary");
const asExpression = require("as-expression");
const { rethrowAs } = require("error-chain");
const { rethrowAs, chain } = require("error-chain");
const textParser = require("../text-parser");

const errors = require("./errors");
@@ -194,7 +194,7 @@ module.exports = function createBinaryInvocation(command, args = []) {

if (!this._settings.expectedExitCodes.includes(exitCode)) {
// FIXME: Can we actually pass `error` to be chained onto here, when there's a case where `error` is undefined? Namely, when requiring a non-zero exit code, but the process exits with 0.
throw new errors.NonZeroExitCode.chain(error, `Expected exit code to be one of ${JSON.stringify(this._settings.expectedExitCodes)}, but got '${exitCode}'`, {
throw chain(error, errors.NonZeroExitCode, `Expected exit code to be one of ${JSON.stringify(this._settings.expectedExitCodes)}, but got '${exitCode}'`, {
exitCode: exitCode,
stdout: stdout,
stderr: stderr
@@ -212,6 +212,7 @@ module.exports = function createBinaryInvocation(command, args = []) {
return undefined;
}
} else {
// FIXME: use @joepie91/unreachable
throw new Error(`Encountered expectation for unexpected channel '${expectation.channel}'; this is a bug, please report it`, {
failedChannel: expectation.channel
});
@@ -230,7 +231,7 @@ module.exports = function createBinaryInvocation(command, args = []) {
return undefined;
}
} else {
throw errors.OutputParsingFailed.chain(error, `An error occurred while parsing '${expectation.channel}'`, {
throw chain(error, errors.OutputParsingFailed, `An error occurred while parsing '${expectation.channel}'`, {
failedChannel: expectation.channel
});
}
@@ -270,7 +271,7 @@ module.exports = function createBinaryInvocation(command, args = []) {
? `Failed while processing ${error.failedChannel} of command`
: "Failed while processing result of command execution";

throw errors.CommandExecutionFailed.chain(error, message, {
throw chain(error, errors.CommandExecutionFailed, message, {
exitCode: exitCode,
stdout: stdout,
stderr: stderr


+ 13
- 12
src/packages/exec-lvm/index.js View File

@@ -1,6 +1,7 @@
"use strict";

const Promise = require("bluebird");
const { chain } = require("error-chain");
const execBinary = require("../exec-binary");
const parseIECBytes = require("../parse-bytes-iec");

@@ -120,11 +121,11 @@ module.exports = {
}).then((_output) => {
return true;
}).catch(hasFlag("deviceNotFound"), (error) => {
throw errors.InvalidPath.chain(error, `Specified device '${devicePath}' does not exist`, {
throw chain(error, errors.InvalidPath, `Specified device '${devicePath}' does not exist`, {
path: devicePath
});
}).catch(hasFlag("partitionTableExists"), (error) => {
throw errors.PartitionExists.chain(error, `Refused to create a Physical Volume, as a partition or partition table already exists on device '${devicePath}'`, {
throw chain(error, errors.PartitionExists, `Refused to create a Physical Volume, as a partition or partition table already exists on device '${devicePath}'`, {
path: devicePath
});
});
@@ -141,11 +142,11 @@ module.exports = {
}).then((_output) => {
return true;
}).catch(hasFlag("deviceNotFound"), (error) => {
throw errors.InvalidPath.chain(error, `Specified device '${devicePath}' does not exist`, {
throw chain(error, errors.InvalidPath, `Specified device '${devicePath}' does not exist`, {
path: devicePath
});
}).catch(hasFlag("notAPhysicalVolume"), (error) => {
throw errors.InvalidPath.chain(error, `Specified device '${devicePath}' is not a Physical Volume`, {
throw chain(error, errors.InvalidPath, `Specified device '${devicePath}' is not a Physical Volume`, {
path: devicePath
});
});
@@ -178,17 +179,17 @@ module.exports = {
}).catch(hasFlag("deviceNotFound"), (error) => {
let failedDevices = error.getAllContext().result.deviceNotFound;

throw errors.InvalidPath.chain(error, `The following specified devices do not exist: ${failedDevices.join(", ")}`, {
throw chain(error, errors.InvalidPath, `The following specified devices do not exist: ${failedDevices.join(", ")}`, {
paths: failedDevices
});
}).catch(hasFlag("partitionTableExists"), (error) => {
let failedDevices = error.getAllContext().result.partitionTableExists;

throw errors.PartitionExists.chain(error, `Refused to create a Volume Group, as partitions or partition tables already exist on the following devices: ${failedDevices.join(", ")}`, {
throw chain(error, errors.PartitionExists, `Refused to create a Volume Group, as partitions or partition tables already exist on the following devices: ${failedDevices.join(", ")}`, {
paths: failedDevices
});
}).catch(hasFlag("volumeGroupExists"), (error) => {
throw errors.VolumeGroupExists.chain(error, `A volume group with the name '${name}' already exists`, {
throw chain(error, errors.VolumeGroupExists, `A volume group with the name '${name}' already exists`, {
volumeGroupName: name
});
}).catch(hasFlag("physicalVolumeInUse"), (error) => {
@@ -198,7 +199,7 @@ module.exports = {
return `${device} (${volumeGroup})`;
}).join(", ");

throw errors.PhysicalVolumeInUse.chain(error, `The following specified Physical Volumes are already in use in another Volume Group: ${failedItemString}`, {
throw chain(error, errors.PhysicalVolumeInUse, `The following specified Physical Volumes are already in use in another Volume Group: ${failedItemString}`, {
volumes: failedItems
});
});
@@ -218,21 +219,21 @@ module.exports = {
}).then((_output) => {
return true;
}).catch(hasFlag("deviceNotFound"), (error) => {
throw errors.InvalidPath.chain(error, `Specified device '${physicalVolume}' does not exist`, {
throw chain(error, errors.InvalidPath, `Specified device '${physicalVolume}' does not exist`, {
path: physicalVolume
});
}).catch(hasFlag("volumeGroupNotFound"), (error) => {
throw errors.InvalidVolumeGroup.chain(error, `Specified Volume Group '${volumeGroup}' does not exist`, {
throw chain(error, errors.InvalidVolumeGroup, `Specified Volume Group '${volumeGroup}' does not exist`, {
volumeGroupName: volumeGroup
});
}).catch(hasFlag("physicalVolumeInUse"), (error) => {
let volume = error.getAllContext().result.physicalVolumeInUse;

throw errors.PhysicalVolumeInUse.chain(error, `Specified Physical Volume '${physicalVolume}' is already in use in another Volume Group (${volume.volumeGroup})`, {
throw chain(error, errors.PhysicalVolumeInUse, `Specified Physical Volume '${physicalVolume}' is already in use in another Volume Group (${volume.volumeGroup})`, {
volume: volume
});
}).catch(hasFlag("partitionTableExists"), (error) => {
throw errors.PartitionExists.chain(error, `Refused to add device to Volume Group, as a partition or partition table already exists on device '${physicalVolume}'`, {
throw chain(error, errors.PartitionExists, `Refused to add device to Volume Group, as a partition or partition table already exists on device '${physicalVolume}'`, {
path: physicalVolume
});
});


+ 15
- 8
src/packages/exec-smartctl/index.js View File

@@ -8,22 +8,25 @@ const itemsToObject = require("../items-to-object");

/* FIXME: Error handling, eg. device not found errors */

function outputParser(rootRule) {
function outputParser(parserPath) {
return createPegParser({
grammarFile: path.join(__dirname, "./parser.pegjs"),
options: {
allowedStartRules: [ rootRule ]
}
grammarFile: path.join(__dirname, parserPath)
});
}

let attributesParser = outputParser("./parsers/commands/attributes.pegjs");
let infoParser = outputParser("./parsers/commands/info.pegjs");
let scanParser = outputParser("./parsers/commands/scan.pegjs");

module.exports = {
attributes: function ({ devicePath }) {
return Promise.try(() => {
return attributesParser;
}).then((parser) => {
return execBinary("smartctl", [devicePath])
.asRoot()
.withFlags({ attributes: true })
.requireOnStdout(outputParser("RootAttributes"))
.requireOnStdout(parser)
.execute();
}).then((output) => {
// NOTE: Ignore the header, for now
@@ -32,10 +35,12 @@ module.exports = {
},
info: function ({ devicePath }) {
return Promise.try(() => {
return infoParser;
}).then((parser) => {
return execBinary("smartctl", [devicePath])
.asRoot()
.withFlags({ info: true })
.requireOnStdout(outputParser("RootInfo"))
.requireOnStdout(parser)
.execute();
}).then((output) => {
// NOTE: Ignore the header, for now
@@ -44,10 +49,12 @@ module.exports = {
},
scan: function () {
return Promise.try(() => {
return scanParser;
}).then((parser) => {
return execBinary("smartctl")
.asRoot()
.withFlags({ scan: true })
.requireOnStdout(outputParser("RootScan"))
.requireOnStdout(parser)
.execute();
}).then((output) => {
// NOTE: Ignore the header, for now


+ 0
- 249
src/packages/exec-smartctl/parser.pegjs View File

@@ -1,249 +0,0 @@
{
const matchValue = require("match-value");
const syncpipe = require("syncpipe");

const {B} = require("../unit-bytes-iec");
const mapAttributeFlags = require("./map-attribute-flags");
}

RootInfo
= header:Header infoSection:InfoSection Newline* {
return { ...header, fields: infoSection }
};

RootScan
= devices:ScanDevice* {
return { devices: devices };
}

RootAttributes
= header:Header attributesSection:AttributesSection Newline* {
return { ...header, attributes: attributesSection }
};

_
= (" " / "\t")*

RestOfLine
= content:$[^\n]+ Newline {
return content;
}

Newline
= "\n"
/ "\r\n"

Header 'header'
= "smartctl " versionString:RestOfLine "Copyright" copyrightStatement:RestOfLine Newline {
return { versionString, copyrightStatement };
}

BytesValue
= value:SeparatedNumberValue {
return B(value);
}
NumberValue
= value:$[0-9]+ {
return parseInt(value);
}

SeparatedNumberValue
= value:$[0-9,]+ {
return syncpipe(value, [
(_) => _.replace(/,/g, ""),
(_) => parseInt(_)
]);
}

HexNumberValue
= value:$[0-9A-Fa-f]+ {
return parseInt(value, 16);
}

IdentifierValue
= value:$[a-zA-Z_-]+ {
return value;
}

// smartctl --scan

ScanDevice 'scanned device'
= path:$[^ ]+ _ "-d" _ interface_:$[^ ]+ _ RestOfLine {
return { path: path, interface: interface_ };
}

// smartctl --info

InfoSection 'information section'
= "=== START OF INFORMATION SECTION ===" Newline fields:(InfoField+) {
return fields.filter((field) => field != null);
}

InfoField 'information field'
= InfoFieldSimple
/ InfoFieldIgnored
/ InfoFieldSize
/ InfoFieldRPM
/ InfoFieldSectorSizes
/ InfoFieldBoolean
/ InfoFieldUnknown

InfoFieldSimpleKey
= "Device Model" { return "model"; }
/ "Model Number" { return "model"; }
/ "Model Family" { return "modelFamily"; }
/ "Serial Number" { return "serialNumber"; }
/ "LU WWN Device Id" { return "wwn"; }
/ "Firmware Version" { return "firmwareVersion"; }
/ "Form Factor" { return "formFactor"; }
/ "ATA Version is" { return "ataVersion"; }
/ "SATA Version is" { return "sataVersion"; }

InfoFieldSimple
= key:InfoFieldSimpleKey ":" _ value:RestOfLine {
return { key: key, value: value };
}

InfoFieldUnknown
= key:$[^:]+ ":" _ RestOfLine {
console.warn(`Encountered unrecognized SMART info key: ${key}`);
return null;
}

InfoFieldIgnoredKey
= "Device is"
/ "Local Time is"

InfoFieldIgnored
= key:InfoFieldIgnoredKey ":" _ RestOfLine {
return null;
}
/ "SMART support is:" _ ("Available" / "Unavailable") RestOfLine {
// We don't actually care about this entry, but have to specify its possible values explicitly, to distinguish it from the entry we *do* care about that (annoyingly) uses the same key; see InfoFieldBoolean
return null;
}

InfoFieldSize
// NOTE: We don't actually care about the human-friendly display size after the 'bytes' specifier, hence the RestOfLine
= InfoFieldSizeKey _ value:SeparatedNumberValue _ "bytes"? _ RestOfLine {
return {
key: "size",
value: B(value)
};
}

InfoFieldSizeKey
= "User Capacity:"
/ "Total NVM Capacity:"

InfoFieldRPM
= "Rotation Rate:" _ value:NumberValue _ "rpm" Newline {
return {
key: "rpm",
value: value
};
}

InfoFieldSectorSizes
= "Sector Sizes:" _ logicalSize:BytesValue _ "bytes logical," _ physicalSize:BytesValue _ "bytes physical" Newline {
return {
key: "sectorSizes",
value: {
logical: logicalSize,
physical: physicalSize
}
};
}

InfoFieldBooleanKey
= "SMART support is" { return "smartEnabled"; }

InfoFieldBoolean
= key:InfoFieldBooleanKey ":" _ value:RestOfLine {
return {
key: key,
value: matchValue(value, {
Enabled: true,
Disabled: false
})
};
}

// smartctl --attributes

AttributesSection
= AttributesSectionSATA
/ AttributesSectionNVMe

AttributesSectionSATA
= "=== START OF READ SMART DATA SECTION ===" Newline
"SMART Attributes Data Structure revision number:" _ NumberValue Newline
"Vendor Specific SMART Attributes with Thresholds:" Newline
"ID#" _ "ATTRIBUTE_NAME" _ "FLAG" _ "VALUE" _ "WORST" _ "THRESH" _ "TYPE" _ "UPDATED" _ "WHEN_FAILED" _ "RAW_VALUE" Newline
attributes:AttributeFieldSATA+ {
return attributes;
}

AttributesSectionNVMe
= "=== START OF SMART DATA SECTION ===" Newline
"SMART/Health Information (NVMe Log 0x02)" Newline
attributes:AttributeFieldNVMe+ {
return attributes;
}

AttributeFlags
= "0x" number:HexNumberValue {
return mapAttributeFlags(number);
}

AttributeUpdatedWhen
= "Always"
/ "Offline"

AttributeFailedWhen
= "FAILING_NOW"
/ "In_the_past"
/ "-"

AttributeFieldType
= "Pre-fail"
/ "Old_age"

AttributeFieldSATA
= _ id:NumberValue
_ attributeName:IdentifierValue
_ flags:AttributeFlags
_ value:NumberValue
_ worstValue:NumberValue
_ threshold:NumberValue
_ type:AttributeFieldType
_ updatedWhen:AttributeUpdatedWhen
_ failedWhen:AttributeFailedWhen
_ rawValue:RestOfLine {
return {
id,
attributeName,
flags,
value,
worstValue,
threshold,
rawValue,
updatedWhen: matchValue(updatedWhen, {
"Always": "always",
"Offline": "offline"
}),
type: matchValue(type, {
"Pre-fail": "preFail",
"Old_age": "oldAge"
}),
failingNow: (failedWhen === "FAILING_NOW"),
/* TODO: Should the below include the FAILING_NOW state? */
failedBefore: (failedWhen === "In_the_past")
};
}

AttributeFieldNVMe
= label:$[^:]+ ":" _ value:RestOfLine {
return { label: label, value };
}

+ 88
- 0
src/packages/exec-smartctl/parsers/commands/attributes.pegjs View File

@@ -0,0 +1,88 @@
import { _, RestOfLine, Newline, NumberValue, HexNumberValue, IdentifierValue } from "../primitives"
import { Header } from "../shared"

{
const matchValue = require("match-value");
const mapAttributeFlags = require("../../map-attribute-flags");
}

RootAttributes
= header:Header attributesSection:AttributesSection Newline* {
return { ...header, attributes: attributesSection }
};

AttributesSection
= AttributesSectionSATA
/ AttributesSectionNVMe

AttributesSectionSATA
= "=== START OF READ SMART DATA SECTION ===" Newline
"SMART Attributes Data Structure revision number:" _ NumberValue Newline
"Vendor Specific SMART Attributes with Thresholds:" Newline
"ID#" _ "ATTRIBUTE_NAME" _ "FLAG" _ "VALUE" _ "WORST" _ "THRESH" _ "TYPE" _ "UPDATED" _ "WHEN_FAILED" _ "RAW_VALUE" Newline
attributes:AttributeFieldSATA+ {
return attributes;
}

AttributesSectionNVMe
= "=== START OF SMART DATA SECTION ===" Newline
"SMART/Health Information (NVMe Log 0x02)" Newline
attributes:AttributeFieldNVMe+ {
return attributes;
}

AttributeFlags
= "0x" number:HexNumberValue {
return mapAttributeFlags(number);
}

AttributeUpdatedWhen
= "Always"
/ "Offline"

AttributeFailedWhen
= "FAILING_NOW"
/ "In_the_past"
/ "-"

AttributeFieldType
= "Pre-fail"
/ "Old_age"

AttributeFieldSATA
= _ id:NumberValue
_ attributeName:IdentifierValue
_ flags:AttributeFlags
_ value:NumberValue
_ worstValue:NumberValue
_ threshold:NumberValue
_ type:AttributeFieldType
_ updatedWhen:AttributeUpdatedWhen
_ failedWhen:AttributeFailedWhen
_ rawValue:RestOfLine {
return {
id,
attributeName,
flags,
value,
worstValue,
threshold,
rawValue,
updatedWhen: matchValue(updatedWhen, {
"Always": "always",
"Offline": "offline"
}),
type: matchValue(type, {
"Pre-fail": "preFail",
"Old_age": "oldAge"
}),
failingNow: (failedWhen === "FAILING_NOW"),
/* TODO: Should the below include the FAILING_NOW state? */
failedBefore: (failedWhen === "In_the_past")
};
}

AttributeFieldNVMe
= label:$[^:]+ ":" _ value:RestOfLine {
return { label: label, value };
}

+ 106
- 0
src/packages/exec-smartctl/parsers/commands/info.pegjs View File

@@ -0,0 +1,106 @@
import { _, RestOfLine, Newline, NumberValue, SeparatedNumberValue, BytesValue } from "../primitives"
import { Header } from "../shared"

{
const matchValue = require("match-value");
}

RootInfo
= header:Header infoSection:InfoSection Newline* {
return { ...header, fields: infoSection }
};

InfoSection 'information section'
= "=== START OF INFORMATION SECTION ===" Newline fields:(InfoField+) {
return fields.filter((field) => field != null);
}

InfoField 'information field'
= InfoFieldSimple
/ InfoFieldIgnored
/ InfoFieldSize
/ InfoFieldRPM
/ InfoFieldSectorSizes
/ InfoFieldBoolean
/ InfoFieldUnknown

InfoFieldSimpleKey
= "Device Model" { return "model"; }
/ "Model Number" { return "model"; }
/ "Model Family" { return "modelFamily"; }
/ "Serial Number" { return "serialNumber"; }
/ "LU WWN Device Id" { return "wwn"; }
/ "Firmware Version" { return "firmwareVersion"; }
/ "Form Factor" { return "formFactor"; }
/ "ATA Version is" { return "ataVersion"; }
/ "SATA Version is" { return "sataVersion"; }

InfoFieldSimple
= key:InfoFieldSimpleKey ":" _ value:RestOfLine {
return { key: key, value: value };
}

InfoFieldUnknown
= key:$[^:]+ ":" _ RestOfLine {
console.warn(`Encountered unrecognized SMART info key: ${key}`);
return null;
}

InfoFieldIgnoredKey
= "Device is"
/ "Local Time is"

InfoFieldIgnored
= key:InfoFieldIgnoredKey ":" _ RestOfLine {
return null;
}
/ "SMART support is:" _ ("Available" / "Unavailable") RestOfLine {
// We don't actually care about this entry, but have to specify its possible values explicitly, to distinguish it from the entry we *do* care about that (annoyingly) uses the same key; see InfoFieldBoolean
return null;
}

InfoFieldSize
// NOTE: We don't actually care about the human-friendly display size after the 'bytes' specifier, hence the RestOfLine
= InfoFieldSizeKey _ value:SeparatedNumberValue _ "bytes"? _ RestOfLine {
return {
key: "size",
value: B(value)
};
}

InfoFieldSizeKey
= "User Capacity:"
/ "Total NVM Capacity:"

InfoFieldRPM
= "Rotation Rate:" _ value:NumberValue _ "rpm" Newline {
return {
key: "rpm",
value: value
};
}

InfoFieldSectorSizes
= "Sector Sizes:" _ logicalSize:BytesValue _ "bytes logical," _ physicalSize:BytesValue _ "bytes physical" Newline {
return {
key: "sectorSizes",
value: {
logical: logicalSize,
physical: physicalSize
}
};
}

InfoFieldBooleanKey
= "SMART support is" { return "smartEnabled"; }

InfoFieldBoolean
= key:InfoFieldBooleanKey ":" _ value:RestOfLine {
return {
key: key,
value: matchValue(value, {
Enabled: true,
Disabled: false
})
};
}

+ 11
- 0
src/packages/exec-smartctl/parsers/commands/scan.pegjs View File

@@ -0,0 +1,11 @@
import { _, RestOfLine } from "../primitives"

RootScan
= devices:ScanDevice* {
return { devices: devices };
}

ScanDevice 'scanned device'
= path:$[^ ]+ _ "-d" _ interface_:$[^ ]+ _ RestOfLine {
return { path: path, interface: interface_ };
}

+ 44
- 0
src/packages/exec-smartctl/parsers/primitives.pegjs View File

@@ -0,0 +1,44 @@
{
const syncpipe = require("syncpipe");
const {B} = require("../../unit-bytes-iec");
}

_
= (" " / "\t")*

RestOfLine
= content:$[^\n]+ Newline {
return content;
}

Newline
= "\n"
/ "\r\n"

BytesValue
= value:SeparatedNumberValue {
return B(value);
}
NumberValue
= value:$[0-9]+ {
return parseInt(value);
}

SeparatedNumberValue
= value:$[0-9,]+ {
return syncpipe(value, [
(_) => _.replace(/,/g, ""),
(_) => parseInt(_)
]);
}

HexNumberValue
= value:$[0-9A-Fa-f]+ {
return parseInt(value, 16);
}

IdentifierValue
= value:$[a-zA-Z_-]+ {
return value;
}

+ 6
- 0
src/packages/exec-smartctl/parsers/shared.pegjs View File

@@ -0,0 +1,6 @@
import { RestOfLine, Newline } from "./primitives"

Header 'header'
= "smartctl " versionString:RestOfLine "Copyright" copyrightStatement:RestOfLine Newline {
return { versionString, copyrightStatement };
}

+ 46
- 44
src/packages/text-parser-pegjs/index.js View File

@@ -1,10 +1,11 @@
"use strict";

const pegjs = require("pegjs");
const fs = require("fs");
const Promise = require("bluebird");
const pegRedux = require("peg-redux");
const moduleEval = require("eval");
const vm = require("vm");
const asExpression = require("as-expression");
const { chain } = require("error-chain");
const textParser = require("../text-parser");

const { validateOptions } = require("@validatem/core");
@@ -12,8 +13,8 @@ const isString = require("@validatem/is-string");
const isPlainObject = require("@validatem/is-plain-object");
const requireEither = require("@validatem/require-either");

module.exports = function createPegParser({ grammar, grammarFile, options }) {
validateOptions(arguments, [
module.exports = function createPegParser(_options) {
let { grammar, grammarFile, options } = validateOptions(arguments, [
{
grammar: [ isString ],
grammarFile: [ isString ],
@@ -21,52 +22,53 @@ module.exports = function createPegParser({ grammar, grammarFile, options }) {
}, requireEither([ "grammar", "grammarFile" ])
]);

if (grammarFile != null) {
// FIXME: cache
grammar = fs.readFileSync(grammarFile, "utf8");
}

let parserCode = pegjs.generate(grammar, {
let parserOptions = {
... options,
output: "source",
format: "commonjs"
});

let parser = asExpression(() => {
if (grammarFile != null) {
return moduleEval(parserCode, grammarFile, {}, true);
} else {
let exports_ = {};
};

let sandbox = {
exports: exports_,
module: {
return Promise.try(() => {
return (grammarFile != null)
? pegRedux.generateFromFile(grammarFile, parserOptions)
: pegRedux.generate(grammar, parserOptions);
}).then((parserCode) => {
let parser = asExpression(() => {
if (grammarFile != null) {
return moduleEval(parserCode, grammarFile, {}, true);
} else {
let exports_ = {};
let sandbox = {
exports: exports_,
},
require: function () {
throw new Error("You cannot use require() when loading a grammar as a string; use the `grammarFile` option instead");
}
};
let script = new vm.Script(parserCode.replace(/^\#\!.*/, ''));
script.runInNewContext(sandbox);

return sandbox.module.exports;
}
});
module: {
exports: exports_,
},
require: function () {
throw new Error("You cannot use require() when loading a grammar as a string; use the `grammarFile` option instead");
}
};
let script = new vm.Script(parserCode.replace(/^\#\!.*/, ''));
script.runInNewContext(sandbox);
return {
supportsStreams: false,
parse: function (text) {
try {
return parser.parse(text);
} catch (error) {
if (error.name === "SyntaxError") {
throw textParser.NoResult.chain(error, "Parsing output failed");
} else {
throw error;
return sandbox.module.exports;
}
});
return {
supportsStreams: false,
parse: function (text) {
try {
return parser.parse(text);
} catch (error) {
if (error.name === "SyntaxError") {
throw chain(error, textParser.NoResult, "Parsing output failed");
} else {
throw error;
}
}
}
}
};
};
});
};

+ 2
- 2
src/test-wrapper.js View File

@@ -26,11 +26,11 @@ return Promise.try(() => {
// return lvm.addVolumeToVolumeGroup({ volumeGroup: "vg-name", physicalVolume: "/dev/loop1" });
// return lvm.destroyPhysicalVolume({ devicePath: "/dev/loop0" });
// return lsblk();
// return smartctl.scan();
return smartctl.scan();
// return smartctl.info({ devicePath: "/dev/sda" })
// return smartctl.info({ devicePath: process.argv[2] })
// return smartctl.attributes({ devicePath: process.argv[2] });
return findmnt();
// return findmnt();
// return nvmeCli.listNamespaces({ devicePath: "/dev/nvme0" });
}).then((result) => {
console.log(util.inspect(result, {colors: true, depth: null}));


+ 7
- 0
yarn.lock View File

@@ -949,6 +949,13 @@
lodash "^4.17.19"
to-fast-properties "^2.0.0"

"@invisible/pegjs-import@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@invisible/pegjs-import/-/pegjs-import-1.1.1.tgz#1c5feb6fd768604cadd63efc9dfb999e8b0a0c7f"
integrity sha512-TiUoDxO08miDb6EQaWQeuBtkPPAsOpw55HBCbBN+EtIXy7URT1fwWNt/5k/k0pI+U58FnnGSj361oRSPATuSmw==
dependencies:
pegjs "^0.10.0"

"@joepie91/eslint-config@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@joepie91/eslint-config/-/eslint-config-1.1.0.tgz#9397e6ce0a010cb57dcf8aef8754d3a5ce0ae36a"


Loading…
Cancel
Save