Skip to content

Commit

Permalink
Modularize index + config checker (#6)
Browse files Browse the repository at this point in the history
* Extract out of index different sub-functionnality into modules
* Create a config checker module that make sure the different attribute are valid
* Reset works for Log based widget
* Changelog modification
  • Loading branch information
genintho authored Jul 4, 2018
1 parent b4754ba commit c2483f9
Show file tree
Hide file tree
Showing 13 changed files with 410 additions and 79 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

# Head

- 🐛 Reset functionality is clearing log from RawLog and FilterLog widgets
- 🐛 Widget were not loading when use in a different directory
- 🛠 Internal code reorganization
- 🛠 Nicer error message when detecting a config error


## 1.0.1

Expand Down
6 changes: 6 additions & 0 deletions exceptions/ConfigError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = class ConfigError extends Error {
constructor(...args) {
super(...args);
Error.captureStackTrace(this, ConfigError);
}
};
89 changes: 16 additions & 73 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#!/usr/bin/env node

const _ = require("lodash");
const blessed = require("blessed");
const calculateScreenSize = require("./utils/calculateScreenSize");
const ConfigError = require("./exceptions/ConfigError");
const configChecker = require("./utils/configChecker");
const configReader = require("./utils/configReader");
const contrib = require("blessed-contrib");
const fs = require("fs");
const path = require("path");
const program = require("commander");
const stripJsonComments = require("strip-json-comments");
const TailLib = require("tail").Tail;
const widgetFactory = require("./utils/widgetFactory");

program
.option(
Expand All @@ -17,85 +18,27 @@ program
)
.parse(process.argv);

if (!fs.existsSync(program.config)) {
console.error(`File ${program.config} can not be found`);
process.exit(1);
}

let config = {};
const config = configReader(program.config);
try {
config = JSON.parse(
stripJsonComments(fs.readFileSync(program.config, { encoding: "utf-8" }))
);
configChecker(config);
} catch (e) {
console.error(e);
process.exit(1);
}
const configTypeConstructor = new Map();

const widgetFolder = path.resolve(__dirname, "./widgets");
fs.readdirSync(widgetFolder).forEach(fileName => {
const modulePath = path.resolve(widgetFolder, fileName);
if (fs.lstatSync(modulePath).isDirectory()) {
return;
}
const module = require(modulePath);

if (!module.hasOwnProperty("CONFIG_TYPE")) {
console.warn(`Widget definition in ${fileName} need to define CONFIG_TYPE`);
return;
}
if (!module.hasOwnProperty("sanitizeConfig")) {
console.warn(
`Widget definition in ${fileName} need to define sanitizeConfig`
);
return;
}
configTypeConstructor.set(module.CONFIG_TYPE, module);
});

const colNb = config.widgets.reduce((accumulator, current) => {
if (!accumulator) {
accumulator = 0;
}
return Math.max(accumulator, current.col + 1);
}, 0);

const rowNb = config.widgets.reduce((accumulator, current) => {
if (!accumulator) {
accumulator = 0;
if (e instanceof ConfigError) {
console.error(e.message);
process.exit(1);
}
return Math.max(accumulator, current.row + 1);
}, 0);

}
const screen = blessed.screen({
smartCSR: true,
autoPadding: true
});
const grid = new contrib.grid({ rows: rowNb, cols: colNb, screen: screen });

const uniqueScreenName = new Set();
const widgets = config.widgets.map((screenConfig, index) => {
if (!_.isString(screenConfig.name)) {
throw new Error(
`Screen ${index + 1} is missing the required attribute 'name'`
);
}
if (uniqueScreenName.has(screenConfig.name)) {
throw new Error(`Multiple screen with name ${screenConfig.name}`);
}
uniqueScreenName.add(screenConfig.name);

if (!configTypeConstructor.has(screenConfig.type)) {
throw new Error(
`Unknown widget type ${screenConfig.type} at index ${index}`
);
}
const cls = configTypeConstructor.get(screenConfig.type);
const cleanConfig = cls.sanitizeConfig(screenConfig);
return new cls(grid, cleanConfig);
const grid = new contrib.grid({
...calculateScreenSize(config),
screen: screen
});

const widgets = widgetFactory(grid, config);

const tail = new TailLib(config.source);

tail.on("line", function(line) {
Expand Down
27 changes: 27 additions & 0 deletions utils/__tests__/calculateScreenSize.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const calculateScreenSize = require("../calculateScreenSize");

describe("utils/calculateScreenSize", () => {
it("find biggest number", () => {
expect(
calculateScreenSize({
widgets: [
{
col: 27,
colspan: 1,
row: 0,
rowspan: 2
},
{
col: 12,
colspan: 1,
row: 1,
rowspan: 2
}
]
})
).toEqual({
rows: 2,
cols: 28
});
});
});
227 changes: 227 additions & 0 deletions utils/__tests__/configChecker.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
const configChecker = require("../configChecker");
const RawLog = require("../../widgets/RawLog");

describe("utils/configChecker", () => {
it("returns true when valud", () => {
expect(
configChecker({
source: "a",
widgets: [
{
name: "colspan invalid",
type: RawLog.CONFIG_TYPE,
row: 1,
col: 1,
rowspan: 1,
colspan: 1
}
]
})
).toBe(true);
});

describe("source", () => {
it("detect missing", () => {
expect(() => {
configChecker({});
}).toThrow("must contain a 'source' ");
});

it("detect invalid", () => {
expect(() => {
configChecker({ source: 1 });
}).toThrow("must contain a 'source' ");
});
});
describe("widgets", () => {
it("detect missing", () => {
expect(() => {
configChecker({ source: "a" });
}).toThrow("must have at least 1 widget");
});
it("detect invalid", () => {
expect(() => {
configChecker({ source: "a", widgets: {} });
}).toThrow("must have at least 1 widget");
});
it("detect empty", () => {
expect(() => {
configChecker({ source: "a", widgets: [] });
}).toThrow("must have at least 1 widget");
});
});

describe("individual widget", () => {
it("detect empty", () => {
expect(() => {
configChecker({ source: "a", widgets: [1] });
}).toThrow("is not an object");
});
describe("name", () => {
it("detect missing", () => {
expect(() => {
configChecker({ source: "a", widgets: [{}] });
}).toThrow("missing the required attribute 'name'");
});
it("detect invalid ", () => {
expect(() => {
configChecker({ source: "a", widgets: [{ name: 1 }] });
}).toThrow("must be a string");
});
it("detect duplicate ", () => {
expect(() => {
configChecker({
source: "a",
widgets: [
{
type: RawLog.CONFIG_TYPE,
name: "Sonos App Error type",
match: "<Error> ([a-z]+)",
col: 3,
colspan: 1,
row: 0,
rowspan: 2
},
{
type: RawLog.CONFIG_TYPE,
name: "Sonos App Error type",
match: "<Error> ([a-z]+)",
col: 3,
colspan: 1,
row: 0,
rowspan: 2
}
]
});
}).toThrow("Multiple widgets with name");
});
});
describe("type", () => {
it("detect missing", () => {
expect(() => {
configChecker({ source: "a", widgets: [{ name: "1" }] });
}).toThrow("must have a type");
});
it("detect invalid ", () => {
expect(() => {
configChecker({ source: "a", widgets: [{ name: "1", type: "1" }] });
}).toThrow("an unknown type ");
});
});
describe("row", () => {
it("detect missing", () => {
expect(() => {
configChecker({
source: "a",
widgets: [{ name: "1", type: RawLog.CONFIG_TYPE }]
});
}).toThrow("missing required integer attribute 'row'");
});
it("detect invalid ", () => {
expect(() => {
configChecker({
source: "a",
widgets: [{ name: "1", type: RawLog.CONFIG_TYPE, row: "a" }]
});
}).toThrow("'row' must be an integer");
});
});
describe("col", () => {
it("detect missing", () => {
expect(() => {
configChecker({
source: "a",
widgets: [{ name: "1", type: RawLog.CONFIG_TYPE, row: 1 }]
});
}).toThrow("missing required integer attribute 'col'");
});
it("detect invalid ", () => {
expect(() => {
configChecker({
source: "a",
widgets: [{ name: "1", type: RawLog.CONFIG_TYPE, row: 1, col: "a" }]
});
}).toThrow("'col' must be an integer");
});
});
describe("col", () => {
it("detect missing", () => {
expect(() => {
configChecker({
source: "a",
widgets: [{ name: "1", type: RawLog.CONFIG_TYPE, row: 1 }]
});
}).toThrow("missing required integer attribute 'col'");
});
it("detect invalid ", () => {
expect(() => {
configChecker({
source: "a",
widgets: [{ name: "1", type: RawLog.CONFIG_TYPE, row: 1, col: "a" }]
});
}).toThrow("'col' must be an integer");
});
});
describe("rowspan", () => {
it("detect missing", () => {
expect(() => {
configChecker({
source: "a",
widgets: [{ name: "1", type: RawLog.CONFIG_TYPE, row: 1, col: 1 }]
});
}).toThrow("missing required integer attribute 'rowspan'");
});
it("detect invalid ", () => {
expect(() => {
configChecker({
source: "a",
widgets: [
{
name: "1",
type: RawLog.CONFIG_TYPE,
row: 1,
col: 1,
rowspan: "a"
}
]
});
}).toThrow("'rowspan' must be an integer");
});
});
describe("colspan", () => {
it("detect missing", () => {
expect(() => {
configChecker({
source: "a",
widgets: [
{
name: "1",
type: RawLog.CONFIG_TYPE,
row: 1,
col: 1,
rowspan: 1
}
]
});
}).toThrow("missing required integer attribute 'colspan'");
});
it("detect invalid ", () => {
expect(() => {
configChecker({
source: "a",
widgets: [
{
name: "colspan invalid",
type: RawLog.CONFIG_TYPE,
row: 1,
col: 1,
rowspan: 1,
colspan: "a"
}
]
});
}).toThrow("'colspan' must be an integer");
});
});
});
});
Loading

0 comments on commit c2483f9

Please sign in to comment.