From 1e2f5f6d0b3411af0787201446e4e11b2cafb4a7 Mon Sep 17 00:00:00 2001 From: Etienne Deladonchamps Date: Sat, 14 Dec 2024 13:38:05 +0100 Subject: [PATCH] Init --- .github/workflows/publish.yml | 21 +++ .vscode/settings.json | 8 + LICENSE | 21 +++ README.md | 4 + deno.json | 11 ++ deno.lock | 3 + mod.ts | 5 + src/applyStyles.ts | 25 +++ src/base.ts | 50 ++++++ src/colors.ts | 293 ++++++++++++++++++++++++++++++++++ src/constants.ts | 1 + src/css.ts | 32 ++++ src/shortcuts.ts | 77 +++++++++ src/styledString.ts | 31 ++++ 14 files changed, 582 insertions(+) create mode 100644 .github/workflows/publish.yml create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 README.md create mode 100644 deno.json create mode 100644 deno.lock create mode 100644 mod.ts create mode 100644 src/applyStyles.ts create mode 100644 src/base.ts create mode 100644 src/colors.ts create mode 100644 src/constants.ts create mode 100644 src/css.ts create mode 100644 src/shortcuts.ts create mode 100644 src/styledString.ts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..d9e0055 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,21 @@ +name: Publish + +on: + push: + branches: + - main + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # The OIDC ID token is used for authentication with JSR. + steps: + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v1 + with: + deno-version: v2.x # Run with latest stable Deno. + + - run: deno task check + - run: npx jsr publish diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a8ca28e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "deno.enable": true, + "editor.defaultFormatter": "denoland.vscode-deno", + "npm.autoDetect": "off", + "[typescript]": { + "editor.defaultFormatter": "denoland.vscode-deno" + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..12c6e51 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Etienne Dldc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..099a672 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# CSS console + +Print styled text in the console using CSS styles +([using `%c` flag](https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output)). diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..8001ee9 --- /dev/null +++ b/deno.json @@ -0,0 +1,11 @@ +{ + "name": "@dldc/console-colors", + "version": "0.1.0", + "exports": "./mod.ts", + "tasks": { + "update": "deno run -A jsr:@molt/cli", + "update:commit": "deno task -q update --commit", + "check": "deno check **/*.ts && deno fmt --check && deno lint" + }, + "imports": {} +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..a39b837 --- /dev/null +++ b/deno.lock @@ -0,0 +1,3 @@ +{ + "version": "4" +} diff --git a/mod.ts b/mod.ts new file mode 100644 index 0000000..8bd9b76 --- /dev/null +++ b/mod.ts @@ -0,0 +1,5 @@ +export * from "./src/applyStyles.ts"; +export * from "./src/base.ts"; +export * from "./src/colors.ts"; +export * from "./src/css.ts"; +export * from "./src/styledString.ts"; diff --git a/src/applyStyles.ts b/src/applyStyles.ts new file mode 100644 index 0000000..8202465 --- /dev/null +++ b/src/applyStyles.ts @@ -0,0 +1,25 @@ +import { CSS_SUBSTITUTIONS } from "./constants.ts"; +import type { TConsoleStyles } from "./css.ts"; +import { styledString, type TStyledString } from "./styledString.ts"; + +export function applyStyles( + value: string | TStyledString, + styles: TConsoleStyles, +): TStyledString { + if (typeof value === "string") { + if (value.includes(CSS_SUBSTITUTIONS)) { + throw new Error("Unexpected CSS_SUBSTITUTIONS in content"); + } + return styledString(CSS_SUBSTITUTIONS + value, [styles]); + } + if (value.content.startsWith(CSS_SUBSTITUTIONS)) { + return styledString( + value.content, + value.styles.map((s) => ({ ...s, ...styles })), + ); + } + return styledString(CSS_SUBSTITUTIONS + value.content, [ + styles, + ...value.styles.map((s) => ({ ...s, ...styles })), + ]); +} diff --git a/src/base.ts b/src/base.ts new file mode 100644 index 0000000..225c144 --- /dev/null +++ b/src/base.ts @@ -0,0 +1,50 @@ +import { CSS_SUBSTITUTIONS } from "./constants.ts"; +import type { TConsoleStyles } from "./css.ts"; +import { styledString, type TStyledString } from "./styledString.ts"; + +// template literal fn +export function base( + strings: TemplateStringsArray, + ...values: (string | TStyledString)[] +): TStyledString { + const parts: (string | TStyledString)[] = []; + const addPart = (part: string | TStyledString) => { + if (part === "") { + return; + } + const lastPart = parts[parts.length - 1]; + if (typeof lastPart === "string") { + parts[parts.length - 1] = lastPart + part; + } else { + parts.push(part); + } + }; + + for (let i = 0; i < strings.length; i++) { + addPart(strings[i]); + if (i < values.length) { + addPart(values[i]); + } + } + + let content = ""; + const styles: TConsoleStyles[] = []; + let onlyString = true; + + parts.forEach((part) => { + if (typeof part === "string") { + if (onlyString) { + content += part; + return; + } + content += CSS_SUBSTITUTIONS + part; + styles.push({}); + return; + } + onlyString = false; + content += part.content; + styles.push(...part.styles); + }); + + return styledString(content, styles); +} diff --git a/src/colors.ts b/src/colors.ts new file mode 100644 index 0000000..695b1a1 --- /dev/null +++ b/src/colors.ts @@ -0,0 +1,293 @@ +/** + * Tailwind colors + * https://github.com/tailwindlabs/tailwindcss/blob/f875ab9706cae8262e15e5b382580fc8e2d4197f/src/public/colors.js + */ + +export const COLORS = { + slate: { + 50: "#f8fafc", + 100: "#f1f5f9", + 200: "#e2e8f0", + 300: "#cbd5e1", + 400: "#94a3b8", + 500: "#64748b", + 600: "#475569", + 700: "#334155", + 800: "#1e293b", + 900: "#0f172a", + 950: "#020617", + }, + gray: { + 50: "#f9fafb", + 100: "#f3f4f6", + 200: "#e5e7eb", + 300: "#d1d5db", + 400: "#9ca3af", + 500: "#6b7280", + 600: "#4b5563", + 700: "#374151", + 800: "#1f2937", + 900: "#111827", + 950: "#030712", + }, + zinc: { + 50: "#fafafa", + 100: "#f4f4f5", + 200: "#e4e4e7", + 300: "#d4d4d8", + 400: "#a1a1aa", + 500: "#71717a", + 600: "#52525b", + 700: "#3f3f46", + 800: "#27272a", + 900: "#18181b", + 950: "#09090b", + }, + neutral: { + 50: "#fafafa", + 100: "#f5f5f5", + 200: "#e5e5e5", + 300: "#d4d4d4", + 400: "#a3a3a3", + 500: "#737373", + 600: "#525252", + 700: "#404040", + 800: "#262626", + 900: "#171717", + 950: "#0a0a0a", + }, + stone: { + 50: "#fafaf9", + 100: "#f5f5f4", + 200: "#e7e5e4", + 300: "#d6d3d1", + 400: "#a8a29e", + 500: "#78716c", + 600: "#57534e", + 700: "#44403c", + 800: "#292524", + 900: "#1c1917", + 950: "#0c0a09", + }, + red: { + 50: "#fef2f2", + 100: "#fee2e2", + 200: "#fecaca", + 300: "#fca5a5", + 400: "#f87171", + 500: "#ef4444", + 600: "#dc2626", + 700: "#b91c1c", + 800: "#991b1b", + 900: "#7f1d1d", + 950: "#450a0a", + }, + orange: { + 50: "#fff7ed", + 100: "#ffedd5", + 200: "#fed7aa", + 300: "#fdba74", + 400: "#fb923c", + 500: "#f97316", + 600: "#ea580c", + 700: "#c2410c", + 800: "#9a3412", + 900: "#7c2d12", + 950: "#431407", + }, + amber: { + 50: "#fffbeb", + 100: "#fef3c7", + 200: "#fde68a", + 300: "#fcd34d", + 400: "#fbbf24", + 500: "#f59e0b", + 600: "#d97706", + 700: "#b45309", + 800: "#92400e", + 900: "#78350f", + 950: "#451a03", + }, + yellow: { + 50: "#fefce8", + 100: "#fef9c3", + 200: "#fef08a", + 300: "#fde047", + 400: "#facc15", + 500: "#eab308", + 600: "#ca8a04", + 700: "#a16207", + 800: "#854d0e", + 900: "#713f12", + 950: "#422006", + }, + lime: { + 50: "#f7fee7", + 100: "#ecfccb", + 200: "#d9f99d", + 300: "#bef264", + 400: "#a3e635", + 500: "#84cc16", + 600: "#65a30d", + 700: "#4d7c0f", + 800: "#3f6212", + 900: "#365314", + 950: "#1a2e05", + }, + green: { + 50: "#f0fdf4", + 100: "#dcfce7", + 200: "#bbf7d0", + 300: "#86efac", + 400: "#4ade80", + 500: "#22c55e", + 600: "#16a34a", + 700: "#15803d", + 800: "#166534", + 900: "#14532d", + 950: "#052e16", + }, + emerald: { + 50: "#ecfdf5", + 100: "#d1fae5", + 200: "#a7f3d0", + 300: "#6ee7b7", + 400: "#34d399", + 500: "#10b981", + 600: "#059669", + 700: "#047857", + 800: "#065f46", + 900: "#064e3b", + 950: "#022c22", + }, + teal: { + 50: "#f0fdfa", + 100: "#ccfbf1", + 200: "#99f6e4", + 300: "#5eead4", + 400: "#2dd4bf", + 500: "#14b8a6", + 600: "#0d9488", + 700: "#0f766e", + 800: "#115e59", + 900: "#134e4a", + 950: "#042f2e", + }, + cyan: { + 50: "#ecfeff", + 100: "#cffafe", + 200: "#a5f3fc", + 300: "#67e8f9", + 400: "#22d3ee", + 500: "#06b6d4", + 600: "#0891b2", + 700: "#0e7490", + 800: "#155e75", + 900: "#164e63", + 950: "#083344", + }, + sky: { + 50: "#f0f9ff", + 100: "#e0f2fe", + 200: "#bae6fd", + 300: "#7dd3fc", + 400: "#38bdf8", + 500: "#0ea5e9", + 600: "#0284c7", + 700: "#0369a1", + 800: "#075985", + 900: "#0c4a6e", + 950: "#082f49", + }, + blue: { + 50: "#eff6ff", + 100: "#dbeafe", + 200: "#bfdbfe", + 300: "#93c5fd", + 400: "#60a5fa", + 500: "#3b82f6", + 600: "#2563eb", + 700: "#1d4ed8", + 800: "#1e40af", + 900: "#1e3a8a", + 950: "#172554", + }, + indigo: { + 50: "#eef2ff", + 100: "#e0e7ff", + 200: "#c7d2fe", + 300: "#a5b4fc", + 400: "#818cf8", + 500: "#6366f1", + 600: "#4f46e5", + 700: "#4338ca", + 800: "#3730a3", + 900: "#312e81", + 950: "#1e1b4b", + }, + violet: { + 50: "#f5f3ff", + 100: "#ede9fe", + 200: "#ddd6fe", + 300: "#c4b5fd", + 400: "#a78bfa", + 500: "#8b5cf6", + 600: "#7c3aed", + 700: "#6d28d9", + 800: "#5b21b6", + 900: "#4c1d95", + 950: "#2e1065", + }, + purple: { + 50: "#faf5ff", + 100: "#f3e8ff", + 200: "#e9d5ff", + 300: "#d8b4fe", + 400: "#c084fc", + 500: "#a855f7", + 600: "#9333ea", + 700: "#7e22ce", + 800: "#6b21a8", + 900: "#581c87", + 950: "#3b0764", + }, + fuchsia: { + 50: "#fdf4ff", + 100: "#fae8ff", + 200: "#f5d0fe", + 300: "#f0abfc", + 400: "#e879f9", + 500: "#d946ef", + 600: "#c026d3", + 700: "#a21caf", + 800: "#86198f", + 900: "#701a75", + 950: "#4a044e", + }, + pink: { + 50: "#fdf2f8", + 100: "#fce7f3", + 200: "#fbcfe8", + 300: "#f9a8d4", + 400: "#f472b6", + 500: "#ec4899", + 600: "#db2777", + 700: "#be185d", + 800: "#9d174d", + 900: "#831843", + 950: "#500724", + }, + rose: { + 50: "#fff1f2", + 100: "#ffe4e6", + 200: "#fecdd3", + 300: "#fda4af", + 400: "#fb7185", + 500: "#f43f5e", + 600: "#e11d48", + 700: "#be123c", + 800: "#9f1239", + 900: "#881337", + 950: "#4c0519", + }, +} as const; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..1bcc485 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const CSS_SUBSTITUTIONS = "%c"; diff --git a/src/css.ts b/src/css.ts new file mode 100644 index 0000000..4321b65 --- /dev/null +++ b/src/css.ts @@ -0,0 +1,32 @@ +export interface TConsoleStyles { + backgroundColor?: string; + borderWidth?: string; + borderColor?: string; + borderStyle?: string; + borderRadius?: string; + color?: string; + margin?: string; + marginLeft?: string; + marginRight?: string; + marginTop?: string; + marginBottom?: string; + padding?: string; + paddingLeft?: string; + paddingRight?: string; + paddingTop?: string; + paddingBottom?: string; + textDecoration?: string; + textTransform?: string; + font?: string; + fontFamily?: string; + fontSize?: string; + fontStyle?: string; + fontWeight?: string; + lineHeight?: string; +} + +export function printStyles(styles: TConsoleStyles): string { + return Object.entries(styles) + .map(([key, value]) => `${key}: ${value}`) + .join("; "); +} diff --git a/src/shortcuts.ts b/src/shortcuts.ts new file mode 100644 index 0000000..f5e91e9 --- /dev/null +++ b/src/shortcuts.ts @@ -0,0 +1,77 @@ +import { COLORS } from "./colors.ts"; +import type { TConsoleStyles } from "./css.ts"; + +const TW_LIGHT = 300; +const TW_REGULAR = 400; +const TW_DARK = 700; + +export const SHORTCUTS = { + bold: { fontWeight: "bold" }, + italic: { fontStyle: "italic" }, + slateLight: { color: COLORS.slate[TW_LIGHT] }, + slate: { color: COLORS.slate[TW_REGULAR] }, + slateDark: { color: COLORS.slate[TW_DARK] }, + grayLight: { color: COLORS.gray[TW_LIGHT] }, + gray: { color: COLORS.gray[TW_REGULAR] }, + grayDark: { color: COLORS.gray[TW_DARK] }, + zincLight: { color: COLORS.zinc[TW_LIGHT] }, + zinc: { color: COLORS.zinc[TW_REGULAR] }, + zincDark: { color: COLORS.zinc[TW_DARK] }, + neutralLight: { color: COLORS.neutral[TW_LIGHT] }, + neutral: { color: COLORS.neutral[TW_REGULAR] }, + neutralDark: { color: COLORS.neutral[TW_DARK] }, + stoneLight: { color: COLORS.stone[TW_LIGHT] }, + stone: { color: COLORS.stone[TW_REGULAR] }, + stoneDark: { color: COLORS.stone[TW_DARK] }, + redLight: { color: COLORS.red[TW_LIGHT] }, + red: { color: COLORS.red[TW_REGULAR] }, + redDark: { color: COLORS.red[TW_DARK] }, + orangeLight: { color: COLORS.orange[TW_LIGHT] }, + orange: { color: COLORS.orange[TW_REGULAR] }, + orangeDark: { color: COLORS.orange[TW_DARK] }, + amberLight: { color: COLORS.amber[TW_LIGHT] }, + amber: { color: COLORS.amber[TW_REGULAR] }, + amberDark: { color: COLORS.amber[TW_DARK] }, + yellowLight: { color: COLORS.yellow[TW_LIGHT] }, + yellow: { color: COLORS.yellow[TW_REGULAR] }, + yellowDark: { color: COLORS.yellow[TW_DARK] }, + limeLight: { color: COLORS.lime[TW_LIGHT] }, + lime: { color: COLORS.lime[TW_REGULAR] }, + limeDark: { color: COLORS.lime[TW_DARK] }, + greenLight: { color: COLORS.green[TW_LIGHT] }, + green: { color: COLORS.green[TW_REGULAR] }, + greenDark: { color: COLORS.green[TW_DARK] }, + emeraldLight: { color: COLORS.emerald[TW_LIGHT] }, + emerald: { color: COLORS.emerald[TW_REGULAR] }, + emeraldDark: { color: COLORS.emerald[TW_DARK] }, + tealLight: { color: COLORS.teal[TW_LIGHT] }, + teal: { color: COLORS.teal[TW_REGULAR] }, + tealDark: { color: COLORS.teal[TW_DARK] }, + cyanLight: { color: COLORS.cyan[TW_LIGHT] }, + cyan: { color: COLORS.cyan[TW_REGULAR] }, + cyanDark: { color: COLORS.cyan[TW_DARK] }, + skyLight: { color: COLORS.sky[TW_LIGHT] }, + sky: { color: COLORS.sky[TW_REGULAR] }, + skyDark: { color: COLORS.sky[TW_DARK] }, + blueLight: { color: COLORS.blue[TW_LIGHT] }, + blue: { color: COLORS.blue[TW_REGULAR] }, + blueDark: { color: COLORS.blue[TW_DARK] }, + indigoLight: { color: COLORS.indigo[TW_LIGHT] }, + indigo: { color: COLORS.indigo[TW_REGULAR] }, + indigoDark: { color: COLORS.indigo[TW_DARK] }, + violetLight: { color: COLORS.violet[TW_LIGHT] }, + violet: { color: COLORS.violet[TW_REGULAR] }, + violetDark: { color: COLORS.violet[TW_DARK] }, + purpleLight: { color: COLORS.purple[TW_LIGHT] }, + purple: { color: COLORS.purple[TW_REGULAR] }, + purpleDark: { color: COLORS.purple[TW_DARK] }, + fuchsiaLight: { color: COLORS.fuchsia[TW_LIGHT] }, + fuchsia: { color: COLORS.fuchsia[TW_REGULAR] }, + fuchsiaDark: { color: COLORS.fuchsia[TW_DARK] }, + pinkLight: { color: COLORS.pink[TW_LIGHT] }, + pink: { color: COLORS.pink[TW_REGULAR] }, + pinkDark: { color: COLORS.pink[TW_DARK] }, + roseLight: { color: COLORS.rose[TW_LIGHT] }, + rose: { color: COLORS.rose[TW_REGULAR] }, + roseDark: { color: COLORS.rose[TW_DARK] }, +} satisfies Record; diff --git a/src/styledString.ts b/src/styledString.ts new file mode 100644 index 0000000..d3bacf8 --- /dev/null +++ b/src/styledString.ts @@ -0,0 +1,31 @@ +import { printStyles, type TConsoleStyles } from "./css.ts"; + +const STYLED_STRING = Symbol("StyledString"); + +export interface TStyledString { + [STYLED_STRING]: true; + content: string; + styles: TConsoleStyles[]; + log: () => string[]; + toString: () => string; +} + +export function styledString( + content: string, + styles: TConsoleStyles[], +): TStyledString { + return { + [STYLED_STRING]: true, + content, + styles, + log() { + return [content, ...styles.map(printStyles)]; + }, + toString() { + return content; + }, + [Symbol.for("Deno.customInspect")]() { + return content; + }, + }; +}