diff --git a/.commitlintrc b/.commitlintrc new file mode 100644 index 0000000..c30e5a9 --- /dev/null +++ b/.commitlintrc @@ -0,0 +1,3 @@ +{ + "extends": ["@commitlint/config-conventional"] +} diff --git a/.czrc b/.czrc new file mode 100644 index 0000000..d1bcc20 --- /dev/null +++ b/.czrc @@ -0,0 +1,3 @@ +{ + "path": "cz-conventional-changelog" +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4a7ea30 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..0166703 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [xinyao27] diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..5c51a36 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,26 @@ +name: Lint + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install bun + uses: oven-sh/setup-bun@v1 + + - name: Install + run: bun install + + - name: Lint + run: bun run lint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..5d4d6f7 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: Test + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + fail-fast: false + + steps: + - uses: actions/checkout@v4 + + - name: Install bun + uses: oven-sh/setup-bun@v1 + + - name: Install + run: bun install + + - name: Build + run: bun run build + + - name: Test + run: bun run test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f88c4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# compiled output +dist +node_modules + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +coverage +/.nyc_output + +# IDEs and editors +.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# config +.env diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..36af219 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx lint-staged diff --git a/.lintstagedrc b/.lintstagedrc new file mode 100644 index 0000000..fd8d9ec --- /dev/null +++ b/.lintstagedrc @@ -0,0 +1,6 @@ +{ + "*.{js?(x),ts?(x),vue,html,md,json,yml}": [ + "eslint --fix", + "git add" + ] +} diff --git a/.release-it.json b/.release-it.json new file mode 100644 index 0000000..3749272 --- /dev/null +++ b/.release-it.json @@ -0,0 +1,11 @@ +{ + "git": { + "commitMessage": "chore: release v${version}" + }, + "plugins": { + "@release-it/conventional-changelog": { + "preset": "angular", + "infile": "CHANGELOG.md" + } + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..11c711e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 xinyao27 + +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..c949fa6 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# [name] + +[![NPM version](https://img.shields.io/npm/v/[name]?color=a1b858&label=)](https://www.npmjs.com/package/[name]) + +```ts +const a = 1 +``` + +## License + +[MIT](./LICENSE) License © 2022 [xinyao27](https://github.com/xinyao27) diff --git a/build.config.ts b/build.config.ts new file mode 100644 index 0000000..6ab75ac --- /dev/null +++ b/build.config.ts @@ -0,0 +1,17 @@ +import { defineBuildConfig } from 'unbuild' +import pkg from './package.json' + +export default defineBuildConfig({ + entries: ['src/index'], + declaration: true, + clean: true, + rollup: { + emitCJS: true, + esbuild: { + minify: true, + }, + }, + replace: { + 'process.env.VERSION': JSON.stringify(pkg.version), + }, +}) diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..68a503e Binary files /dev/null and b/bun.lockb differ diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..e0043d7 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,3 @@ +import { all } from '@xystack/style-guide/eslint' + +export default all diff --git a/package.json b/package.json new file mode 100644 index 0000000..0f6e814 --- /dev/null +++ b/package.json @@ -0,0 +1,78 @@ +{ + "name": "cli-high", + "version": "0.0.0", + "description": "tiny cli highlighter", + "type": "module", + "keywords": [ + "cli", + "highlighter", + "highlight", + "color", + "terminal", + "console" + ], + "license": "MIT", + "homepage": "https://github.com/xinyao27/cli-high#readme", + "bugs": { + "url": "https://github.com/xinyao27/cli-high/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/xinyao27/cli-high.git" + }, + "author": { + "name": "xinyao", + "email": "hi@xinyao.me" + }, + "funding": "https://github.com/sponsors/xinyao27", + "files": [ + "dist/*", + "package.json" + ], + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.cjs", + "import": "./dist/index.mjs" + } + }, + "sideEffects": false, + "scripts": { + "build": "unbuild", + "stub": "unbuild --stub", + "typecheck": "tsc --noEmit", + "lint": "nr typecheck && eslint . --fix", + "prepublishOnly": "nr build", + "release": "release-it", + "start": "bun run src/index.ts", + "test": "bun test", + "preinstall": "npx only-allow bun", + "up": "taze major -I", + "prepare": "husky install" + }, + "dependencies": { + "picocolors": "^1.0.0", + "sugar-high": "^0.6.1" + }, + "devDependencies": { + "@antfu/ni": "^0.21.12", + "@commitlint/cli": "^19.3.0", + "@release-it/conventional-changelog": "^8.0.1", + "@types/bun": "^1.1.1", + "@xystack/style-guide": "^0.0.4", + "commitizen": "^4.3.0", + "cz-conventional-changelog": "^3.3.0", + "eslint": "^9.2.0", + "husky": "^9.0.11", + "lint-staged": "^15.2.2", + "prettier": "^3.2.5", + "release-it": "^17.2.1", + "taze": "^0.13.8", + "typescript": "^5.4.5", + "unbuild": "^2.0.0" + }, + "prettier": "@xystack/style-guide/prettier" +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..b2282d3 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,70 @@ +import { highlight as highlightAsHtml } from 'sugar-high' +import * as c from 'picocolors' + +export interface HighlightOptions { + showLineNumbers?: boolean +} +export function highlight(code: string, options: HighlightOptions = { showLineNumbers: false }) { + const html = highlightAsHtml(code) + const regex = /(.*)<\/span>/g + + let match + let result = '' + let i = 0 + + // eslint-disable-next-line no-cond-assign + while ((match = regex.exec(html)) !== null) { + const innerMatches = [...match[1].matchAll(/(.*?)<\/span>/gs)] + result += options.showLineNumbers ? `${c.dim(`${i}`.padEnd(2, ' '))} ` : '' + innerMatches.forEach((innerMatch) => { + const classType = innerMatch[1] + const text = decode(innerMatch[3]) + + switch (classType) { + case 'sh__token--identifier': + result += c.black(text) + break + case 'sh__token--keyword': + result += c.red(text) + break + case 'sh__token--string': + result += c.green(text) + break + case 'sh__token--class': + result += c.cyan(text) + break + case 'sh__token--property': + result += c.cyan(text) + break + case 'sh__token--entity': + result += c.magenta(text) + break + case 'sh__token--jsxliterals': + result += c.green(text) + break + case 'sh__token--sign': + result += c.dim(text) + break + case 'sh__token--comment': + result += c.dim(text) + break + default: + result += text + break + } + }) + result += '\n' + i++ + } + + return result +} + +function decode(str: string) { + return str + .replace('&', '&') + .replace('<', '<') + .replace('>', '>') + .replace('"', '"') + .replace(''', "'") +} diff --git a/test/__snapshots__/index.test.ts.snap b/test/__snapshots__/index.test.ts.snap new file mode 100644 index 0000000..daed061 --- /dev/null +++ b/test/__snapshots__/index.test.ts.snap @@ -0,0 +1,77 @@ +// Bun Snapshot v1, https://goo.gl/fbAQLP + +exports[`should work klassjs 1`] = ` +"/** +* @param {string} names +* @return {Promise} +*/ +async function notify(names) { + const tags = [] + for (let i = 0; i < names.length; i++) { + tags.push('@' + names[i]) + } + await ping(tags) +} +class SuperArray extends Array { + static core = Object.create(null) + constructor(...args) { super(...args); } + bump(value) { + return this.map( + x => x == undefined ? x + 1 : 0 + ).concat(value) + } +} +" +`; + +exports[`should work regexjs 1`] = ` +"export const test = (str) => /^/[0-5]/$/g.test(str) +// This is a super lightweight javascript syntax highlighter npm package +// This is a inline comment / <- a slash +///  + <Food + season={{ + sault: <p a={[{}]} /> + }}> +  + {/* jsx comment */} + <h1 className="title" data-title="true"> + Read{' '} + <Link href="/posts/first-post"> + <a>this page! - {Date.now()} +  +  +  +) +" +`; + +exports[`should work jsx with line numbers 1`] = ` +"0  const element = ( +1  <> +2  <Food +3  season={{ +4  sault: <p a={[{}]} /> +5  }}> +6   +7  {/* jsx comment */} +8  <h1 className="title" data-title="true"> +9   Read{' '} +10  <Link href="/posts/first-post"> +11  <a>this page! - {Date.now()} +12  +13  +14  +15 ) +" +`; diff --git a/test/index.test.ts b/test/index.test.ts new file mode 100644 index 0000000..de13200 --- /dev/null +++ b/test/index.test.ts @@ -0,0 +1,74 @@ +/* eslint-disable no-console */ +import { describe, expect, it } from 'bun:test' +import { highlight } from '../src/index' + +const klassjs = `/** +* @param {string} names +* @return {Promise} +*/ +async function notify(names) { + const tags = [] + for (let i = 0; i < names.length; i++) { + tags.push('@' + names[i]) + } + await ping(tags) +} +class SuperArray extends Array { + static core = Object.create(null) + constructor(...args) { super(...args); } + bump(value) { + return this.map( + x => x == undefined ? x + 1 : 0 + ).concat(value) + } +}` +const regexjs = `export const test = (str) => /^\/[0-5]\/$/g.test(str) +// This is a super lightweight javascript syntax highlighter npm package +// This is a inline comment / <- a slash +/// // reference comment +/* This is another comment */ alert('good') // <- alerts +// Invalid calculation: regex and numbers +const _in = 123 - /555/ + 444; +const _iu = /* evaluate */ (19) / 234 + 56 / 7;` +const jsx = `const element = ( + <> + + }}> + + {/* jsx comment */} +

+ Read{' '} + + this page! - {Date.now()} + +

+ +)` + +describe('should work', () => { + it('klassjs', () => { + const result = highlight(klassjs) + console.log(result) + expect(result).toMatchSnapshot() + }) + + it('regexjs', () => { + const result = highlight(regexjs) + console.log(result) + expect(result).toMatchSnapshot() + }) + + it('jsx', () => { + const result = highlight(jsx) + console.log(result) + expect(result).toMatchSnapshot() + }) + + it('jsx with line numbers', () => { + const result = highlight(jsx, { showLineNumbers: true }) + console.log(result) + expect(result).toMatchSnapshot() + }) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..11d53fc --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "@xystack/style-guide/typescript" +}