From 34346dd3d005a45ca0284742afd235cfee78bb3c Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Wed, 19 Nov 2014 14:22:45 -0400 Subject: [PATCH] Refactor table utilities into tableHelpers --- lib/table/table-helpers.coffee | 67 +++++++++++++ lib/table/table-helpers.spec.coffee | 148 ++++++++++++++++++++++++++++ lib/table/table.coffee | 67 ++----------- lib/table/table.spec.coffee | 133 ++----------------------- package.json | 3 +- 5 files changed, 234 insertions(+), 184 deletions(-) create mode 100644 lib/table/table-helpers.coffee create mode 100644 lib/table/table-helpers.spec.coffee diff --git a/lib/table/table-helpers.coffee b/lib/table/table-helpers.coffee new file mode 100644 index 00000000..e6389cf0 --- /dev/null +++ b/lib/table/table-helpers.coffee @@ -0,0 +1,67 @@ +_ = require('lodash') +_.str = require('underscore.string') + +KEY_DISPLAY_MAP = + app_name: 'Name' + last_seen_time: 'Last Seen' + ip_address: 'IP Address' + id: 'ID' + +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? + key = key.replace('_', ' ') + return _.str.titleize(key) + +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) -> + + # For some reason, _.isEmpty returns true for numbers + return _.isEmpty(value) and not _.isNumber(value) + + return object + +exports.processTableContents = (contents, map) -> + + # Allows us to simplify the algorithm by not + # concerning about different input types + if not _.isArray(contents) + contents = [ contents ] + + contents = _.map(contents, map or _.identity) + contents = _.map(contents, exports.prepareObject) + return contents + +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, _.str.titleize) + return exports.getDefaultContentsOrdering(contents) diff --git a/lib/table/table-helpers.spec.coffee b/lib/table/table-helpers.spec.coffee new file mode 100644 index 00000000..61b767ee --- /dev/null +++ b/lib/table/table-helpers.spec.coffee @@ -0,0 +1,148 @@ +expect = require('chai').expect +_ = 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') + + 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) + + 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 be able to manipulate the contents', -> + result = tableHelpers.processTableContents { hey: 'there' }, (item) -> + item.hey = 'yo' + return item + + expect(result).to.deep.equal([ Hey: 'yo' ]) + + 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) diff --git a/lib/table/table.coffee b/lib/table/table.coffee index b71e8fe9..305b6e02 100644 --- a/lib/table/table.coffee +++ b/lib/table/table.coffee @@ -1,72 +1,19 @@ -_ = require('lodash') cliff = require('cliff') - -KEY_DISPLAY_MAP = - commit: 'Commit' - app_name: 'Name' - git_repository: 'Git Repository' - device_type: 'Device Type' - id: 'ID' - -startsWithLetter = (string) -> - firstLetter = _.first(string) - return /[a-z|A-Z]/.test(firstLetter) - -renameObjectKey = (object, key, newKey) -> - object[newKey] = object[key] - delete object[key] - -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) - - displayKey = KEY_DISPLAY_MAP[key] - if displayKey? - renameObjectKey(object, key, displayKey) - - object = _.omit object, (value, key) -> - - # For some reason, _.isEmpty returns true for numbers - return _.isEmpty(value) and not _.isNumber(value) - - return object - -exports.processTableContents = (contents, map) -> - - # Allows us to simplify the algorithm by not - # concerning about different input types - if not _.isArray(contents) - contents = [ contents ] - - contents = _.map(contents, map or _.identity) - contents = _.map(contents, exports.prepareObject) - return contents - -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) +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, map, ordering, colours) -> - contents = exports.processTableContents(contents, map) - ordering ?= exports.getDefaultContentsOrdering(contents) + contents = tableHelpers.processTableContents(contents, map) + ordering = tableHelpers.normaliseOrdering(ordering, contents) return cliff.stringifyObjectRows(contents, ordering, colours) exports.vertical = (contents, map, ordering) -> - contents = exports.processTableContents(contents, map) - ordering ?= exports.getDefaultContentsOrdering(contents) + contents = tableHelpers.processTableContents(contents, map) + 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 diff --git a/lib/table/table.spec.coffee b/lib/table/table.spec.coffee index 939371bc..e56ed896 100644 --- a/lib/table/table.spec.coffee +++ b/lib/table/table.spec.coffee @@ -1,136 +1,23 @@ expect = require('chai').expect -_ = require('lodash') table = require('./table') 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' + One: 'one' + Two: 'two' + Three: 'three' describe 'Table:', -> - describe '#prepareObject()', -> - - it 'should get rid of keys not starting with letters', -> - expect(table.prepareObject(OBJECTS.basic)).to.deep.equal - hello: 'world' - Hey: 'there' - - it 'should get rid of keys not starting with letters recursively', -> - expect(table.prepareObject(OBJECTS.recursive)).to.deep.equal - hello: 'world' - Hey: - value: - data: 'There' - - it 'should do proper key renamings', -> - expect(table.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(table.prepareObject(object)).to.deep.equal(object) - - describe '#processTableContents()', -> - - checkIfArray = (input) -> - result = table.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 be able to manipulate the contents', -> - result = table.processTableContents { hey: 'there' }, (item) -> - item.hey = 'yo' - return item - - expect(result).to.deep.equal([ hey: 'yo' ]) - - it 'should get rid of keys not starting with letters', -> - result = table.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 = table.processTableContents([ OBJECTS.valid ], map) - expect(result).to.deep.equal([ OBJECTS.valid ]) - - describe '#getDefaultContentsOrdering()', -> - - it 'should return undefined if no contents', -> - expect(table.getDefaultContentsOrdering()).to.be.undefined - - it 'should return undefined if contents is empty', -> - expect(table.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(table.getDefaultContentsOrdering(input)).to.be.undefined - - it 'should return an array containing all the object keys', -> - result = table.getDefaultContentsOrdering([ OBJECTS.valid ]) - console.log result - for key, value of OBJECTS.valid - expect(result.indexOf(key)).to.not.equal(-1) - describe '#vertical()', -> it 'should return a string respecting the ordering', -> - ordering = [ 'one', 'two', 'three' ] + ordering = [ 'One', 'Two', 'Three' ] result = table.vertical(OBJECTS.valid, null, ordering).split('\n') expected = [ - 'one: one' - 'two: two' - 'three: three' + 'One: one' + 'Two: two' + 'Three: three' ] expect(result).to.deep.equal(expected) @@ -138,9 +25,9 @@ describe 'Table:', -> 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' + 'One: one' + 'Two: two' + 'Three: three' ] for line in expected diff --git a/package.json b/package.json index c764449a..28ed9745 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "bluebird": "~2.3.11", "open": "0.0.5", "inquirer": "~0.8.0", - "cliff": "~0.1.9" + "cliff": "~0.1.9", + "underscore.string": "~2.4.0" } }