ui.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import { EOL } from "os"
  2. import { Schema } from "effect"
  3. import { logo as glyphs } from "./logo"
  4. const wordmark = [
  5. `⠀ ▄ `,
  6. `██▀▄ █▀▀█ █▀▀▄ █▀▀█ █▀▀▄ █▀▀█ █▀▀█ █▀▀▄ █▀▀█`,
  7. `█▀ █ █ █ █▄▄▀ █ █ █▀ █ █ █ █ █ █ █▀▀▀`,
  8. `█▀ █ █▀▀▀ █ █ █▀▀▀ █▀ █ █ █▀▀▀ █▀▀▀ ▀▀▀▀`,
  9. ]
  10. export class CancelledError extends Schema.TaggedErrorClass<CancelledError>()("UICancelledError", {}) {}
  11. export const Style = {
  12. TEXT_HIGHLIGHT: "\x1b[96m",
  13. TEXT_HIGHLIGHT_BOLD: "\x1b[96m\x1b[1m",
  14. TEXT_DIM: "\x1b[90m",
  15. TEXT_DIM_BOLD: "\x1b[90m\x1b[1m",
  16. TEXT_NORMAL: "\x1b[0m",
  17. TEXT_NORMAL_BOLD: "\x1b[1m",
  18. TEXT_WARNING: "\x1b[93m",
  19. TEXT_WARNING_BOLD: "\x1b[93m\x1b[1m",
  20. TEXT_DANGER: "\x1b[91m",
  21. TEXT_DANGER_BOLD: "\x1b[91m\x1b[1m",
  22. TEXT_SUCCESS: "\x1b[92m",
  23. TEXT_SUCCESS_BOLD: "\x1b[92m\x1b[1m",
  24. TEXT_INFO: "\x1b[94m",
  25. TEXT_INFO_BOLD: "\x1b[94m\x1b[1m",
  26. }
  27. export function println(...message: string[]) {
  28. print(...message)
  29. process.stderr.write(EOL)
  30. }
  31. export function print(...message: string[]) {
  32. blank = false
  33. process.stderr.write(message.join(" "))
  34. }
  35. let blank = false
  36. export function empty() {
  37. if (blank) return
  38. println("" + Style.TEXT_NORMAL)
  39. blank = true
  40. }
  41. export function logo(pad?: string) {
  42. if (!process.stdout.isTTY && !process.stderr.isTTY) {
  43. const result = []
  44. for (const row of wordmark) {
  45. if (pad) result.push(pad)
  46. result.push(row)
  47. result.push(EOL)
  48. }
  49. return result.join("").trimEnd()
  50. }
  51. const result: string[] = []
  52. const reset = "\x1b[0m"
  53. const left = {
  54. fg: "\x1b[90m",
  55. shadow: "\x1b[38;5;235m",
  56. bg: "\x1b[48;5;235m",
  57. }
  58. const right = {
  59. fg: reset,
  60. shadow: "\x1b[38;5;238m",
  61. bg: "\x1b[48;5;238m",
  62. }
  63. const gap = " "
  64. const draw = (line: string, fg: string, shadow: string, bg: string) => {
  65. const parts: string[] = []
  66. for (const char of line) {
  67. if (char === "_") {
  68. parts.push(bg, " ", reset)
  69. continue
  70. }
  71. if (char === "^") {
  72. parts.push(fg, bg, "▀", reset)
  73. continue
  74. }
  75. if (char === "~") {
  76. parts.push(shadow, "▀", reset)
  77. continue
  78. }
  79. if (char === " ") {
  80. parts.push(" ")
  81. continue
  82. }
  83. parts.push(fg, char, reset)
  84. }
  85. return parts.join("")
  86. }
  87. glyphs.left.forEach((row, index) => {
  88. if (pad) result.push(pad)
  89. result.push(draw(row, left.fg, left.shadow, left.bg))
  90. result.push(gap)
  91. const other = glyphs.right[index] ?? ""
  92. result.push(draw(other, right.fg, right.shadow, right.bg))
  93. result.push(EOL)
  94. })
  95. return result.join("").trimEnd()
  96. }
  97. export async function input(prompt: string): Promise<string> {
  98. const readline = require("readline")
  99. const rl = readline.createInterface({
  100. input: process.stdin,
  101. output: process.stdout,
  102. })
  103. return new Promise((resolve) => {
  104. rl.question(prompt, (answer: string) => {
  105. rl.close()
  106. resolve(answer.trim())
  107. })
  108. })
  109. }
  110. export function error(message: string) {
  111. if (message.startsWith("Error: ")) {
  112. message = message.slice("Error: ".length)
  113. }
  114. println(Style.TEXT_DANGER_BOLD + "Error: " + Style.TEXT_NORMAL + message)
  115. }
  116. export function markdown(text: string): string {
  117. return text
  118. }
  119. export * as UI from "./ui"