From fc80a2fe040e8e17d88af2a5be3e80e955faccda Mon Sep 17 00:00:00 2001 From: Kirill Shumilov Date: Wed, 19 Apr 2017 22:18:35 +0300 Subject: [PATCH] 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 --- packages/xod-arduino-builder/src/index.js | 106 +++++++++++------- packages/xod-arduino-builder/src/messages.js | 29 +++++ packages/xod-cli/src/xodc-ab.js | 6 +- .../src/app/uploadActions.js | 27 +++-- 4 files changed, 116 insertions(+), 52 deletions(-) create mode 100644 packages/xod-arduino-builder/src/messages.js diff --git a/packages/xod-arduino-builder/src/index.js b/packages/xod-arduino-builder/src/index.js index e2336576..f40cca28 100644 --- a/packages/xod-arduino-builder/src/index.js +++ b/packages/xod-arduino-builder/src/index.js @@ -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} */ + * @return {Promise} */ 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} */ 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} */ + * @return {Promise} */ 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} */ + * @return {Promise} */ 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} */ + * @return {Promise} */ 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>} */ + CmdResult.data contains Map + * @return {Promise} */ 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} */ + * @return {Promise} */ 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} */ + * @return {Promise} */ 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} */ + CmdResult.data contains Port[] + * @return {Promise} */ 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} */ + * @return {Promise} */ 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} */ + * @return {Promise} */ 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)); } ) )); diff --git a/packages/xod-arduino-builder/src/messages.js b/packages/xod-arduino-builder/src/messages.js new file mode 100644 index 00000000..d4c3c347 --- /dev/null +++ b/packages/xod-arduino-builder/src/messages.js @@ -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'; diff --git a/packages/xod-cli/src/xodc-ab.js b/packages/xod-cli/src/xodc-ab.js index 575f4400..fd9f5133 100644 --- a/packages/xod-cli/src/xodc-ab.js +++ b/packages/xod-cli/src/xodc-ab.js @@ -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); diff --git a/packages/xod-client-electron/src/app/uploadActions.js b/packages/xod-client-electron/src/app/uploadActions.js index 565446bb..ee8c41db 100644 --- a/packages/xod-client-electron/src/app/uploadActions.js +++ b/packages/xod-client-electron/src/app/uploadActions.js @@ -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)); };