UNPKG

22.7 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var math = require('@aryth/math');
6var enumDataTypes = require('@typen/enum-data-types');
7var nullish = require('@typen/nullish');
8var readline = require('readline');
9
10function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
12var readline__default = /*#__PURE__*/_interopDefaultLegacy(readline);
13
14const trailZero = num => {
15 if (!num) return '0';
16 const tx = '' + math.roundD2(num);
17 let i = tx.indexOf('.');
18
19 if (!~i) {
20 return tx + '.00';
21 }
22
23 let df = tx.length - i;
24
25 if (df === 3) {
26 return tx;
27 }
28
29 if (df === 2) {
30 return tx + '0';
31 }
32
33 if (df === 1) {
34 return tx + '00';
35 }
36
37 return tx;
38};
39const base3ToScale = (base3, dec) => {
40 if (base3 === 0) return 'B'; //
41
42 if (base3 === 1) return dec ? 'K' : 'KB'; // Kilo
43
44 if (base3 === 2) return dec ? 'M' : 'MB'; // Mega
45
46 if (base3 === 3) return dec ? 'G' : 'GB'; // Giga
47
48 if (base3 === 4) return dec ? 'T' : 'TB'; // Tera
49
50 if (base3 === 5) return dec ? 'P' : 'PB'; // Peta
51
52 if (base3 === 6) return dec ? 'E' : 'EB'; // Exa
53
54 if (base3 === 7) return dec ? 'Z' : 'ZB'; // Zetta
55
56 if (base3 === 8) return dec ? 'Y' : 'YB'; // Yotta
57};
58
59const DEFAULT_SENTENCE = 'progress [{bar}] {progress}% | ETA: {eta}s | {value}/{total}';
60class Layout {
61 /**
62 * @param {object} [options]
63 * @param {number} [options.size] size of the progressbar in chars
64 * @param {string[]|string} [options.char]
65 * @param {string} [options.glue]
66 * @param {string} [options.sentence]
67 * @param {boolean} [options.autoZero = false] autoZero - false
68 * @param {function(State):string} [options.bar]
69 * @param {function(State):string} [options.degree]
70 * @param {function(State,object):string} [options.formatter]
71 */
72 constructor(options) {
73 var _options$size, _options$glue, _options$autoZero, _options$sentence;
74
75 const char = typeof options.char === enumDataTypes.STR ? [options.char, ' '] : Array.isArray(options.char) ? options.char : ['=', '-'];
76 const [x, y] = char;
77 this.size = (_options$size = options.size) !== null && _options$size !== void 0 ? _options$size : 24;
78 this.chars = [x.repeat(this.size + 1), y.repeat(this.size + 1)];
79 this.glue = (_options$glue = options.glue) !== null && _options$glue !== void 0 ? _options$glue : '';
80 this.autoZero = (_options$autoZero = options.autoZero) !== null && _options$autoZero !== void 0 ? _options$autoZero : false; // autoZero - false
81
82 this.sentence = (_options$sentence = options.sentence) !== null && _options$sentence !== void 0 ? _options$sentence : DEFAULT_SENTENCE;
83 if (options.bar) this.bar = options.bar.bind(this);
84 if (options.degree) this.degree = options.degree.bind(this);
85 if (options.formatter) this.formatter = options.formatter.bind(this);
86 }
87
88 static build(options) {
89 return new Layout(options);
90 }
91
92 loadFormatter(formatter) {
93 this.formatter = formatter.bind(this);
94 return this;
95 }
96
97 bar(state) {
98 const {
99 progress
100 } = state;
101 const {
102 chars,
103 glue,
104 size
105 } = this;
106 const lenX = math.round(progress * size),
107 lenY = size - lenX;
108 const [x, y] = chars; // generate bar string by stripping the pre-rendered strings
109
110 return x.slice(0, lenX) + glue + y.slice(0, lenY);
111 }
112
113 degree(state) {
114 let {
115 value,
116 total
117 } = state;
118 const {
119 base3 = true,
120 decimal = true
121 } = this;
122 if (!base3) return `${math.round(value)}/${total}`;
123 const thousand = decimal ? 1000 : 1024;
124 let base3Level = 0;
125
126 while (total > thousand) {
127 // base3Level <= base3
128 total /= thousand;
129 value /= thousand;
130 base3Level++;
131 }
132
133 const totalText = trailZero(total);
134 const valueText = trailZero(value).padStart(totalText.length); // return { value: valueText, total: totalText, scale: base3ToScale(base3, dec) }
135
136 return `${valueText}/${totalText} ${base3ToScale(base3Level, decimal)}`;
137 }
138 /**
139 *
140 * @param {State|object} state
141 * @returns {string}
142 */
143
144
145 formatter(state) {
146 var _this$sentence;
147
148 return (_this$sentence = this.sentence) === null || _this$sentence === void 0 ? void 0 : _this$sentence.replace(/\{(\w+)\}/g, (match, key) => {
149 if (key === 'bar') return this.bar(state);
150 if (key === 'degree') return this.degree(state);
151 return key in state ? state[key] : match;
152 });
153 }
154
155}
156
157class Config {
158 /**
159 *
160 * @param {object} config
161 *
162 * @param {WriteStream} [config.stream = process.stderr] the output stream to write on
163 * @param {number} [config.fps = 12] the max update rate in fps (redraw will only triggered on value change)
164 * @param {Terminal|any} [config.terminal = null] external terminal provided ?
165 * @param {boolean} [config.autoClear = false] clear on finish ?
166 * @param {boolean} [config.autoStop = false] stop on finish ?
167 * @param {boolean} [config.hideCursor = false] hide the cursor ?
168 * @param {boolean} [config.lineWrap = false] allow or disable setLineWrap ?
169 * @param {string} [config.sentence = DEFAULT_FORMAT] the bar sentence
170 * @param {function} [config.formatTime = null] external time-sentence provided ?
171 * @param {function} [config.formatValue = null] external value-sentence provided ?
172 * @param {function} [config.formatBar = null] external bar-sentence provided ?
173 * @param {boolean} [config.syncUpdate = true] allow synchronous updates ?
174 * @param {boolean} [config.noTTYOutput = false] noTTY mode
175 * @param {number} [config.notTTYSchedule = 2000] schedule - 2s
176 * @param {boolean} [config.forceRedraw = false] force bar redraw even if progress did not change
177 *
178 * @param {object} [config.eta] eta config
179 * @param {boolean} [config.eta.on = false] switch to turn on eta
180 * @param {number} [config.eta.capacity = 10] the number of results to average ETA over
181 * @param {boolean} [config.eta.autoUpdate = false] automatic eta updates based on fps
182 * @returns {Config}
183 */
184 constructor(config) {
185 var _config$fps, _config$stream, _config$eta$capacity, _config$eta, _config$eta$autoUpdat, _config$eta2, _config$terminal, _config$autoClear, _config$autoStop, _config$hideCursor, _config$lineWrap, _config$syncUpdate, _config$noTTYOutput, _config$notTTYSchedul, _config$forceRedraw;
186
187 // merge layout
188 // the max update rate in fps (redraw will only triggered on value change)
189 this.throttle = 1000 / ((_config$fps = config.fps) !== null && _config$fps !== void 0 ? _config$fps : 10); // the output stream to write on
190
191 this.stream = (_config$stream = config.stream) !== null && _config$stream !== void 0 ? _config$stream : process.stderr;
192 this.eta = config.eta ? {
193 capacity: (_config$eta$capacity = (_config$eta = config.eta) === null || _config$eta === void 0 ? void 0 : _config$eta.capacity) !== null && _config$eta$capacity !== void 0 ? _config$eta$capacity : 10,
194 // the number of results to average ETA over
195 autoUpdate: (_config$eta$autoUpdat = (_config$eta2 = config.eta) === null || _config$eta2 === void 0 ? void 0 : _config$eta2.autoUpdate) !== null && _config$eta$autoUpdat !== void 0 ? _config$eta$autoUpdat : false // automatic eta updates based on fps
196
197 } : null;
198 this.terminal = (_config$terminal = config.terminal) !== null && _config$terminal !== void 0 ? _config$terminal : null; // external terminal provided ?
199
200 this.autoClear = (_config$autoClear = config.autoClear) !== null && _config$autoClear !== void 0 ? _config$autoClear : false; // clear on finish ?
201
202 this.autoStop = (_config$autoStop = config.autoStop) !== null && _config$autoStop !== void 0 ? _config$autoStop : false; // stop on finish ?
203
204 this.hideCursor = (_config$hideCursor = config.hideCursor) !== null && _config$hideCursor !== void 0 ? _config$hideCursor : false; // hide the cursor ?
205
206 this.lineWrap = (_config$lineWrap = config.lineWrap) !== null && _config$lineWrap !== void 0 ? _config$lineWrap : false; // disable setLineWrap ?
207
208 this.syncUpdate = (_config$syncUpdate = config.syncUpdate) !== null && _config$syncUpdate !== void 0 ? _config$syncUpdate : true; // allow synchronous updates ?
209
210 this.noTTYOutput = (_config$noTTYOutput = config.noTTYOutput) !== null && _config$noTTYOutput !== void 0 ? _config$noTTYOutput : false; // noTTY mode
211
212 this.notTTYSchedule = (_config$notTTYSchedul = config.notTTYSchedule) !== null && _config$notTTYSchedul !== void 0 ? _config$notTTYSchedul : 2000; // schedule - 2s
213
214 this.forceRedraw = (_config$forceRedraw = config.forceRedraw) !== null && _config$forceRedraw !== void 0 ? _config$forceRedraw : false; // force bar redraw even if progress did not change
215
216 return this;
217 }
218
219 static build(config) {
220 return new Config(config);
221 }
222
223}
224
225function _defineProperty(obj, key, value) {
226 if (key in obj) {
227 Object.defineProperty(obj, key, {
228 value: value,
229 enumerable: true,
230 configurable: true,
231 writable: true
232 });
233 } else {
234 obj[key] = value;
235 }
236
237 return obj;
238}
239
240function _classPrivateMethodGet(receiver, privateSet, fn) {
241 if (!privateSet.has(receiver)) {
242 throw new TypeError("attempted to get private field on non-instance");
243 }
244
245 return fn;
246}
247
248function _checkPrivateRedeclaration(obj, privateCollection) {
249 if (privateCollection.has(obj)) {
250 throw new TypeError("Cannot initialize the same private elements twice on an object");
251 }
252}
253
254function _classPrivateMethodInitSpec(obj, privateSet) {
255 _checkPrivateRedeclaration(obj, privateSet);
256
257 privateSet.add(obj);
258}
259
260// ETA calculation
261class ETA {
262 constructor(capacity, initTime, initValue) {
263 // size of eta buffer
264 this.capacity = capacity || 100; // eta buffer with initial values
265
266 this.valueSeries = [initValue];
267 this.timeSeries = [initTime]; // eta time value
268
269 this.eta = '0';
270 } // add new values to calculation buffer
271
272
273 update(time, {
274 value,
275 total
276 }) {
277 this.valueSeries.push(value);
278 this.timeSeries.push(time); // trigger recalculation
279
280 this.calculate(total - value);
281 } // fetch estimated time
282
283
284 get value() {
285 return this.eta;
286 } // eta calculation - request number of remaining events
287
288
289 calculate(remaining) {
290 // get number of samples in eta buffer
291 const consumed = this.valueSeries.length;
292 const gap = Math.min(this.capacity, consumed);
293 const dValue = this.valueSeries[consumed - 1] - this.valueSeries[consumed - gap];
294 const dTime = this.timeSeries[consumed - 1] - this.timeSeries[consumed - gap]; // get progress per ms
295
296 const marginalRate = dValue / dTime; // strip past elements
297
298 this.valueSeries = this.valueSeries.slice(-this.capacity);
299 this.timeSeries = this.timeSeries.slice(-this.capacity); // eq: vt_rate *x = total
300
301 const eta = Math.ceil(remaining / marginalRate / 1000); // check values
302
303 return this.eta = isNaN(eta) ? 'NULL' : !isFinite(eta) ? 'INF' // +/- Infinity --- NaN already handled
304 : eta > 1e7 ? 'INF' // > 10M s ? - set upper display limit ~115days (1e7/60/60/24)
305 : eta < 0 ? 0 // negative ?
306 : eta; // assign
307 }
308
309}
310
311class State {
312 /** @type {number} the current bar value */
313
314 /** @type {number} the end value of the bar */
315
316 /** @type {?number} start time (used for eta calculation) */
317
318 /** @type {?number} stop time (used for duration calculation) */
319 constructor(total, value, payload, eta) {
320 var _eta$capacity;
321
322 _defineProperty(this, "value", 0);
323
324 _defineProperty(this, "total", 100);
325
326 _defineProperty(this, "start", null);
327
328 _defineProperty(this, "end", null);
329
330 this.value = value !== null && value !== void 0 ? value : 0;
331 this.total = total !== null && total !== void 0 ? total : 100;
332 this.start = Date.now(); // store start time for duration+eta calculation
333
334 this.end = null; // reset stop time for 're-start' scenario (used for duration calculation)
335
336 this.calETA = eta ? new ETA((_eta$capacity = eta.capacity) !== null && _eta$capacity !== void 0 ? _eta$capacity : 64, this.start, this.value) : null; // initialize eta buffer
337
338 this.payload = payload !== null && payload !== void 0 ? payload : {};
339 return this;
340 }
341
342 static build(values) {
343 const {
344 total,
345 value,
346 payload,
347 eta
348 } = values;
349 return new State(total, value, payload, eta);
350 }
351
352 initialize(total, value, payload, eta) {
353 return Object.assign(this, new State(total, value, payload, eta));
354 }
355
356 get eta() {
357 return this.calETA.eta;
358 }
359
360 get reachLimit() {
361 return this.value >= this.total;
362 }
363
364 get progress() {
365 const progress = this.value / this.total;
366 return isNaN(progress) ? 0 // this.preset?.autoZero ? 0.0 : 1.0
367 : math.constraint(progress, 0, 1);
368 }
369
370 update(value, payload) {
371 var _this$calETA;
372
373 // if (payload) for (let key in payload) this[key] = payload[key]
374 this.value = value;
375 if (payload) this.payload = payload;
376 (_this$calETA = this.calETA) === null || _this$calETA === void 0 ? void 0 : _this$calETA.update(Date.now(), this); // add new value; recalculate eta
377
378 if (this.reachLimit) this.end = Date.now();
379 return this;
380 }
381
382 stop(value, payload) {
383 this.end = Date.now();
384 if (nullish.valid(value)) this.value = value;
385 if (payload) this.payload = payload;
386 }
387
388 get elapsed() {
389 var _this$end;
390
391 return math.round((((_this$end = this.end) !== null && _this$end !== void 0 ? _this$end : Date.now()) - this.start) / 1000);
392 }
393
394 get percent() {
395 return ~~(this.progress * 100);
396 }
397
398}
399
400class Escape {
401 constructor(conf) {
402 var _conf$ctx, _conf$arg, _conf$instant;
403
404 this.ctx = (_conf$ctx = conf.ctx) !== null && _conf$ctx !== void 0 ? _conf$ctx : {};
405 this.arg = (_conf$arg = conf.arg) !== null && _conf$arg !== void 0 ? _conf$arg : null;
406 this.fn = conf.fn.bind(this.ctx, this.arg);
407 this.instant = (_conf$instant = conf.instant) !== null && _conf$instant !== void 0 ? _conf$instant : true;
408 this.timer = null;
409 this.logs = [];
410 }
411
412 static build(conf) {
413 return new Escape(conf);
414 }
415
416 get active() {
417 return nullish.valid(this.timer);
418 }
419
420 loop(ms) {
421 if (typeof this.timer === enumDataTypes.OBJ) this.stop();
422 if (!this.fn) return void 0;
423
424 const func = () => {
425 this.fn();
426 this.logs.push(this.arg.map(state => state.eta));
427 };
428
429 if (this.instant) func();
430 this.timer = setInterval(func, ms);
431 }
432
433 stop() {
434 clearTimeout(this.timer);
435 return this.timer = null;
436 }
437
438}
439
440class Terminal {
441 /** @type {boolean} line wrapping enabled */
442
443 /** @type {number} current, relative y position */
444
445 /**
446 *
447 * @param {Config|object} configs
448 * @param {node::WriteStream} configs.stream
449 * @param {boolean} [configs.lineWrap]
450 * @param {number} [configs.dy]
451 *
452 */
453 constructor(configs) {
454 var _configs$lineWrap, _configs$dy;
455
456 _defineProperty(this, "stream", null);
457
458 _defineProperty(this, "lineWrap", true);
459
460 _defineProperty(this, "dy", 0);
461
462 this.stream = configs.stream;
463 this.lineWrap = (_configs$lineWrap = configs.lineWrap) !== null && _configs$lineWrap !== void 0 ? _configs$lineWrap : true;
464 this.dy = (_configs$dy = configs.dy) !== null && _configs$dy !== void 0 ? _configs$dy : 0;
465 } // tty environment ?
466
467
468 get isTTY() {
469 return this.stream.isTTY;
470 } // get terminal width
471
472
473 get width() {
474 var _this$stream$columns;
475
476 return (_this$stream$columns = this.stream.columns) !== null && _this$stream$columns !== void 0 ? _this$stream$columns : this.stream.isTTY ? 80 : 200; // set max width to 80 in tty-mode and 200 in noTTY-mode
477 } // write content to output stream
478 // @TODO use string-width to strip length
479
480
481 write(tx) {
482 this.stream.write(tx); // this.stream.write(this.lineWrap ? tx.slice(0, this.width) : tx)
483 }
484
485 cleanWrite(tx) {
486 this.cursorTo(0, null); // set cursor to start of line
487
488 this.stream.write(tx); // write output
489
490 this.clearRight(); // clear to the right from cursor
491 } // save cursor position + settings
492
493
494 saveCursor() {
495 if (!this.isTTY) return void 0;
496 this.stream.write('\x1B7'); // save position
497 } // restore last cursor position + settings
498
499
500 restoreCursor() {
501 if (!this.isTTY) return void 0;
502 this.stream.write('\x1B8'); // restore cursor
503 } // show/hide cursor
504
505
506 showCursor(enabled) {
507 if (!this.isTTY) return void 0;
508 enabled ? this.stream.write('\x1B[?25h') : this.stream.write('\x1B[?25l');
509 } // change cursor position
510
511
512 cursorTo(x, y) {
513 if (!this.isTTY) return void 0;
514 readline__default["default"].cursorTo(this.stream, x, y); // move cursor absolute
515 } // change relative cursor position
516
517
518 moveCursor(dx, dy) {
519 if (!this.isTTY) return void 0;
520 if (dy) this.dy += dy; // store current position
521
522 if (dx || dy) readline__default["default"].moveCursor(this.stream, dx, dy); // move cursor relative
523 } // reset relative cursor
524
525
526 resetCursor() {
527 if (!this.isTTY) return void 0;
528 readline__default["default"].moveCursor(this.stream, 0, -this.dy); // move cursor to initial line
529
530 readline__default["default"].cursorTo(this.stream, 0, null); // first char
531
532 this.dy = 0; // reset counter
533 } // clear to the right from cursor
534
535
536 clearRight() {
537 if (!this.isTTY) return void 0;
538 readline__default["default"].clearLine(this.stream, 1);
539 } // clear the full line
540
541
542 clearLine() {
543 if (!this.isTTY) return void 0;
544 readline__default["default"].clearLine(this.stream, 0);
545 } // clear everything beyond the current line
546
547
548 clearDown() {
549 if (!this.isTTY) return void 0;
550 readline__default["default"].clearScreenDown(this.stream);
551 } // add new line; increment counter
552
553
554 newline() {
555 this.stream.write('\n');
556 this.dy++;
557 } // control line wrapping
558
559
560 setLineWrap(enabled) {
561 if (!this.isTTY) return void 0;
562 this.lineWrap = enabled; // store state
563
564 enabled ? this.stream.write('\x1B[?7h') : this.stream.write('\x1B[?7l');
565 }
566
567}
568
569var _renderStates = /*#__PURE__*/new WeakSet();
570
571class Baro {
572 /** @type {Config|object} store config */
573
574 /** @type {object} payload data */
575
576 /** @type {Terminal|object} store terminal instance */
577
578 /** @type {?string} last drawn string - only render on change! */
579
580 /** @type {number} last update time */
581
582 /** @type {boolean} progress bar active ? */
583
584 /** @type {function} use default formatter or custom one ? */
585
586 /** @type {State} */
587
588 /**
589 *
590 * @param {Config} config
591 * @param {Layout} layout
592 */
593 constructor(config, layout) {
594 var _this$config$terminal;
595
596 _classPrivateMethodInitSpec(this, _renderStates);
597
598 _defineProperty(this, "config", void 0);
599
600 _defineProperty(this, "payload", {});
601
602 _defineProperty(this, "terminal", void 0);
603
604 _defineProperty(this, "phrase", null);
605
606 _defineProperty(this, "prev", void 0);
607
608 _defineProperty(this, "active", false);
609
610 _defineProperty(this, "formatter", void 0);
611
612 _defineProperty(this, "state", void 0);
613
614 this.config = config;
615 this.layout = layout;
616 this.states = [];
617 this.escape = Escape.build({
618 fn: _classPrivateMethodGet(this, _renderStates, _renderStates2),
619 ctx: this,
620 arg: this.states
621 });
622 this.terminal = (_this$config$terminal = this.config.terminal) !== null && _this$config$terminal !== void 0 ? _this$config$terminal : new Terminal(this.config);
623 }
624
625 static build(config, layout) {
626 config = Config.build(config);
627 layout = Layout.build(layout);
628 return new Baro(config, layout);
629 }
630
631 get progress() {
632 return this.state.progress;
633 }
634
635 get forceRedraw() {
636 return this.config.forceRedraw || this.config.noTTYOutput && !this.terminal.isTTY; // force redraw in noTTY-mode!
637 }
638
639 get noTTY() {
640 return this.config.noTTYOutput && !this.terminal.isTTY;
641 }
642
643 boot() {
644 if (this.config.hideCursor) this.terminal.showCursor(false); // hide the cursor ?
645
646 if (!this.config.lineWrap) this.terminal.setLineWrap(false); // disable line wrapping ?
647
648 this.escape.loop(this.config.throttle); // initialize update timer
649
650 this.active = true;
651 } // add a new bar to the stack
652
653 /**
654 *
655 * @param total
656 * @param value
657 * @param payload
658 * @returns {State}
659 */
660
661
662 create(total, value, payload) {
663 // progress updates are only visible in TTY mode!
664 if (this.noTTY) return void 0;
665 const state = new State(total, value, payload, true);
666 state.last = Number.NEGATIVE_INFINITY;
667 this.states.push(state);
668 if (!this.escape.active && this.states.length) this.boot();
669 return state;
670 } // remove a bar from the stack
671
672
673 remove(state) {
674 const index = this.states.indexOf(state); // find element
675
676 if (index < 0) {
677 return false;
678 } // element found ?
679
680
681 this.states.splice(index, 1); // remove element
682
683 this.terminal.newline();
684 this.terminal.clearDown();
685 return true;
686 }
687
688 stop() {
689 this.escape.stop(); // stop timer
690
691 this.active = false; // set flag
692
693 if (this.config.hideCursor) {
694 this.terminal.showCursor(true);
695 } // cursor hidden ?
696
697
698 if (!this.config.lineWrap) {
699 this.terminal.setLineWrap(true);
700 } // re-enable line wrapping ?
701
702
703 if (this.config.autoClear) {
704 this.terminal.resetCursor(); // reset cursor
705
706 this.terminal.clearDown();
707 } // clear all bars or show final progress
708 else {
709 for (let state of this.states) {
710 state.stop();
711 }
712
713 _classPrivateMethodGet(this, _renderStates, _renderStates2).call(this, this.states);
714 }
715 }
716
717}
718
719function _renderStates2(states) {
720 this.terminal.resetCursor(); // reset cursor
721
722 for (let i = 0, hi = states.length; i < hi; i++) {
723 const state = states[i]; // update each bar
724
725 if (this.forceRedraw || state.value !== state.last) {
726 this.terminal.cleanWrite(this.layout.formatter(state));
727 } // string updated, only trigger redraw on change
728
729
730 this.terminal.newline();
731 state.last = state.value;
732 }
733
734 if (this.noTTY) {
735 this.terminal.newline();
736 this.terminal.newline();
737 } // add new line in noTTY mode
738
739
740 if (this.config.autoStop && states.every(state => state.reachLimit)) {
741 this.stop();
742 } // stop if autoStop and all bars stopped
743
744}
745
746exports.Baro = Baro;
747exports.Config = Config;
748exports.ETA = ETA;
749exports.Escape = Escape;
750exports.Layout = Layout;
751exports.State = State;