From 3b4d3d2036a1298d6616e632b57fcb595903851a Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Wed, 19 Jun 2019 00:02:34 +0200 Subject: [PATCH] code --- .gitignore | 6 +++ .npmignore | 2 + package.json | 31 +++++++++++ src/index.ts | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 26 +++++++++ 5 files changed, 210 insertions(+) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bcc957f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +package-lock.json +.idea + +lib/ +test/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..b180318 --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +* +!lib/* \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6003a1d --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "clitastic", + "version": "0.0.1", + "description": "Interactive CLI utility", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/CupCakeArmy/clitastic" + }, + "scripts": { + "build": "tsc", + "dev": "tsnd --respawn --no-notify test/main.ts", + "prepublishOnly": "rm -rf ./lib && npm run build" + }, + "keywords": [ + "cli", + "interface", + "interactive" + ], + "author": "Niccolo Borgioli", + "license": "MIT", + "devDependencies": { + "@types/node": "^12", + "ts-node-dev": "^1.0.0-pre", + "typescript": "^3.5" + }, + "dependencies": { + "colors": "^1.3.3" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0dbac1d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,145 @@ +import 'colors' +import readline from 'readline' +import Timeout = NodeJS.Timeout + +export const sleep = (time: number) => new Promise(resolve => setTimeout(resolve, time * 1000)) + +type CLIWriterStream = NodeJS.ReadWriteStream + + +export class Writer { + private rl: readline.Interface + private readonly output: CLIWriterStream + private readonly input: CLIWriterStream + private lines: number = 1 + + constructor(initial?: string, output: CLIWriterStream = process.stdout, input: CLIWriterStream = process.stdin) { + this.output = output + this.input = input + this.rl = this.create() + + if (initial) + this.update(initial) + } + + replaceAll(data?: string): this { + readline.moveCursor(this.output, 0, -(this.lines -1 )) + readline.cursorTo(this.output, 0) + readline.clearScreenDown(this.output) + this.update(data) + return this + } + + replaceLn(data?: string): this { + readline.clearLine(this.output, 0) + readline.cursorTo(this.output, 0) + this.lines-- + this.update(data) + return this + } + + append(data?: string): this { + this.update(data) + return this + } + + appendLn(data?: string): this { + this.append('\n' + (data || '')) + return this + } + + done(data?: string) { + if (data) + this.replaceLn(data) + this.append('\n') + this.rl.close() + // console.log('lines', this.lines) + } + + private create() { + return readline.createInterface({ + input: this.input, + output: this.output, + }) + } + + private update(data?: string) { + if (!data) return + this.lines += data.split(/\r\n|\r|\n/).length - 1 + this.rl.write(data) + } +} + + +export class Spinner { + private readonly chars: string[] + private readonly speed: number + private readonly writer: Writer + private readonly timeout: Timeout + private counter: number = 0 + + constructor(chars: string | string[] = '◝◞◟◜', speed: number = 100) { + this.chars = Array.isArray(chars) ? chars : chars.split('') + this.speed = speed + this.writer = new Writer(this.getNextChar()) + this.timeout = setInterval(() => { + this.update() + }, speed) + } + + done() { + this.writer.done() + clearInterval(this.timeout) + } + + private getNextChar(): string { + const c = this.chars[this.counter] + this.counter = (this.counter + 1) % this.chars.length + return c + } + + private update() { + this.writer.replaceLn(this.getNextChar()) + } +} + + +export class List { + static readonly DefaultIcon: string = '○' + private readonly writer: Writer + private icon: string | string[] + private items: any[] + + constructor(items: any[], icon: string | string[] = List.DefaultIcon) { + this.writer = new Writer() + this.items = items + this.icon = icon + + this.render() + } + + update(items: any[], icon: string | string[] = List.DefaultIcon): this { + this.items = items + this.icon = icon + this.render() + return this + } + + done() { + this.writer.done() + } + + private render() { + this.writer.replaceAll() + let first = true + for (let i = 0; i < this.items.length; ++i) { + const text = `${Array.isArray(this.icon) ? this.icon[i] : this.icon} ${String(this.items[i])}` + if (first) { + this.writer.append(text) + first = false + } else + this.writer.appendLn(text) + } + // this.writer.appendLn() + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e2a9b51 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "commonjs", + "declaration": true, + "outDir": "./lib", + "removeComments": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "test" + ] +}