Make use of resin-cli-visuals and get rid of local ui module

This commit is contained in:
Juan Cruz Viotti 2015-01-21 09:50:19 -04:00
parent 2c90d1176c
commit 50325d0f8f
15 changed files with 29 additions and 453 deletions

View File

@ -1,7 +1,7 @@
_ = require('lodash-contrib')
async = require('async')
resin = require('resin-sdk')
ui = require('../ui')
visuals = require('resin-cli-visuals')
commandOptions = require('./command-options')
exports.create =
@ -39,7 +39,7 @@ exports.create =
return callback(null, deviceType)
else
deviceTypes = resin.models.device.getSupportedDeviceTypes()
ui.widgets.select('Select a type', deviceTypes, callback)
visuals.widgets.select('Select a type', deviceTypes, callback)
(type, callback) ->
resin.models.application.create(params.name, type, callback)
@ -62,7 +62,7 @@ exports.list =
action: (params, options, done) ->
resin.models.application.getAll (error, applications) ->
return done(error) if error?
console.log ui.widgets.table.horizontal applications, [
console.log visuals.widgets.table.horizontal applications, [
'ID'
'Name'
'Device Display Name'
@ -84,7 +84,7 @@ exports.info =
action: (params, options, done) ->
resin.models.application.get params.id, (error, application) ->
return done(error) if error?
console.log ui.widgets.table.vertical application, [
console.log visuals.widgets.table.vertical application, [
'ID'
'Name'
'Device Display Name'
@ -122,7 +122,7 @@ exports.remove =
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
ui.patterns.remove 'application', options.yes, (callback) ->
visuals.patterns.remove 'application', options.yes, (callback) ->
resin.models.application.remove(params.id, callback)
, done

View File

@ -2,7 +2,7 @@ _ = require('lodash-contrib')
url = require('url')
async = require('async')
resin = require('resin-sdk')
ui = require('../ui')
visuals = require('resin-cli-visuals')
helpers = require('../helpers/helpers')
exports.login =
@ -26,7 +26,7 @@ exports.login =
if params.credentials?
return helpers.parseCredentials(params.credentials, callback)
else
return ui.widgets.login(callback)
return visuals.widgets.login(callback)
(credentials, callback) ->
resin.auth.login(credentials, callback)
@ -70,7 +70,7 @@ exports.signup =
async.waterfall([
(callback) ->
ui.widgets.register(callback)
visuals.widgets.register(callback)
(credentials, callback) ->
resin.auth.register credentials, (error, token) ->

View File

@ -4,7 +4,7 @@ resin = require('resin-sdk')
os = require('os')
fs = require('fs')
progressStream = require('progress-stream')
ui = require('../ui')
visuals = require('resin-cli-visuals')
commandOptions = require('./command-options')
exports.list =
@ -21,7 +21,7 @@ exports.list =
action: (params, options, done) ->
resin.models.device.getAllByApplication options.application, (error, devices) ->
return done(error) if error?
console.log ui.widgets.table.horizontal devices, [
console.log visuals.widgets.table.horizontal devices, [
'ID'
'Name'
'Device Display Name'
@ -46,7 +46,7 @@ exports.info =
action: (params, options, done) ->
resin.models.device.get params.id, (error, device) ->
return done(error) if error?
console.log ui.widgets.table.vertical device, [
console.log visuals.widgets.table.vertical device, [
'ID'
'Name'
'Device Display Name'
@ -80,7 +80,7 @@ exports.remove =
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
ui.patterns.remove 'device', options.yes, (callback) ->
visuals.patterns.remove 'device', options.yes, (callback) ->
resin.models.device.remove(params.id, callback)
, done
@ -118,7 +118,7 @@ exports.rename =
(callback) ->
if not _.isEmpty(params.name)
return callback(null, params.name)
ui.widgets.ask('How do you want to name this device?', callback)
visuals.widgets.ask('How do you want to name this device?', callback)
(name, callback) ->
resin.models.device.rename(params.id, name, callback)
@ -184,7 +184,8 @@ exports.init =
if options.yes
return callback(null, true)
else
ui.widgets.confirm("This will completely erase #{params.device}. Are you sure you want to continue?", callback)
confirmMessage = "This will completely erase #{params.device}. Are you sure you want to continue?"
visuals.widgets.confirm(confirmMessage, callback)
(confirmed, callback) ->
return done() if not confirmed
@ -198,7 +199,7 @@ exports.init =
time: 500
if not options.quiet
progressBar = new ui.widgets.Progress('Writing device OS', imageFileSize)
progressBar = new visuals.widgets.Progress('Writing device OS', imageFileSize)
progress.on 'progress', (status) ->
progressBar.tick(status.delta)

View File

@ -1,6 +1,6 @@
_ = require('lodash-contrib')
resin = require('resin-sdk')
ui = require('../ui')
visuals = require('resin-cli-visuals')
commandOptions = require('./command-options')
exports.list =
@ -36,7 +36,7 @@ exports.list =
if not options.verbose
environmentVariables = _.reject(environmentVariables, resin.models.environmentVariables.isSystemVariable)
console.log(ui.widgets.table.horizontal(environmentVariables))
console.log(visuals.widgets.table.horizontal(environmentVariables))
return done()
exports.remove =
@ -57,7 +57,7 @@ exports.remove =
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
ui.patterns.remove 'environment variable', options.yes, (callback) ->
visuals.patterns.remove 'environment variable', options.yes, (callback) ->
resin.models.environmentVariables.remove(params.id, callback)
, done

View File

@ -4,7 +4,7 @@ path = require('path')
_ = require('lodash')
gitCli = require('git-cli')
resin = require('resin-sdk')
ui = require('../ui')
visuals = require('resin-cli-visuals')
examplesData = require('../data/examples.json')
exports.list =
@ -26,7 +26,7 @@ exports.list =
example.author ?= 'Unknown'
return example
console.log ui.widgets.table.horizontal examplesData, [
console.log visuals.widgets.table.horizontal examplesData, [
'ID'
'Display Name'
'Repository'
@ -53,7 +53,7 @@ exports.info =
example.id = id
example.author ?= 'Unknown'
console.log ui.widgets.table.vertical example, [
console.log visuals.widgets.table.vertical example, [
'ID'
'Display Name'
'Description'

View File

@ -4,7 +4,7 @@ async = require('async')
fs = require('fs')
resin = require('resin-sdk')
helpers = require('../helpers/helpers')
ui = require('../ui')
visuals = require('resin-cli-visuals')
commandOptions = require('./command-options')
exports.list =
@ -20,7 +20,7 @@ exports.list =
action: (params, options, done) ->
resin.models.key.getAll (error, keys) ->
return done(error) if error?
console.log ui.widgets.table.horizontal keys, [ 'ID', 'Title' ]
console.log visuals.widgets.table.horizontal keys, [ 'ID', 'Title' ]
return done()
exports.info =
@ -37,7 +37,7 @@ exports.info =
resin.models.key.get params.id, (error, key) ->
return done(error) if error?
key.public_key = '\n' + _.str.chop(key.public_key, resin.settings.get('sshKeyWidth')).join('\n')
console.log(ui.widgets.table.vertical(key, [ 'ID', 'Title', 'Public Key' ]))
console.log(visuals.widgets.table.vertical(key, [ 'ID', 'Title', 'Public Key' ]))
return done()
exports.remove =
@ -56,7 +56,7 @@ exports.remove =
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
ui.patterns.remove 'key', options.yes, (callback) ->
visuals.patterns.remove 'key', options.yes, (callback) ->
resin.models.key.remove(params.id, callback)
, done

View File

@ -3,7 +3,7 @@ async = require('async')
path = require('path')
mkdirp = require('mkdirp')
resin = require('resin-sdk')
ui = require('../ui')
visuals = require('resin-cli-visuals')
exports.download =
signature: 'os download <id>'
@ -81,7 +81,7 @@ exports.download =
resin.models.os.download osParams, outputFile, callback, (state) ->
return if options.quiet
bar ?= new ui.widgets.Progress('Downloading device OS', state.total)
bar ?= new visuals.widgets.Progress('Downloading device OS', state.total)
return if bar.complete or not state?

View File

@ -1,3 +0,0 @@
module.exports =
widgets: require('./widgets/widgets')
patterns: require('./patterns/patterns')

View File

@ -1,19 +0,0 @@
async = require('async')
fs = require('fs')
widgets = require('../widgets/widgets')
ProgressBar = require('progress')
exports.remove = (name, confirmAttribute, deleteFunction, outerCallback) ->
async.waterfall([
(callback) ->
if confirmAttribute
return callback(null, true)
widgets.confirmRemoval(name, callback)
(confirmed, callback) ->
return callback() if not confirmed
deleteFunction(callback)
], outerCallback)

View File

@ -1,83 +0,0 @@
_ = require('lodash')
_.str = require('underscore.string')
KEY_DISPLAY_MAP =
app_name: 'Name'
last_seen_time: 'Last Seen'
ip_address: 'IP Address'
id: 'ID'
uuid: 'UUID'
startsWithLetter = (string) ->
firstLetter = _.first(string)
return /[a-z|A-Z]/.test(firstLetter)
renameObjectKey = (object, key, newKey) ->
return if key is newKey
object[newKey] = object[key]
delete object[key]
exports.getKeyName = (key) ->
nameFromMap = KEY_DISPLAY_MAP[key]
return nameFromMap if nameFromMap?
# Prevent modifying a value that is part of
# the map values.
# This is really an heuristic, as making sure
# the client actually refers to that value means
# converting the value to lowercase-underscore-cased
# and do the check, but that seems overkill.
if _.values(KEY_DISPLAY_MAP).indexOf(key) isnt -1
return key
key = _.str.humanize(key)
return _.str.titleize(key)
isReallyEmpty = (value) ->
# For some reason, _.isEmpty returns
# true for numbers and booleans
return false if _.isNumber(value) or _.isBoolean(value)
return _.isEmpty(value)
exports.prepareObject = (object) ->
object = _.omit object, (value, key) ->
return not startsWithLetter(key)
for key, value of object
if _.isObject(value) and not _.isArray(value)
object[key] = exports.prepareObject(value)
newKeyName = exports.getKeyName(key)
renameObjectKey(object, key, newKeyName)
object = _.omit object, (value, key) ->
return isReallyEmpty(value)
return object
exports.processTableContents = (contents) ->
return if not contents?
# Allows us to simplify the algorithm by not
# concerning about different input types
if not _.isArray(contents)
contents = [ contents ]
return _.map(contents, exports.prepareObject)
isRealObject = (object) ->
return false if _.isArray(object) or _.isFunction(object)
return _.isObject(object)
exports.getDefaultContentsOrdering = (contents) ->
return if _.isEmpty(contents)
firstContentEntry = _.first(contents)
return if not isRealObject(firstContentEntry)
return _.keys(firstContentEntry)
exports.normaliseOrdering = (ordering, contents) ->
if not _.isEmpty(ordering)
return _.map(ordering, exports.getKeyName)
return exports.getDefaultContentsOrdering(contents)

View File

@ -1,157 +0,0 @@
expect = require('chai').expect
sinon = require('sinon')
_ = require('lodash')
tableHelpers = require('./table-helpers')
OBJECTS =
application:
device: null
id: 162
user:
__deferred: []
__id: 24
app_name: 'HelloResin'
git_repository: 'git@git.staging.resin.io:jviotti/helloresin.git'
commit: '1234'
device_type: 'raspberry-pi'
__metadata:
uri: '/ewa/application(162)'
basic:
Hello: 'world'
Hey: 'there'
__private: true
_option: false
$data: [ 1, 2, 3 ]
recursive:
hello: 'world'
Hey:
__private: true
value:
$option: false
data: 'There'
_option: false
__something:
value: 'ok'
nested:
$data: [ 1, 2, 3 ]
valid:
One: 'one'
Two: 'two'
Three: 'three'
describe 'Table Helpers:', ->
describe '#getKeyName()', ->
it 'should return titleized names', ->
expect(tableHelpers.getKeyName('hello world')).to.equal('Hello World')
expect(tableHelpers.getKeyName('foo Bar')).to.equal('Foo Bar')
it 'should return custom names from the map', ->
expect(tableHelpers.getKeyName('last_seen_time')).to.equal('Last Seen')
expect(tableHelpers.getKeyName('app_name')).to.equal('Name')
it 'should remove underscores', ->
expect(tableHelpers.getKeyName('git_repository')).to.equal('Git Repository')
expect(tableHelpers.getKeyName('device_type')).to.equal('Device Type')
expect(tableHelpers.getKeyName('is_web_accessible')).to.equal('Is Web Accessible')
describe '#prepareObject()', ->
it 'should get rid of keys not starting with letters', ->
expect(tableHelpers.prepareObject(OBJECTS.basic)).to.deep.equal
Hello: 'world'
Hey: 'there'
it 'should get rid of keys not starting with letters recursively', ->
expect(tableHelpers.prepareObject(OBJECTS.recursive)).to.deep.equal
Hello: 'world'
Hey:
Value:
Data: 'There'
it 'should do proper key renamings', ->
expect(tableHelpers.prepareObject(OBJECTS.application)).to.deep.equal
ID: 162
Name: 'HelloResin'
'Git Repository': 'git@git.staging.resin.io:jviotti/helloresin.git'
Commit: '1234'
'Device Type': 'raspberry-pi'
it 'should not remove not empty arrays', ->
object = { Array: [ 1, 2, 3 ] }
expect(tableHelpers.prepareObject(object)).to.deep.equal(object)
it 'should preserve false fields', ->
object = { Test: false }
expect(tableHelpers.prepareObject(object)).to.deep.equal(object)
describe '#processTableContents()', ->
checkIfArray = (input) ->
result = tableHelpers.processTableContents(input, _.identity)
expect(result).to.be.an.instanceof(Array)
it 'should always return an array', ->
checkIfArray(OBJECTS.basic)
checkIfArray([ OBJECTS.basic ])
checkIfArray([ 'contents' ])
it 'should get rid of keys not starting with letters', ->
result = tableHelpers.processTableContents(OBJECTS.basic, _.identity)
expect(result).to.deep.equal [
{
Hello: 'world'
Hey: 'there'
}
]
it 'should allow a null/undefined map function without corrupting the data', ->
for map in [ null, undefined ]
result = tableHelpers.processTableContents([ OBJECTS.valid ], map)
expect(result).to.deep.equal([ OBJECTS.valid ])
describe '#getDefaultContentsOrdering()', ->
it 'should return undefined if no contents', ->
expect(tableHelpers.getDefaultContentsOrdering()).to.be.undefined
it 'should return undefined if contents is empty', ->
expect(tableHelpers.getDefaultContentsOrdering([])).to.be.undefined
it 'should return undefined if contents is not an array of objects', ->
inputs = [
[ 1, 2, 3 ]
[ '1', '2', '3' ]
[ _.identity ]
]
for input in inputs
expect(tableHelpers.getDefaultContentsOrdering(input)).to.be.undefined
it 'should return an array containing all the object keys', ->
result = tableHelpers.getDefaultContentsOrdering([ OBJECTS.valid ])
for key, value of OBJECTS.valid
expect(result.indexOf(key)).to.not.equal(-1)
describe '#normaliseOrdering()', ->
it 'should return titleized words if ordering is not empty', ->
ordering = [ 'one', 'two', 'three' ]
result = tableHelpers.normaliseOrdering(ordering, {})
expect(result).to.deep.equal([ 'One', 'Two', 'Three' ])
it 'should return an array containing all the object keys', ->
result = tableHelpers.normaliseOrdering(null, [ OBJECTS.valid ])
for key, value of OBJECTS.valid
expect(result.indexOf(key)).to.not.equal(-1)
it 'should not give precendence to names from the map', ->
ordering = [ 'id', 'ip_address', 'name' ]
result = tableHelpers.normaliseOrdering(ordering, {})
expect(result).to.deep.equal([ 'ID', 'IP Address', 'Name' ])
it 'should preverse a string that is the result of a map lookup', ->
ordering = [ 'ID', 'IP Address' ]
result = tableHelpers.normaliseOrdering(ordering, {})
expect(result).to.deep.equal(ordering)

View File

@ -1,23 +0,0 @@
cliff = require('cliff')
tableHelpers = require('./table-helpers')
# TODO: Maybe there is a (sane) way to test this, given
# that the result is not automatically printed by cliff?
exports.horizontal = (contents, ordering, colours) ->
return if not contents?
contents = tableHelpers.processTableContents(contents)
ordering = tableHelpers.normaliseOrdering(ordering, contents)
return cliff.stringifyObjectRows(contents, ordering, colours)
exports.vertical = (contents, ordering) ->
return if not contents?
contents = tableHelpers.processTableContents(contents)
ordering = tableHelpers.normaliseOrdering(ordering, contents)
# TODO: Add some kind of separator to make clear
# when we're printing another content item
result = []
for item in contents
for next in ordering
result.push("#{next}: #{item[next]}")
return result.join('\n')

View File

@ -1,42 +0,0 @@
expect = require('chai').expect
table = require('./table')
OBJECTS =
valid:
One: 'one'
Two: 'two'
Three: 'three'
describe 'Table:', ->
describe '#vertical()', ->
it 'should return a string respecting the ordering', ->
ordering = [ 'One', 'Two', 'Three' ]
result = table.vertical(OBJECTS.valid, null, ordering).split('\n')
expected = [
'One: one'
'Two: two'
'Three: three'
]
expect(result).to.deep.equal(expected)
it 'should be able to print everything without explicit ordering', ->
result = table.vertical(OBJECTS.valid, null).split('\n')
expected = [
'One: one'
'Two: two'
'Three: three'
]
for line in expected
expect(result.indexOf(line)).to.not.equal(-1)
it 'should return undefined if contents does not exist', ->
expect(table.vertical(undefined)).to.be.undefined
describe '#horizontal()', ->
it 'should return undefined if contents does not exist', ->
expect(table.horizontal(undefined)).to.be.undefined

View File

@ -1,99 +0,0 @@
_ = require('lodash')
inquirer = require('inquirer')
ProgressBar = require('progress')
exports.table = require('./table/table')
exports.register = (callback) ->
inquirer.prompt([
{
type: 'input'
name: 'email'
message: 'Email'
}
{
type: 'input'
name: 'username'
message: 'Username'
}
{
type: 'password'
name: 'password'
message: 'Password'
validate: (input) ->
if input.length < 8
return 'Password should be 8 characters long'
return true
}
], _.partial(callback, null))
exports.login = (callback) ->
inquirer.prompt([
{
type: 'input'
name: 'username'
message: 'Username'
}
{
type: 'password'
name: 'password'
message: 'Password'
}
], _.partial(callback, null))
exports.select = (message, list, callback) ->
inquirer.prompt [
{
type: 'list'
name: 'option'
message: message or 'Select an option'
choices: list
}
], (response) ->
return callback(null, response.option)
exports.confirmRemoval = (name, callback) ->
inquirer.prompt [
{
type: 'confirm'
name: 'confirmed'
message: "Are you sure you want to delete the #{name}?"
default: false
}
], (response) ->
return callback(null, response.confirmed)
exports.confirm = (message, callback) ->
inquirer.prompt [
{
type: 'confirm'
name: 'confirmed'
message: message
default: false
}
], (response) ->
return callback(null, response.confirmed)
exports.ask = (question, callback) ->
inquirer.prompt [
{
type: 'input'
name: 'answer'
message: question
validate: (input) ->
return _.isString(input) and not _.isEmpty(input)
}
], (response) ->
return callback(null, response.answer)
exports.Progress = class Progress extends ProgressBar
constructor: (message, size) ->
message = "#{message} [:bar] :percent :etas"
options =
complete: '='
incomplete: ' '
width: 40
total: size
super(message, options)

View File

@ -62,6 +62,7 @@
"progress-bar": "~0.1.1",
"progress-stream": "^0.5.0",
"resin-sdk": "git+ssh://git@bitbucket.org/rulemotion/resin-sdk.git",
"resin-cli-visuals": "git+ssh://git@bitbucket.org/rulemotion/resin-cli-visuals.git",
"underscore.string": "~2.4.0",
"yeoman-environment": "^1.2.0"
}