pixl-cli 1.0.21 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/string-width/.editorconfig +12 -0
- package/agents/string-width/.gitattributes +1 -0
- package/agents/string-width/.github/security.md +3 -0
- package/agents/string-width/.github/workflows/main.yml +22 -0
- package/agents/string-width/index.d.ts +39 -0
- package/agents/string-width/index.js +207 -0
- package/agents/string-width/index.test-d.ts +7 -0
- package/agents/string-width/license +9 -0
- package/agents/string-width/package.json +65 -0
- package/agents/string-width/readme.md +66 -0
- package/agents/string-width/test.js +339 -0
- package/agents/widest-line/.editorconfig +12 -0
- package/agents/widest-line/.gitattributes +1 -0
- package/agents/widest-line/.github/security.md +3 -0
- package/agents/widest-line/.github/workflows/main.yml +21 -0
- package/agents/widest-line/index.d.ts +12 -0
- package/agents/widest-line/index.js +11 -0
- package/agents/widest-line/license +9 -0
- package/agents/widest-line/package.json +60 -0
- package/agents/widest-line/readme.md +26 -0
- package/agents/widest-line/test.js +8 -0
- package/agents/word-wrap/.editorconfig +13 -0
- package/agents/word-wrap/.eslintrc.json +122 -0
- package/agents/word-wrap/.gitattributes +10 -0
- package/agents/word-wrap/.github/workflows/publish.yml +19 -0
- package/agents/word-wrap/.travis.yml +13 -0
- package/agents/word-wrap/.verb.md +114 -0
- package/agents/word-wrap/LICENSE +21 -0
- package/agents/word-wrap/README.md +201 -0
- package/agents/word-wrap/bower.json +60 -0
- package/agents/word-wrap/index.d.ts +50 -0
- package/agents/word-wrap/index.js +61 -0
- package/agents/word-wrap/package.json +77 -0
- package/agents/word-wrap/test.js +69 -0
- package/cli.js +17 -10
- package/package.json +3 -8
- package/test.js +20 -0
- package/util.js +15 -0
- package/width.js +75 -0
- package/wrap.js +141 -0
package/cli.js
CHANGED
|
@@ -6,10 +6,14 @@ var fs = require('fs');
|
|
|
6
6
|
var readline = require('readline');
|
|
7
7
|
var path = require('path');
|
|
8
8
|
var chalk = require('chalk');
|
|
9
|
-
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
var wordWrap = require('
|
|
9
|
+
|
|
10
|
+
var Util = require('./util');
|
|
11
|
+
var Width = require('./width');
|
|
12
|
+
var wordWrap = require('./wrap');
|
|
13
|
+
|
|
14
|
+
var ansiPattern = Util.ansiPattern;
|
|
15
|
+
var stringWidth = Width.stringWidth;
|
|
16
|
+
var widestLine = Width.widestLine;
|
|
13
17
|
|
|
14
18
|
var Tools = require('pixl-tools');
|
|
15
19
|
var Args = require('pixl-args');
|
|
@@ -20,7 +24,7 @@ var cli = module.exports = {
|
|
|
20
24
|
// CLI args hash
|
|
21
25
|
args: args.get(),
|
|
22
26
|
|
|
23
|
-
// expose some
|
|
27
|
+
// expose some utility functions and third-party modules
|
|
24
28
|
chalk: chalk,
|
|
25
29
|
stringWidth: stringWidth,
|
|
26
30
|
widestLine: widestLine,
|
|
@@ -28,10 +32,7 @@ var cli = module.exports = {
|
|
|
28
32
|
Tools: Tools,
|
|
29
33
|
|
|
30
34
|
// for stripping colors:
|
|
31
|
-
ansiPattern:
|
|
32
|
-
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
33
|
-
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
|
|
34
|
-
].join('|'), 'g'),
|
|
35
|
+
ansiPattern: ansiPattern,
|
|
35
36
|
|
|
36
37
|
mapArgs: function(aliases) {
|
|
37
38
|
// apply alias lookup to a set of args
|
|
@@ -99,7 +100,7 @@ var cli = module.exports = {
|
|
|
99
100
|
repeat: function(text, amount) {
|
|
100
101
|
// repeat string by specified number of times
|
|
101
102
|
if (!amount || (amount < 0)) return "";
|
|
102
|
-
return
|
|
103
|
+
return (''+text).repeat(amount);
|
|
103
104
|
},
|
|
104
105
|
|
|
105
106
|
space: function(amount) {
|
|
@@ -112,6 +113,12 @@ var cli = module.exports = {
|
|
|
112
113
|
return text + this.space(width - stringWidth(text));
|
|
113
114
|
},
|
|
114
115
|
|
|
116
|
+
emoji: function(emoji) {
|
|
117
|
+
// Draw the emoji, restore its starting cursor position, then explicitly move
|
|
118
|
+
// forward two cells. This overrides each terminal's automatic cursor advance.
|
|
119
|
+
return '\u001b7' + emoji + '\u001b8\u001b[2C';
|
|
120
|
+
},
|
|
121
|
+
|
|
115
122
|
center: function(text, width) {
|
|
116
123
|
// center string horizontally
|
|
117
124
|
var self = this;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixl-cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Tools for building command-line apps for Node.js.",
|
|
5
5
|
"author": "Joseph Huckaby <jhuckaby@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/jhuckaby/pixl-cli",
|
|
@@ -22,14 +22,9 @@
|
|
|
22
22
|
"table"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"pixl-class": "^1.0.3",
|
|
26
25
|
"pixl-args": "^1.0.0",
|
|
27
|
-
"pixl-tools": "^
|
|
28
|
-
"chalk": "2.4.1"
|
|
29
|
-
"string-width": "4.2.0",
|
|
30
|
-
"widest-line": "3.0.0",
|
|
31
|
-
"repeating": "3.0.0",
|
|
32
|
-
"word-wrap": "1.2.4"
|
|
26
|
+
"pixl-tools": "^2.0.2",
|
|
27
|
+
"chalk": "2.4.1"
|
|
33
28
|
},
|
|
34
29
|
"devDependencies": {}
|
|
35
30
|
}
|
package/test.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
var cli = require('.');
|
|
2
|
+
var chalk = cli.chalk;
|
|
3
|
+
|
|
4
|
+
cli.global();
|
|
5
|
+
|
|
6
|
+
var fixedEmoji = function(emoji) {
|
|
7
|
+
// Draw the emoji, restore its starting cursor position, then explicitly move
|
|
8
|
+
// forward two cells. This overrides each terminal's automatic cursor advance.
|
|
9
|
+
return '\u001b7' + emoji + '\u001b8\u001b[2C';
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
println( table([
|
|
13
|
+
|
|
14
|
+
['Name', 'Status'],
|
|
15
|
+
['Success', fixedEmoji('✅') + ' ' + chalk.rgb(0, 201, 80).bold('Success')],
|
|
16
|
+
['Warning', fixedEmoji('⚠️') + ' ' + chalk.rgb(240, 177, 0).bold('Warning')],
|
|
17
|
+
['Critical', fixedEmoji('☢️') + ' ' + chalk.rgb(173, 70, 255).bold('Critical')],
|
|
18
|
+
['Abort', fixedEmoji('🚫') + ' ' + chalk.rgb(127, 127, 127).bold('Abort')]
|
|
19
|
+
|
|
20
|
+
]) );
|
package/util.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Shared Unicode and terminal utilities for pixl-cli.
|
|
2
|
+
|
|
3
|
+
// Match ANSI terminal escape sequences so they can be ignored during measurement.
|
|
4
|
+
var ansiPattern = new RegExp([
|
|
5
|
+
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
6
|
+
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
|
|
7
|
+
].join('|'), 'g');
|
|
8
|
+
|
|
9
|
+
// Split strings into user-perceived characters, including complete emoji sequences.
|
|
10
|
+
var graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
ansiPattern: ansiPattern,
|
|
14
|
+
graphemeSegmenter: graphemeSegmenter
|
|
15
|
+
};
|
package/width.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Terminal display-width measurement for pixl-cli.
|
|
2
|
+
|
|
3
|
+
var Util = require('./util');
|
|
4
|
+
var ansiPattern = Util.ansiPattern;
|
|
5
|
+
var graphemeSegmenter = Util.graphemeSegmenter;
|
|
6
|
+
|
|
7
|
+
// These built-in Unicode properties let us identify emoji without carrying a large,
|
|
8
|
+
// generated lookup table. VS16 changes text-default symbols to emoji presentation.
|
|
9
|
+
var zeroWidthClusterPattern = /^(?:\p{Default_Ignorable_Code_Point}|\p{Control}|\p{Format}|\p{Nonspacing_Mark}|\p{Enclosing_Mark}|\p{Surrogate})+$/u;
|
|
10
|
+
var emojiPattern = /\p{Emoji}/u;
|
|
11
|
+
var emojiPresentationPattern = /\p{Emoji_Presentation}/u;
|
|
12
|
+
var emojiModifierPattern = /\p{Emoji_Modifier}/u;
|
|
13
|
+
var regionalIndicatorPattern = /\p{Regional_Indicator}/gu;
|
|
14
|
+
var extendedPictographicPattern = /\p{Extended_Pictographic}/gu;
|
|
15
|
+
var keycapPattern = /^[\d#*]\uFE0F?\u20E3$/;
|
|
16
|
+
var keycapBaseWithVs16Pattern = /^[\d#*]\uFE0F$/;
|
|
17
|
+
|
|
18
|
+
function stringWidth(text) {
|
|
19
|
+
// Measure Western Unicode and emoji terminal columns, ignoring ANSI styling.
|
|
20
|
+
if ((typeof(text) != 'string') || !text.length) return 0;
|
|
21
|
+
text = text.replace(ansiPattern, '');
|
|
22
|
+
if (!text.length) return 0;
|
|
23
|
+
|
|
24
|
+
// Printable ASCII needs no Unicode segmentation and is by far the common case.
|
|
25
|
+
if (text.match(/^[\u0020-\u007E]*$/)) return text.length;
|
|
26
|
+
|
|
27
|
+
var width = 0;
|
|
28
|
+
var segments = graphemeSegmenter.segment(text);
|
|
29
|
+
for (var item of segments) {
|
|
30
|
+
var segment = item.segment;
|
|
31
|
+
if (zeroWidthClusterPattern.test(segment)) continue;
|
|
32
|
+
|
|
33
|
+
// Native emoji generally occupy two terminal columns. The extra checks cover
|
|
34
|
+
// text-default emoji switched by VS16, flags, keycaps and unqualified sequences.
|
|
35
|
+
var regionalIndicators = segment.match(regionalIndicatorPattern);
|
|
36
|
+
var pictographs = segment.match(extendedPictographicPattern);
|
|
37
|
+
var isEmoji = false;
|
|
38
|
+
|
|
39
|
+
if (segment.includes('\u20E3')) {
|
|
40
|
+
isEmoji = keycapPattern.test(segment);
|
|
41
|
+
}
|
|
42
|
+
else if (regionalIndicators) {
|
|
43
|
+
isEmoji = (regionalIndicators.length >= 2);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
isEmoji = emojiPresentationPattern.test(segment) ||
|
|
47
|
+
(emojiPattern.test(segment) && segment.includes('\uFE0F') &&
|
|
48
|
+
!keycapBaseWithVs16Pattern.test(segment)) ||
|
|
49
|
+
(emojiPattern.test(segment) && emojiModifierPattern.test(segment)) ||
|
|
50
|
+
(segment.includes('\u200D') && pictographs && (pictographs.length >= 2));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
width += isEmoji ? 2 : 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return width;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function widestLine(text) {
|
|
60
|
+
// Return the visual width of the widest line in a multi-line string.
|
|
61
|
+
var width = 0;
|
|
62
|
+
text.split(/\n/).forEach( function(line) {
|
|
63
|
+
width = Math.max( width, stringWidth(line) );
|
|
64
|
+
} );
|
|
65
|
+
return width;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Preserve the compatibility aliases provided by the old CommonJS dependencies.
|
|
69
|
+
stringWidth.default = stringWidth;
|
|
70
|
+
widestLine.default = widestLine;
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
stringWidth: stringWidth,
|
|
74
|
+
widestLine: widestLine
|
|
75
|
+
};
|
package/wrap.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Display-width-aware word wrapping for pixl-cli.
|
|
2
|
+
|
|
3
|
+
var Util = require('./util');
|
|
4
|
+
var stringWidth = require('./width').stringWidth;
|
|
5
|
+
var ansiPattern = Util.ansiPattern;
|
|
6
|
+
var graphemeSegmenter = Util.graphemeSegmenter;
|
|
7
|
+
|
|
8
|
+
function getWrapTokens(text) {
|
|
9
|
+
// Split text into alternating words and breakable whitespace, while preserving
|
|
10
|
+
// ANSI sequences as zero-width units in their original positions.
|
|
11
|
+
var units = [];
|
|
12
|
+
var pattern = new RegExp(ansiPattern.source, 'g');
|
|
13
|
+
var offset = 0;
|
|
14
|
+
var match = null;
|
|
15
|
+
|
|
16
|
+
var addText = function(value) {
|
|
17
|
+
for (var item of graphemeSegmenter.segment(value)) {
|
|
18
|
+
units.push({
|
|
19
|
+
text: item.segment,
|
|
20
|
+
width: stringWidth(item.segment),
|
|
21
|
+
breakable: !!item.segment.match(/^(?:\s|\u200B)+$/u),
|
|
22
|
+
ansi: false
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
while ((match = pattern.exec(text))) {
|
|
28
|
+
if (match.index > offset) addText( text.substring(offset, match.index) );
|
|
29
|
+
units.push({ text: match[0], width: 0, breakable: false, ansi: true });
|
|
30
|
+
offset = pattern.lastIndex;
|
|
31
|
+
}
|
|
32
|
+
if (offset < text.length) addText( text.substring(offset) );
|
|
33
|
+
|
|
34
|
+
var tokens = [];
|
|
35
|
+
var token = null;
|
|
36
|
+
var pending = [];
|
|
37
|
+
units.forEach( function(unit) {
|
|
38
|
+
// ANSI codes do not determine whether a token is a word or whitespace.
|
|
39
|
+
if (unit.ansi) {
|
|
40
|
+
if (token) token.units.push(unit);
|
|
41
|
+
else pending.push(unit);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var type = unit.breakable ? 'break' : 'word';
|
|
46
|
+
if (!token || (token.type != type)) {
|
|
47
|
+
if (token) tokens.push(token);
|
|
48
|
+
token = { type: type, units: pending.concat([unit]) };
|
|
49
|
+
pending = [];
|
|
50
|
+
}
|
|
51
|
+
else token.units.push(unit);
|
|
52
|
+
} );
|
|
53
|
+
|
|
54
|
+
if (token) tokens.push(token);
|
|
55
|
+
else if (pending.length) tokens.push({ type: 'word', units: pending });
|
|
56
|
+
return tokens;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function joinWrapUnits(units) {
|
|
60
|
+
// Join tokenized graphemes and ANSI sequences back into their original string.
|
|
61
|
+
return units.map( function(unit) { return unit.text; } ).join('');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function trimWrapLines(text) {
|
|
65
|
+
// Trim spaces and tabs from each line without removing the initial indentation.
|
|
66
|
+
return text.split('\n').map( function(line) {
|
|
67
|
+
return line.replace(/[ \t]+$/, '');
|
|
68
|
+
} ).join('\n');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function wordWrap(text, options) {
|
|
72
|
+
// Wrap text using terminal display columns instead of JavaScript string length.
|
|
73
|
+
options = options || {};
|
|
74
|
+
if (text == null) return text;
|
|
75
|
+
|
|
76
|
+
var width = options.width || 50;
|
|
77
|
+
var indent = (typeof(options.indent) == 'string') ? options.indent : ' ';
|
|
78
|
+
var newline = options.newline || ('\n' + indent);
|
|
79
|
+
var escape = (typeof(options.escape) == 'function') ? options.escape : function(value) {
|
|
80
|
+
return value;
|
|
81
|
+
};
|
|
82
|
+
var tokens = getWrapTokens(text);
|
|
83
|
+
var lines = [];
|
|
84
|
+
var lineUnits = [];
|
|
85
|
+
|
|
86
|
+
var flushLine = function() {
|
|
87
|
+
var line = joinWrapUnits(lineUnits);
|
|
88
|
+
if (line.slice(-1) == '\n') line = line.slice(0, -1);
|
|
89
|
+
lines.push( escape(line) );
|
|
90
|
+
lineUnits = [];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (options.cut === true) {
|
|
94
|
+
// Cut mode fills every line to the requested display width, even when that
|
|
95
|
+
// means breaking a word. Complete grapheme clusters always stay together.
|
|
96
|
+
var lineWidth = 0;
|
|
97
|
+
tokens.forEach( function(token) {
|
|
98
|
+
token.units.forEach( function(unit) {
|
|
99
|
+
if (unit.text.includes('\n')) {
|
|
100
|
+
if (stringWidth(joinWrapUnits(lineUnits))) flushLine();
|
|
101
|
+
else lineUnits = [];
|
|
102
|
+
lineWidth = 0;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!unit.ansi && lineWidth && ((lineWidth + unit.width) > width)) {
|
|
107
|
+
flushLine();
|
|
108
|
+
lineWidth = 0;
|
|
109
|
+
}
|
|
110
|
+
lineUnits.push(unit);
|
|
111
|
+
lineWidth += unit.width;
|
|
112
|
+
} );
|
|
113
|
+
} );
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
tokens.forEach( function(token) {
|
|
117
|
+
var tokenText = joinWrapUnits(token.units);
|
|
118
|
+
|
|
119
|
+
if (token.type == 'break') {
|
|
120
|
+
lineUnits = lineUnits.concat(token.units);
|
|
121
|
+
if (tokenText.includes('\n')) {
|
|
122
|
+
if (stringWidth(joinWrapUnits(lineUnits))) flushLine();
|
|
123
|
+
else lineUnits = [];
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
var lineText = joinWrapUnits(lineUnits);
|
|
129
|
+
var candidateWidth = stringWidth(lineText + tokenText);
|
|
130
|
+
if (lineUnits.length && (candidateWidth > width)) flushLine();
|
|
131
|
+
lineUnits = lineUnits.concat(token.units);
|
|
132
|
+
} );
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (lineUnits.length) flushLine();
|
|
136
|
+
var result = indent + lines.join(newline);
|
|
137
|
+
if (options.trim === true) result = trimWrapLines(result);
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = wordWrap;
|