kodevu 0.1.18 → 0.1.20

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/progress-ui.js +18 -255
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodevu",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "type": "module",
5
5
  "description": "Poll SVN revisions or Git commits, send each change diff to a reviewer CLI, and write configurable review reports.",
6
6
  "bin": {
@@ -1,8 +1,3 @@
1
- import readline from "node:readline";
2
-
3
- const SPINNER_FRAMES = ["|", "/", "-", "\\"];
4
- const ELLIPSIS = "...";
5
-
6
1
  function clampProgress(value) {
7
2
  if (!Number.isFinite(value)) {
8
3
  return 0;
@@ -11,78 +6,6 @@ function clampProgress(value) {
11
6
  return Math.max(0, Math.min(1, value));
12
7
  }
13
8
 
14
- function getCharacterWidth(character) {
15
- const codePoint = character.codePointAt(0);
16
-
17
- if (!codePoint || codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) {
18
- return 0;
19
- }
20
-
21
- if (
22
- codePoint >= 0x1100 &&
23
- (
24
- codePoint <= 0x115f ||
25
- codePoint === 0x2329 ||
26
- codePoint === 0x232a ||
27
- (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
28
- (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
29
- (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
30
- (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
31
- (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
32
- (codePoint >= 0xff00 && codePoint <= 0xff60) ||
33
- (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
34
- (codePoint >= 0x1f300 && codePoint <= 0x1f64f) ||
35
- (codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
36
- (codePoint >= 0x20000 && codePoint <= 0x3fffd)
37
- )
38
- ) {
39
- return 2;
40
- }
41
-
42
- return 1;
43
- }
44
-
45
- function getDisplayWidth(text) {
46
- let width = 0;
47
-
48
- for (const character of text) {
49
- width += getCharacterWidth(character);
50
- }
51
-
52
- return width;
53
- }
54
-
55
- function truncateLine(line, maxWidth) {
56
- if (!Number.isFinite(maxWidth) || maxWidth <= 0) {
57
- return "";
58
- }
59
-
60
- if (getDisplayWidth(line) <= maxWidth) {
61
- return line;
62
- }
63
-
64
- if (maxWidth <= 3) {
65
- return ".".repeat(maxWidth);
66
- }
67
-
68
- const targetWidth = maxWidth - getDisplayWidth(ELLIPSIS);
69
- let result = "";
70
- let width = 0;
71
-
72
- for (const character of line) {
73
- const nextWidth = width + getCharacterWidth(character);
74
-
75
- if (nextWidth > targetWidth) {
76
- break;
77
- }
78
-
79
- result += character;
80
- width = nextWidth;
81
- }
82
-
83
- return `${result}${ELLIPSIS}`;
84
- }
85
-
86
9
  class ProgressItem {
87
10
  constructor(display, label) {
88
11
  this.display = display;
@@ -90,20 +13,13 @@ class ProgressItem {
90
13
  this.progress = 0;
91
14
  this.stage = "";
92
15
  this.active = false;
93
- this.lastFallbackLine = "";
16
+ this.lastStatusLine = "";
94
17
  }
95
18
 
96
19
  start(stage = "starting") {
97
20
  this.active = true;
98
21
  this.stage = stage;
99
-
100
- if (!this.display.enabled) {
101
- this.writeFallback(`... ${this.label}: ${stage}`);
102
- return;
103
- }
104
-
105
- this.display.start();
106
- this.display.activate(this);
22
+ this.writeStatus();
107
23
  }
108
24
 
109
25
  update(progress, stage) {
@@ -113,16 +29,11 @@ class ProgressItem {
113
29
  this.stage = stage;
114
30
  }
115
31
 
116
- if (!this.display.enabled) {
117
- this.writeFallback(`... ${this.label}: ${this.stage}`);
118
- return;
119
- }
120
-
121
- this.display.activate(this);
32
+ this.writeStatus();
122
33
  }
123
34
 
124
35
  log(message) {
125
- this.display.log(message);
36
+ this.display.writeLine(message);
126
37
  }
127
38
 
128
39
  succeed(message) {
@@ -136,184 +47,36 @@ class ProgressItem {
136
47
  finish(prefix, progress, message) {
137
48
  this.progress = clampProgress(progress);
138
49
  this.active = false;
139
- this.display.deactivate(this);
140
- this.display.writeStaticLine(this.buildFinalLine(prefix, message));
141
- }
142
-
143
- renderLine(frameIndex) {
144
- return this.buildStatusLine(SPINNER_FRAMES[frameIndex], this.stage);
145
- }
146
-
147
- buildFinalLine(prefix, message) {
148
- return this.buildStatusLine(prefix, message);
50
+ this.display.writeLine(`${prefix} ${message}`);
149
51
  }
150
52
 
151
- buildStatusLine(prefix, suffix) {
152
- const availableWidth = this.display.getAvailableWidth();
153
- const pct = `${Math.round(this.progress * 100)}`.padStart(3, " ");
154
- const suffixText = suffix ? ` | ${suffix}` : "";
155
- return truncateLine(`${prefix} ${pct}% ${this.label}${suffixText}`, availableWidth);
156
- }
53
+ writeStatus() {
54
+ const line = this.buildStatusLine();
157
55
 
158
- writeFallback(line) {
159
- if (line === this.lastFallbackLine) {
56
+ if (line === this.lastStatusLine) {
160
57
  return;
161
58
  }
162
59
 
163
- this.lastFallbackLine = line;
164
- this.display.stream.write(`${line}\n`);
60
+ this.lastStatusLine = line;
61
+ this.display.writeLine(line);
62
+ }
63
+
64
+ buildStatusLine() {
65
+ const pct = `${Math.round(this.progress * 100)}`.padStart(3, " ");
66
+ return `[progress] ${pct}% ${this.label}${this.stage ? ` | ${this.stage}` : ""}`;
165
67
  }
166
68
  }
167
69
 
168
70
  export class ProgressDisplay {
169
71
  constructor(options = {}) {
170
72
  this.stream = options.stream || process.stdout;
171
- this.enabled = Boolean(this.stream.isTTY);
172
- this.frameIndex = 0;
173
- this.timer = null;
174
- this.items = [];
175
- this.currentItem = null;
176
- this.statusVisible = false;
177
- this.lastStatusWidth = 0;
178
- this.resizeAttached = false;
179
- this.handleResize = this.handleResize.bind(this);
180
73
  }
181
74
 
182
75
  createItem(label) {
183
- const item = new ProgressItem(this, label);
184
- this.items.push(item);
185
- return item;
186
- }
187
-
188
- start() {
189
- if (!this.enabled || this.timer) {
190
- return;
191
- }
192
-
193
- this.attachResizeHandler();
194
- this.timer = setInterval(() => {
195
- this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
196
- this.render();
197
- }, 120);
198
- }
199
-
200
- activate(item) {
201
- this.currentItem = item;
202
- this.render();
203
- }
204
-
205
- deactivate(item) {
206
- if (this.currentItem === item) {
207
- this.currentItem = this.items.findLast((candidate) => candidate.active) || null;
208
- }
209
-
210
- this.stopIfIdle();
211
- this.clearStatusLine();
212
-
213
- if (this.currentItem) {
214
- this.render();
215
- }
216
- }
217
-
218
- log(message) {
219
- if (!this.enabled) {
220
- this.stream.write(`${message}\n`);
221
- return;
222
- }
223
-
224
- this.clearStatusLine();
225
- this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
226
- this.render();
76
+ return new ProgressItem(this, label);
227
77
  }
228
78
 
229
- writeStaticLine(message) {
230
- if (!this.enabled) {
231
- this.stream.write(`${message}\n`);
232
- return;
233
- }
234
-
235
- this.clearStatusLine();
236
- this.stream.write(`${truncateLine(message, this.getAvailableWidth())}\n`);
237
- this.render();
238
- }
239
-
240
- render() {
241
- if (!this.enabled || !this.currentItem?.active) {
242
- return;
243
- }
244
-
245
- this.clearStatusLine();
246
- const line = this.currentItem.renderLine(this.frameIndex);
247
- this.stream.write(line);
248
- this.statusVisible = true;
249
- this.lastStatusWidth = getDisplayWidth(line);
250
- }
251
-
252
- clearStatusLine() {
253
- if (!this.enabled || !this.statusVisible) {
254
- return;
255
- }
256
-
257
- const rows = Math.max(1, Math.ceil(this.lastStatusWidth / Math.max(this.stream.columns || 1, 1)));
258
-
259
- readline.moveCursor(this.stream, 0, -Math.max(rows - 1, 0));
260
-
261
- for (let index = 0; index < rows; index += 1) {
262
- readline.clearLine(this.stream, 0);
263
- readline.cursorTo(this.stream, 0);
264
-
265
- if (index < rows - 1) {
266
- readline.moveCursor(this.stream, 0, 1);
267
- }
268
- }
269
-
270
- readline.moveCursor(this.stream, 0, -Math.max(rows - 1, 0));
271
- readline.cursorTo(this.stream, 0);
272
- this.statusVisible = false;
273
- this.lastStatusWidth = 0;
274
- }
275
-
276
- stopIfIdle() {
277
- if (this.items.some((item) => item.active)) {
278
- return;
279
- }
280
-
281
- if (this.timer) {
282
- clearInterval(this.timer);
283
- this.timer = null;
284
- }
285
-
286
- this.detachResizeHandler();
287
- }
288
-
289
- getAvailableWidth() {
290
- const columns = this.stream.columns || 80;
291
- return Math.max(columns - 1, 1);
292
- }
293
-
294
- handleResize() {
295
- if (!this.enabled) {
296
- return;
297
- }
298
-
299
- this.render();
300
- }
301
-
302
- attachResizeHandler() {
303
- if (typeof this.stream.on !== "function" || this.resizeAttached) {
304
- return;
305
- }
306
-
307
- this.stream.on("resize", this.handleResize);
308
- this.resizeAttached = true;
309
- }
310
-
311
- detachResizeHandler() {
312
- if (typeof this.stream.off !== "function" || !this.resizeAttached) {
313
- return;
314
- }
315
-
316
- this.stream.off("resize", this.handleResize);
317
- this.resizeAttached = false;
79
+ writeLine(message) {
80
+ this.stream.write(`${message}\n`);
318
81
  }
319
82
  }