Skip to content

Commit 6c75cbb

Browse files
yaroslav-codefreshpasha-codefresh
authored andcommitted
Formatting output (#236)
* SAAS-927 -- formatting layer added * SAAS-928 -- builds output formatted * SAAS-927 -- return from formatter immediately when no styles * SAAS-928 -- colors redone as web ui * remove italic * move pretty only for workflows * update version
1 parent 2303491 commit 6c75cbb

File tree

7 files changed

+239
-34
lines changed

7 files changed

+239
-34
lines changed

lib/interface/cli/commands/workflow/get.cmd.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
const debug = require('debug')('codefresh:cli:create:context');
21
const Command = require('../../Command');
32
const CFError = require('cf-errors');
43
const _ = require('lodash');
54
const DEFAULTS = require('../../defaults');
65
const { workflow , pipeline } = require('../../../../logic').api;
7-
const { specifyOutputForSingle, specifyOutputForArray } = require('../../helpers/get');
6+
const { specifyOutputForArray } = require('../../helpers/get');
87
const getRoot = require('../root/get.cmd');
9-
const { printError } = require('../../helpers/general');
108

119
const command = new Command({
1210
command: 'builds [id..]',
@@ -51,6 +49,10 @@ const command = new Command({
5149
type: Array,
5250
default: [],
5351
})
52+
.option('pretty', {
53+
describe: 'Use colors and signs for output',
54+
group: 'Output Options',
55+
})
5456
.example('codefresh get build ID', 'Get build ID')
5557
.example('codefresh get builds', 'Get all builds')
5658
.example('codefresh get builds --pipeline-id ID', 'Get all builds that are executions of pipeline "ID"')
@@ -60,10 +62,7 @@ const command = new Command({
6062
},
6163
handler: async (argv) => {
6264
const workflowIds = argv.id;
63-
const limit = argv.limit;
64-
const page = argv.page;
65-
const status = argv.status;
66-
const trigger = argv.trigger;
65+
const { limit, page, status, trigger } = argv;
6766
const pipelineNames = !_.isArray(argv['pipeline-name']) ? [(argv['pipeline-name'])] : argv['pipeline-name'];
6867
const pipelineIds = !_.isArray(argv['pipeline-id']) ? [(argv['pipeline-id'])] : argv['pipeline-id'];
6968

@@ -96,7 +95,7 @@ const command = new Command({
9695
pipelineIds,
9796
});
9897
}
99-
specifyOutputForArray(argv.output, workflows);
98+
specifyOutputForArray(argv.output, workflows, argv.pretty); // todo: extract output layer
10099
},
101100
});
102101

lib/interface/cli/helpers/get.js

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
/* eslint-disable indent */
2-
const _ = require('lodash');
3-
const columnify = require('columnify');
4-
const yaml = require('js-yaml');
5-
const draftlog = require('draftlog');
2+
const _ = require('lodash');
3+
const columnify = require('columnify');
4+
const chalk = require('chalk');
5+
const yaml = require('js-yaml');
6+
const draftlog = require('draftlog');
7+
const { Formatter } = require('../../../output');
8+
const { FormatterRegistry } = require('../../../output/formatters');
9+
10+
const NOOP_FORMATTER = new Formatter();
11+
12+
const prettyOpts = {
13+
headingTransform: chalk.bold,
14+
columnSplitter: ' ',
15+
};
616

717
draftlog.into(console);
818

@@ -16,7 +26,7 @@ const print = (output) => {
1626
};
1727

1828
//i tried that this function will be dynamic (with the keys). it is also possible to add an array with all the fields if you think it better
19-
const _printArrayTable = (data) => {
29+
const _printArrayTable = (data, pretty = false) => {
2030
if (data.length === 0) {
2131
console.log('no available resources');
2232
} else {
@@ -30,49 +40,59 @@ const _printArrayTable = (data) => {
3040
});
3141
res.push(obj);
3242
});
33-
const columns = columnify(res);
43+
const columns = columnify(res, pretty ? prettyOpts : {});
3444
print(columns);
3545
}
3646
};
3747

38-
const _printSingleTable = (info) => {
48+
const _printSingleTable = (info, pretty = false) => {
3949
const keys = Object.keys(info);
4050
const res = [];
4151
const obj = [];
4252
_.forEach(keys, (key) => {
4353
obj[key.toUpperCase()] = info[key];
4454
});
4555
res.push(obj);
46-
const columns = columnify(res);
56+
const columns = columnify(res, pretty ? prettyOpts : {});
4757
print(columns);
4858
};
4959

5060

51-
const specifyOutputForSingle = (type, enitity) => {
61+
// todo: extract output layer from commands
62+
const specifyOutputForSingle = (type, entity, pretty = false) => {
63+
let formatter = NOOP_FORMATTER;
64+
if (pretty && entity) {
65+
formatter = FormatterRegistry.get(entity.constructor.name);
66+
}
5267
switch (type) {
5368
case 'json':
54-
print(enitity.toJson());
69+
print(entity.toJson());
5570
break;
5671
case 'yaml':
57-
print(enitity.toYaml());
72+
print(entity.toYaml());
5873
break;
5974
case 'name':
60-
print(enitity.toName());
75+
print(entity.toName());
6176
break;
6277
case 'wide':
63-
_printSingleTable(enitity.toWide());
78+
_printSingleTable(formatter.apply(entity.toWide()), pretty);
6479
break;
6580
default:
66-
_printSingleTable(enitity.toDefault());
81+
_printSingleTable(formatter.apply(entity.toDefault()), pretty);
6782
}
6883
};
6984

7085

71-
const specifyOutputForArray = (type, enitities) => {
86+
// todo: extract output layer from commands
87+
const specifyOutputForArray = (type, entities, pretty = false) => {
88+
let formatter = NOOP_FORMATTER;
89+
if (pretty && entities.length) {
90+
formatter = FormatterRegistry.get(entities[0].constructor.name);
91+
}
7292
switch (type) {
7393
case 'json':
7494
const jsonArray = [];
75-
_.forEach(enitities, (entity) => {
95+
_.forEach(entities, (entity) => {
7696
jsonArray.push(entity.info);
7797
});
7898

@@ -87,7 +107,7 @@ const specifyOutputForArray = (type, enitities) => {
87107
let yamlArray = {
88108
items: [],
89109
};
90-
_.forEach(enitities, (entity) => {
110+
_.forEach(entities, (entity) => {
91111
yamlArray.items.push(entity.info);
92112
});
93113

@@ -98,28 +118,28 @@ const specifyOutputForArray = (type, enitities) => {
98118
}
99119
break;
100120
case 'name':
101-
_.forEach(enitities, (entity) => {
121+
_.forEach(entities, (entity) => {
102122
console.log(entity.toName());
103123
});
104124
break;
105125
case 'id':
106-
_.forEach(enitities, (entity) => {
126+
_.forEach(entities, (entity) => {
107127
console.log(entity.toId());
108128
});
109129
break;
110130
case 'wide':
111131
const wideArray = [];
112-
_.forEach(enitities, (entity) => {
113-
wideArray.push(entity.toWide());
132+
_.forEach(entities, (entity) => {
133+
wideArray.push(formatter.apply(entity.toWide()));
114134
});
115-
_printArrayTable(wideArray);
135+
_printArrayTable(wideArray, pretty);
116136
break;
117137
default:
118138
const defaultArray = [];
119-
_.forEach(enitities, (entity) => {
120-
defaultArray.push(entity.toDefault());
139+
_.forEach(entities, (entity) => {
140+
defaultArray.push(formatter.apply(entity.toDefault()));
121141
});
122-
_printArrayTable(defaultArray);
142+
_printArrayTable(defaultArray, pretty);
123143
}
124144
};
125145

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const { Formatter, Style } = require('../../output');
2+
3+
const CHECK_SIGN = '✓';
4+
const CROSS_SIGN = '✗';
5+
const PLAY_SIGN = '▸';
6+
const TWO_ARR_DOWN_SIGN = '⇊';
7+
const CIRCLE_SIGN = '◉';
8+
const BRACKET_SIGN = '«';
9+
10+
const STATUS_CHOICES = {
11+
success: Style.green.appendedBy(CHECK_SIGN),
12+
error: Style.red.appendedBy(CROSS_SIGN),
13+
terminated: Style.red.appendedBy(CROSS_SIGN),
14+
pending: Style.yellow.appendedBy(CIRCLE_SIGN),
15+
elected: Style.yellow.appendedBy(BRACKET_SIGN),
16+
running: Style.cyan.appendedBy(PLAY_SIGN),
17+
terminating: Style.red.appendedBy(TWO_ARR_DOWN_SIGN),
18+
};
19+
20+
const FORMATTER = Formatter.build()
21+
.style('status', input => STATUS_CHOICES[input].bold.uppercase(input));
22+
23+
module.exports = FORMATTER;

lib/output/formatters/index.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const _ = require('lodash');
2+
const recursive = require('recursive-readdir');
3+
const path = require('path');
4+
const {Formatter} = require('../index');
5+
6+
const formatters = {};
7+
const EMPTY_FORMATTER = new Formatter();
8+
9+
10+
class FormatterRegistry {
11+
static get(formatterName) {
12+
return formatters[formatterName] || EMPTY_FORMATTER;
13+
}
14+
15+
static set(name, formatter) {
16+
formatters[name] = formatter;
17+
}
18+
}
19+
20+
recursive(path.resolve(__dirname), (err, files) => {
21+
_.forEach(files, (file) => {
22+
const ending = '.formatter.js';
23+
if (file.endsWith(ending)) {
24+
const formatter = require(file);
25+
const entityName = path.parse(file).base.replace(ending, '');
26+
FormatterRegistry.set(entityName, formatter);
27+
}
28+
});
29+
});
30+
31+
module.exports = {
32+
FormatterRegistry,
33+
};

lib/output/index.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const _ = require('lodash');
2+
const Style = require('./style');
3+
4+
class Formatter {
5+
constructor(styles) {
6+
this.styles = styles || {};
7+
}
8+
9+
apply(source) {
10+
const keys = _.keys(source);
11+
if (!keys || !_.keys(this.styles)) {
12+
return source;
13+
}
14+
const obj = { ...source };
15+
keys.forEach((key) => {
16+
const prop = obj[key];
17+
obj[key] = this._applyStyles(key, prop);
18+
});
19+
return obj;
20+
}
21+
22+
style(key, func) {
23+
if (typeof func === 'function') {
24+
this.styles[key] = func;
25+
}
26+
return this;
27+
}
28+
29+
_applyStyles(key, prop) {
30+
const style = this.styles[key] || _.identity;
31+
return style(prop);
32+
}
33+
34+
static build() {
35+
return new Formatter();
36+
}
37+
}
38+
39+
module.exports = {
40+
Formatter,
41+
Style,
42+
};

lib/output/style.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
const chalk = require('chalk');
2+
const _ = require('lodash');
3+
4+
/**
5+
* chalk styles
6+
*
7+
* @see https://github.com/chalk/chalk#styles
8+
* */
9+
const STYLES = [
10+
'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray',
11+
12+
'bgBlack', 'bgRed', 'bgGreen', 'bgYellow', 'bgBlue', 'bgMagenta', 'bgCyan', 'bgWhite', 'bgBlackBright',
13+
'bgRedBright', 'bgGreenBright', 'bgYellowBright', 'bgBlueBright', 'bgMagentaBright', 'bgCyanBright', 'bgWhiteBright',
14+
15+
'reset', 'bold', 'dim', 'italic', 'underline', 'inverse', 'hidden', 'strikethrough', 'visible',
16+
];
17+
18+
const MODIFIERS = {
19+
uppercase: inp => inp.toUpperCase(),
20+
lowercase: inp => inp.toLowerCase(),
21+
trim: inp => inp.trim(),
22+
};
23+
24+
const symbolAppender = (arr, styleCollector) => {
25+
return (symbol) => {
26+
arr.push(symbol);
27+
return styleCollector;
28+
};
29+
};
30+
31+
const chainedGetter = (arr, symbol, styleCollector) => {
32+
return {
33+
get: () => {
34+
arr.push(symbol);
35+
return styleCollector;
36+
},
37+
};
38+
};
39+
40+
41+
/**
42+
* Simple wrapper over chalk.
43+
* Applies all chalk styles and some self-defined.
44+
*
45+
* usage: console.log(Style.red.italic.bold.uppercase.appendedBy('some char')('some text'))
46+
* */
47+
let Style = function Style() {
48+
const styles = [];
49+
const after = [];
50+
const before = [];
51+
const modifiers = [];
52+
53+
function _Style(input = '') {
54+
const style = styles.reduce((_chalk, _style) => _chalk[_style], chalk); // apply chalk styles
55+
const beforeInp = before.length ? `${before.join(' ')} ` : '';
56+
const afterInp = after.length ? ` ${after.join(' ')}` : '';
57+
const output = modifiers.reduce((inp, type) => MODIFIERS[type](inp), input); // apply modifiers
58+
return style(`${beforeInp}${output}${afterInp}`);
59+
}
60+
61+
// for chain-like style
62+
const getters = {};
63+
STYLES.forEach((styleName) => {
64+
getters[styleName] = chainedGetter(styles, styleName, _Style);
65+
});
66+
_.keys(MODIFIERS).forEach((modifierName) => {
67+
getters[modifierName] = chainedGetter(modifiers, modifierName, _Style);
68+
});
69+
Object.defineProperties(_Style, getters);
70+
71+
_Style.appendedBy = symbolAppender(after, _Style);
72+
_Style.prependedBy = symbolAppender(before, _Style);
73+
return _Style;
74+
};
75+
76+
/**
77+
* Allows not to call Style(), e.g.:
78+
*
79+
* Style().red -> Style.red
80+
* */
81+
Style = new Proxy(Style, {
82+
get(target, p) {
83+
return target()[p];
84+
},
85+
});
86+
87+
module.exports = Style;
88+

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codefresh",
3-
"version": "0.8.96",
3+
"version": "0.8.97",
44
"description": "Codefresh command line utility",
55
"main": "index.js",
66
"preferGlobal": true,

0 commit comments

Comments
 (0)