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.
Files changed (40) hide show
  1. package/agents/string-width/.editorconfig +12 -0
  2. package/agents/string-width/.gitattributes +1 -0
  3. package/agents/string-width/.github/security.md +3 -0
  4. package/agents/string-width/.github/workflows/main.yml +22 -0
  5. package/agents/string-width/index.d.ts +39 -0
  6. package/agents/string-width/index.js +207 -0
  7. package/agents/string-width/index.test-d.ts +7 -0
  8. package/agents/string-width/license +9 -0
  9. package/agents/string-width/package.json +65 -0
  10. package/agents/string-width/readme.md +66 -0
  11. package/agents/string-width/test.js +339 -0
  12. package/agents/widest-line/.editorconfig +12 -0
  13. package/agents/widest-line/.gitattributes +1 -0
  14. package/agents/widest-line/.github/security.md +3 -0
  15. package/agents/widest-line/.github/workflows/main.yml +21 -0
  16. package/agents/widest-line/index.d.ts +12 -0
  17. package/agents/widest-line/index.js +11 -0
  18. package/agents/widest-line/license +9 -0
  19. package/agents/widest-line/package.json +60 -0
  20. package/agents/widest-line/readme.md +26 -0
  21. package/agents/widest-line/test.js +8 -0
  22. package/agents/word-wrap/.editorconfig +13 -0
  23. package/agents/word-wrap/.eslintrc.json +122 -0
  24. package/agents/word-wrap/.gitattributes +10 -0
  25. package/agents/word-wrap/.github/workflows/publish.yml +19 -0
  26. package/agents/word-wrap/.travis.yml +13 -0
  27. package/agents/word-wrap/.verb.md +114 -0
  28. package/agents/word-wrap/LICENSE +21 -0
  29. package/agents/word-wrap/README.md +201 -0
  30. package/agents/word-wrap/bower.json +60 -0
  31. package/agents/word-wrap/index.d.ts +50 -0
  32. package/agents/word-wrap/index.js +61 -0
  33. package/agents/word-wrap/package.json +77 -0
  34. package/agents/word-wrap/test.js +69 -0
  35. package/cli.js +17 -10
  36. package/package.json +3 -8
  37. package/test.js +20 -0
  38. package/util.js +15 -0
  39. package/width.js +75 -0
  40. 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
- var stringWidth = require('string-width');
10
- var widestLine = require('widest-line');
11
- var repeating = require('repeating');
12
- var wordWrap = require('word-wrap');
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 3rd party utilities
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: new RegExp([
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 repeating(amount, ''+text);
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.21",
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": "^1.0.35",
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;