refactor(xod-arduino-builder, xod-cli, xod-client-electron): make all Promises in xod-arduino-builder returns unified object CmdResult and use it in packages-consumers

This commit is contained in:
Kirill Shumilov
2017-04-19 22:18:35 +03:00
parent 7b2bac8cc8
commit fc80a2fe04
4 changed files with 116 additions and 52 deletions

View File

@@ -37,6 +37,13 @@
* @property {string} board
* @property {string} package */
/** Command result
* @typedef {Object} CmdResult
* @property {boolean} success
* @property {string} module
* @property {string} message
* @property {*} data */
/** Serial port object.
* @typedef {Object} Port
* @property {string} comName - The {@link Path} or identifier used to open the serial port.
@@ -51,7 +58,12 @@ import mime from 'rest/interceptor/mime';
import SerialPort from 'serialport';
import shelljs from 'shelljs';
const catchRestError = err => Promise.reject(R.prop('error', err));
import * as msg from './messages';
const success = R.curry((message, data) => ({ success: true, module: module.id, message, data }));
const error = R.compose(R.assoc('success', false), success);
const unwrapCmdResult = R.prop('data');
/** A url of the [official Arduino package index]{@link http://downloads.arduino.cc/packages/package_index.json}.
* @constant
@@ -75,20 +87,22 @@ const CONFIG_PATH = path.resolve(path.dirname(module.filename), 'config.json');
/** Writes the provided configuration to {@link CONFIG_PATH} file.
* @param {*} config
* @return {Promise<void>} */
* @return {Promise<CmdResult>} */
const setConfig = config =>
Promise.resolve()
.then(() => fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2)))
.catch(() => Promise.reject([module.id, 'could not set config']));
.then(() => success(msg.CONFIG_SETTED, {}))
.catch(err => Promise.reject(error(msg.CONFIG_SET_ERROR, err)));
/** Reads the configuration value from file at {@link CONFIG_PATH}.
* @param {RamdaPath} [ramdaPath=[]] - Ramda path to configuration value.
* @return {Promise<*>} */
* @return {Promise<CmdResult>} */
const getConfig = (ramdaPath = []) =>
Promise.resolve()
.then(() => JSON.parse(fs.readFileSync(CONFIG_PATH).toString()))
.then(R.path(ramdaPath))
.catch(() => Promise.reject([module.id, 'could not get config']));
.then(success(msg.CONFIG_GETTED))
.catch(err => Promise.reject(error(msg.CONFIG_GET_ERROR, err)));
/** Parses Arduino's `.txt` definition file.
* @kind function
@@ -107,59 +121,68 @@ const parseTxtConfig = R.compose(
/** Sets path to Arduino IDE executable.
* @param {Path} arduinoIdePathExecutable - Path to Arduino IDE executable.
* @return {Promise<void>} */
* @return {Promise<CmdResult>} */
export const setArduinoIdePathExecutable = arduinoIdePathExecutable =>
getConfig()
.then(unwrapCmdResult)
.catch(() => Promise.resolve({}))
.then(R.assocPath(ARDUINO_IDE_PATH_EXECUTABLE)(arduinoIdePathExecutable))
.then(setConfig);
/** Sets path to Arduino IDE packages.
* @param {Path} arduinoIdePathPackages - Path to Arduino IDE packages.
* @return {Promise<void>} */
* @return {Promise<CmdResult>} */
export const setArduinoIdePathPackages = arduinoIdePathPackages =>
getConfig()
.then(unwrapCmdResult)
.catch(() => Promise.resolve({}))
.then(R.assocPath(ARDUINO_IDE_PATH_PACKAGES)(arduinoIdePathPackages))
.then(setConfig);
/** Lists the raw [official Arduino package index]{@link http://downloads.arduino.cc/packages/package_index.json}.
* @return {Promise<Object>} */
* @return {Promise<CmdResult>} */
export const listPackageIndex = () =>
rest.wrap(mime).wrap(errorCode)({ path: ARDUINO_PACKAGE_INDEX_URL })
.then(R.prop('entity'))
.catch(catchRestError);
.then(success(msg.INDEX_LIST_GETTED))
.catch(err => Promise.reject(error(msg.INDEX_LIST_ERROR, err)));
/** Lists the processed [official Arduino package index]{@link http://downloads.arduino.cc/packages/package_index.json}, optimized for {@link PAV} selection.
* @return {Promise<Map<string, PAV[]>>} */
CmdResult.data contains Map<string, PAV[]>
* @return {Promise<CmdResult>} */
export const listPAVs = () =>
listPackageIndex().then(R.compose(
R.groupBy(pav => `${pav.package}:${pav.architecture}`),
R.unnest,
R.map(({ name, platforms }) =>
platforms.map(({ architecture, version }) => ({
package: name,
architecture,
version,
}))
),
R.prop('packages')
)).catch(catchRestError);
listPackageIndex()
.then(unwrapCmdResult)
.then(R.compose(
R.groupBy(pav => `${pav.package}:${pav.architecture}`),
R.unnest,
R.map(({ name, platforms }) =>
platforms.map(({ architecture, version }) => ({
package: name,
architecture,
version,
}))
),
R.prop('packages')
))
.then(success(msg.PAVS_LIST_GETTED))
.catch(error(msg.PAVS_LIST_ERROR));
/** Installs the selected {@link PAV}.
* @param {PAV} pav - Selected {@link PAV}.
* @return {Promise<boolean>} */
* @return {Promise<CmdResult>} */
export const installPAV = pav =>
getConfig(ARDUINO_IDE_PATH_EXECUTABLE)
.then(unwrapCmdResult)
.then(
arduino => new Promise(
(resolve, reject) => {
shelljs.exec(
`${arduino} --install-boards ${pav.package}:${pav.architecture}:${pav.version}`,
(code) => {
if (code === 255) { return resolve('This PAV is already installed.'); }
if (code === 0) { return resolve(); }
return reject([module.id, 'could not install boards']);
(code, stdout, stderr) => {
if (code === 255) { return resolve(success(msg.PAV_ALREADY_INSTALLED, stdout)); }
if (code === 0) { return resolve(success(msg.PAV_INSTALLED, stdout)); }
return reject(error(msg.PAV_INSTALL_ERROR, stderr));
}
);
}
@@ -168,35 +191,39 @@ export const installPAV = pav =>
/** Lists the boards supported by the selected {@link PAV}.
* @param {PAV} pav - Selected {@link PAV}.
* @return {Promise<Object>} */
* @return {Promise<CmdResult>} */
export const listPAVBoards = pav =>
getConfig(ARDUINO_IDE_PATH_PACKAGES)
.then(unwrapCmdResult)
.then(packages => fs.readFileSync(path.resolve(packages, pav.package, 'hardware', pav.architecture, pav.version, 'boards.txt')).toString())
.then(parseTxtConfig)
.catch(() => Promise.reject([module.id, 'could not parse boards config']));
.then(success(msg.BOARDS_LIST_GETTED))
.catch(err => Promise.reject(error(msg.BOARDS_LIST_ERROR, err)));
/** Lists the available {@link Port}s.
* @return {Promise<Port[]>} */
CmdResult.data contains Port[]
* @return {Promise<CmdResult>} */
export const listPorts = () =>
new Promise((resolve, reject) => {
SerialPort.list((error, ports) => {
if (error) reject(error);
else resolve(ports);
SerialPort.list((err, ports) => {
if (err) reject(error(msg.PORTS_LIST_ERROR, err));
else resolve(success(msg.PORTS_LIST_GETTED, ports));
});
});
/** Compiles the file for the selected {@link PAB}.
* @param {PAB} pab - Package, architecture, board.
* @param {Path} file - Path to the compilation source.
* @return {Promise<boolean>} */
* @return {Promise<CmdResult>} */
export const verify = (pab, file) =>
getConfig(ARDUINO_IDE_PATH_EXECUTABLE)
.then(unwrapCmdResult)
.then(arduino => new Promise((resolve, reject) =>
shelljs.exec(
`${arduino} --verify --board "${pab.package}:${pab.architecture}:${pab.board}" "${file}"`,
(code, stdout, stderr) => {
if (code === 0) { return resolve(stdout); }
return reject(stderr);
if (code === 0) { return resolve(success(msg.SKETCH_VERIFIED, stdout)); }
return reject(error(msg.SKETCH_VERIFY_ERROR, stderr));
}
)
))
@@ -206,15 +233,16 @@ export const verify = (pab, file) =>
* @param {PAB} pab - Package, architecture, board.
* @param {Port#comName} port - Port.
* @param {Path} file - Path to the compilation source.
* @return {Promise<boolean>} */
* @return {Promise<CmdResult>} */
export const upload = (pab, port, file) =>
getConfig(ARDUINO_IDE_PATH_EXECUTABLE)
.then(unwrapCmdResult)
.then(arduino => new Promise((resolve, reject) =>
shelljs.exec(
`${arduino} --upload --board "${pab.package}:${pab.architecture}:${pab.board}" --port "${port}" "${file}"`,
(code, stdout, stderr) => {
if (code === 0) { return resolve(stdout); }
return reject(stderr);
if (code === 0) { return resolve(success(msg.SKETCH_UPLOADED, stdout)); }
return reject(error(msg.SKETCH_UPLOAD_ERROR, stderr));
}
)
));

View File

@@ -0,0 +1,29 @@
export const CONFIG_SETTED = 'Config setted';
export const CONFIG_SET_ERROR = 'Could not set config';
export const CONFIG_GETTED = 'Config getted';
export const CONFIG_GET_ERROR = 'Could not get config';
export const REST_ERROR = 'Could not get REST';
export const PAVS_LIST_GETTED = 'List of PAVs getted';
export const PAVS_LIST_ERROR = 'Could not get list of PAVs';
export const INDEX_LIST_GETTED = 'List of packages getted';
export const INDEX_LIST_ERROR = 'Could not get list of packages';
export const BOARDS_LIST_GETTED = 'List of Boards getted';
export const BOARDS_LIST_ERROR = 'Could not parse boards config';
export const PORTS_LIST_GETTED = 'List of ports getted';
export const PORTS_LIST_ERROR = 'Could not get list of ports';
export const PAV_ALREADY_INSTALLED = 'This PAV is already installed';
export const PAV_INSTALLED = 'PAV has been installed';
export const PAV_INSTALL_ERROR = 'Could not install PAV';
export const SKETCH_VERIFIED = 'Sketch verified';
export const SKETCH_UPLOADED = 'Sketch uploaded';
export const SKETCH_VERIFY_ERROR = 'Could not verify sketch';
export const SKETCH_UPLOAD_ERROR = 'Could not upload sketch';

View File

@@ -2,9 +2,9 @@ import * as ab from 'xod-arduino-builder';
import * as messages from './messages';
function run(promise, { success, error }) {
promise.then((value) => {
if (value) {
messages.notice(JSON.stringify(value, null, 2));
promise.then((response) => {
if (response && response.data) {
messages.notice(JSON.stringify(response.data, null, 2));
}
if (success) {
messages.success(success);

View File

@@ -74,6 +74,7 @@ export const checkArduinoIde = (updatePaths, success) => {
const getPAV = pab => R.composeP(
R.last,
R.prop(`${pab.package}:${pab.architecture}`),
R.prop('data'),
xab.listPAVs
)();
@@ -100,7 +101,7 @@ export const findPort = (pab, success) => {
};
return xab.listPorts()
.then(ports => R.compose(
.then(R.compose(
R.ifElse(
R.isNil,
() => Promise.reject(new Error('Could not find Arduino device on opened ports.')),
@@ -109,8 +110,9 @@ export const findPort = (pab, success) => {
R.propOr(null, 'comName'),
R.find(
R.propEq('vendorId', '0x2341') // TODO: Replace it with normal find function
)
)(ports))
),
R.prop('data')
))
.then((port) => { success(result); return port; })
.catch(throwError(result));
};
@@ -126,7 +128,7 @@ export const doTranspileForArduino = ({ pab, project, patchId }, success) => {
return Promise.resolve(project)
.then(v2 => transpileForArduino(v2, patchId))
.then(foldEither(
err => Promise.reject(new Error(err)),
err => Promise.reject(err),
code => Promise.resolve(code)
))
.then((code) => { success(result); return code; })
@@ -144,16 +146,21 @@ export const uploadToArduino = (pab, port, code, success) => {
message: 'Code has been successfully uploaded.',
percentage: 55,
};
const updateMessageByData = R.compose(
R.assoc('message', R.__, result),
R.prop('data')
);
const clearTmp = () => fs.unlinkSync(tmpPath);
return writeFile(tmpPath, code)
.then(({ path }) => xab.upload(pab, port, path))
.then((response) => { result.message = response; })
.catch((errorMessage) => {
clearTmp();
return Promise.reject(new Error(`Can't build or upload project to Arduino.\n${errorMessage}`));
})
.then(() => success(result))
.then(updateMessageByData)
.catch(R.compose(
err => Promise.reject(err),
R.tap(clearTmp),
updateMessageByData
))
.then(success)
.then(() => clearTmp())
.catch(throwError(result));
};