nbarikipoulos/poppy-robot-cli

View on GitHub
cli/commands/query-commands.js

Summary

Maintainability
A
0 mins
Test Coverage
'use strict'

const Table = require('cli-table')
const treeify = require('treeify')

const { createPoppy } = require('../../lib/ext-poppy-factory')
const { createYargsHelper } = require('../cli-helper')
const { prettifyError: prettify } = require('../../lib/utils')

module.exports = [{
  cmd: 'query',
  desc: 'Query registers of motors.',
  builder: (yargs) => {
    const helper = createYargsHelper(yargs)
    const flags = ['motor', 'register', 'invert', 'tree']

    helper.addOptions(flags, 'Query Options:')
      .addConnectionOptionsGroup()
      .yargs
      .strict()
      .example(
        '$0 query -r compliant',
        'Get the `compliant` register value of all motors'
      )
      .example(
        '$0 query -r compliant -t',
        'Get the `compliant` register value of all motors and display result as tree.'
      )
      .example(
        '$0 query -m m1 m6 -r present_position upper_limit',
        'Get the `present_position` and `upper_limit` register values of motors m1 and m6'
      )
  },
  handler: (argv) => query(argv) // Main job
}]

// ////////////////////////////////
// The query command itself
// ////////////////////////////////

const query = async (argv) => {
  const poppy = await createPoppy()

  //
  // Get data...
  //

  const data = await poppy.query({
    motors: argv.motor,
    registers: argv.register
  }).catch(error => {
    throw new Error(prettify(
      'error',
      'Unable to perform querying. Check connection settings',
      `Request URL: ${error.config.url}`
    ))
  })

  //
  // ...and display them, if any
  //

  if (data) {
    const d = _format(data)
    const result = argv.t
      ? _tree(d, poppy.descriptor)
      : _table(d, argv.I)

    console.log(result)
  }
}

// ////////////////////////////////
// Misc.
// ////////////////////////////////

// Display as tree
const _tree = (data, descriptor) => {
  const structure = getStructuredValues(descriptor, data)
    .filter(alias => Object.keys(alias.motors).length !== 0)
    .reduce((acc, alias) => {
      acc[alias.name] = alias.motors
      return acc
    }, {})

  let tree = 'Poppy\n'

  treeify.asLines(
    structure,
    true,
    (line) => { tree += ` ${line}\n` }
  )

  return tree
}

// Structure data with robot structure aka aliases/motors
const getStructuredValues = (descriptor, data) => {
  const motorIds = Object.keys(data)
  const registers = Object.keys(data[motorIds[0]])

  return descriptor.aliases
    .map(alias => ({
      name: alias.name,
      motors: alias.motors
        .filter(motor => data[motor])
        .reduce((acc, motor) => {
          // 1 register queried => compact motor name and value on a single line
          acc[motor] = registers.length === 1
            ? Object.values(data[motor])[0]
            : data[motor] // should be copied...
          return acc
        }, {})
    }))
}

// Display as table
const _table = (data, invert = false) => {
  const motorIds = Object.keys(data)
  const registers = Object.keys(data[motorIds[0]])

  const rows = invert ? motorIds : registers
  const cols = invert ? registers : motorIds

  const value = (invert, col, row) => invert ? data[row][col] : data[col][row]

  const table = new Table({ head: ['', ...cols] })

  for (const row of rows) {
    table.push({
      [row]: cols.map(col => value(invert, col, row))
    })
  }

  return table.toString()
}

const _format = (value) => {
  const type = typeof value
  let fn

  switch (type) {
    case 'object':
      fn = (object) => {
        Object.entries(object)
          .forEach(([k, v]) => { object[k] = _format(v) })
        return object
      }
      break
    case 'undefined':
      fn = _ => '---'
      break
    default:
      fn = (value) => (!isNaN(parseFloat(value)) ^ !Number.isInteger(value))
        ? value // String, Boolean, Integer
        : Number(value).toFixed(1) // Float: 1 significant digit is enough
  }

  return fn(value)
}