Zion Boggan
repos/TreeTrace/src/util.js
zionboggan.com ↗
103 lines · javascript
History for this file →
1
import { createHash } from 'node:crypto';
2
 
3
const useColor =
4
  process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== 'dumb';
5
 
6
const wrap = (open, close) => (s) =>
7
  useColor ? `\x1b[${open}m${s}\x1b[${close}m` : String(s);
8
 
9
export const c = {
10
  bold: wrap(1, 22),
11
  dim: wrap(2, 22),
12
  red: wrap(31, 39),
13
  green: wrap(32, 39),
14
  yellow: wrap(33, 39),
15
  blue: wrap(34, 39),
16
  magenta: wrap(35, 39),
17
  cyan: wrap(36, 39),
18
  gray: wrap(90, 39),
19
};
20
 
21
export function sha256(text) {
22
  return createHash('sha256').update(text, 'utf8').digest('hex');
23
}
24
 
25
export function truncate(s, n = 80) {
26
  if (!s) return '';
27
  const one = s.replace(/\s+/g, ' ').trim();
28
  return one.length <= n ? one : `${one.slice(0, n - 1).trimEnd()}...`;
29
}
30
 
31
export function plural(n, word, pluralWord) {
32
  return `${n} ${n === 1 ? word : pluralWord || `${word}s`}`;
33
}
34
 
35
export function formatDuration(ms) {
36
  if (!Number.isFinite(ms) || ms <= 0) return null;
37
  const minutes = Math.round(ms / 60000);
38
  if (minutes < 60) return `${minutes} min`;
39
  const hours = ms / 3600000;
40
  if (hours < 48) return `${Math.round(hours * 10) / 10} hours`;
41
  const days = Math.round(ms / 86400000);
42
  return `${days} days`;
43
}
44
 
45
export function formatDay(ts) {
46
  if (!ts) return null;
47
  const d = new Date(ts);
48
  if (Number.isNaN(d.getTime())) return null;
49
  return d.toISOString().slice(0, 10);
50
}
51
 
52
export function daySpan(timestamps) {
53
  const valid = timestamps.map((t) => new Date(t).getTime()).filter(Number.isFinite);
54
  if (!valid.length) return null;
55
  const span = Math.max(...valid) - Math.min(...valid);
56
  const days = Math.max(1, Math.ceil(span / 86400000));
57
  return days;
58
}
59
 
60
export function shannonEntropy(s) {
61
  if (!s) return 0;
62
  const freq = new Map();
63
  for (const ch of s) freq.set(ch, (freq.get(ch) || 0) + 1);
64
  let entropy = 0;
65
  for (const count of freq.values()) {
66
    const p = count / s.length;
67
    entropy -= p * Math.log2(p);
68
  }
69
  return entropy;
70
}
71
 
72
export function mdEscapePipe(s) {
73
  return String(s).replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
74
}
75
 
76
export function escapeMd(text) {
77
  return String(text == null ? '' : text)
78
    .replace(/&/g, '&amp;')
79
    .replace(/</g, '&lt;')
80
    .replace(/>/g, '&gt;');
81
}
82
 
83
export function escapeMdTags(text) {
84
  return String(text == null ? '' : text)
85
    .replace(/</g, '&lt;')
86
    .replace(/>/g, '&gt;');
87
}
88
 
89
export const ExitCode = Object.freeze({
90
  OK: 0,
91
  ERROR: 1,
92
  USAGE: 2,
93
  NO_DATA: 3,
94
  WOULD_LEAK: 4,
95
});
96
 
97
export class TreetraceError extends Error {
98
  constructor(message, exitCode = ExitCode.ERROR) {
99
    super(message);
100
    this.name = 'TreetraceError';
101
    this.exitCode = exitCode;
102
  }
103
}