tinyinput 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ name: Code quality
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ quality:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v5
15
+ with:
16
+ persist-credentials: false
17
+ - name: Setup Biome
18
+ uses: biomejs/setup-biome@v2
19
+ with:
20
+ version: latest
21
+ - name: Run Biome
22
+ run: biome ci .
package/README.md CHANGED
@@ -27,20 +27,73 @@ npm install tinyinput
27
27
  **Usage**
28
28
  -----
29
29
 
30
- To use TinyInput, simply import the library and call the `input()` function:
30
+ To use TinyInput, import the library and call the `input()` function. By default, it returns a string and ensures the input is not empty.
31
+
31
32
  ```ts
32
33
  import { input } from 'tinyinput';
33
34
 
34
- async function main(){
35
- const userInput = await input('Please enter your name: ', opt);
36
- console.log(`Hello, ${userInput}!`);
37
- }
35
+ async function main() {
36
+ // Basic usage (returns string)
37
+ const name = await input('What is your name? ');
38
+ console.log(`Hello, ${name}!`);
39
+
40
+ // Integer input (retries until valid)
41
+ const age = await input('How old are you? ', 'int');
42
+ console.log(`Next year you will be ${age + 1}`);
43
+
44
+ // Float input (retries until valid)
45
+ const price = await input('Enter price: ', 'float');
46
+ console.log(`Total with tax: ${(price * 1.15).toFixed(2)}`);
47
+
48
+ // Password input (hides typing)
49
+ const password = await input('Enter password: ', 'password');
50
+ console.log('Securely received password');
38
51
 
39
- main()
52
+ // Email validation
53
+ const email = await input("Enter email: ", "email");
54
+ console.log(`Updating record for ${email}`);
40
55
 
41
- //opt can be "int" | "float" | "string"
56
+ // Confirmation (returns boolean)
57
+ const save = await confirm("Save changes?");
58
+ if (save) {
59
+ console.log("Saved!");
60
+ }
61
+
62
+ // Selection (returns the string choice)
63
+ const color = await select("Pick a color", ["Red", "Green", "Blue"]);
64
+ console.log(`You chose ${color}`);
65
+ }
66
+
67
+ main();
42
68
  ```
43
69
 
70
+ **API Reference**
71
+ -----------
72
+
73
+ ### `input(question, opt)`
74
+
75
+ The main function for reading input. Returns a `Promise<string | number>`.
76
+
77
+ * **question** (string): The prompt to show.
78
+ * **opt** (string, optional):
79
+ * `"string"` (default): Returns trimmed, non-empty string.
80
+ * `"int"`: Parses and returns a valid integer.
81
+ * `"float"`: Parses and returns a valid number.
82
+ * `"password"`: Hides input while typing.
83
+ * `"email"`: Validates email format.
84
+
85
+ ### `confirm(question, defaultValue?)`
86
+
87
+ Simplified helper for yes/no questions. Returns a `Promise<boolean>`.
88
+
89
+ * **defaultValue** (boolean, optional): Defaults to `true`. Used if the user just presses Enter.
90
+
91
+ ### `select(question, choices)`
92
+
93
+ Displays a numbered list of choices. Returns a `Promise<string>`.
94
+
95
+ * **choices** (string[]): A non-empty array of options to choose from.
96
+
44
97
  **Technologies**
45
98
  -------------
46
99
 
package/biome.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "files": {
9
+ "includes": ["**", "!!**/dist"]
10
+ },
11
+ "formatter": {
12
+ "enabled": true,
13
+ "indentStyle": "tab"
14
+ },
15
+ "linter": {
16
+ "enabled": true,
17
+ "rules": {
18
+ "recommended": true
19
+ }
20
+ },
21
+ "javascript": {
22
+ "formatter": {
23
+ "quoteStyle": "double"
24
+ }
25
+ },
26
+ "assist": {
27
+ "enabled": true,
28
+ "actions": {
29
+ "source": {
30
+ "organizeImports": "on"
31
+ }
32
+ }
33
+ }
34
+ }
package/dist/index.d.mts CHANGED
@@ -1,5 +1,7 @@
1
- type Opt = "int" | "float" | "string";
1
+ type Opt = "int" | "float" | "string" | "password" | "email";
2
2
 
3
- declare function input(question: string, opt: Opt): Promise<string | number>;
3
+ declare function input(question: string, opt?: Opt): Promise<string | number>;
4
+ declare function confirm(question: string, defaultValue?: boolean): Promise<boolean>;
5
+ declare function select(question: string, choices: string[]): Promise<string>;
4
6
 
5
- export { input };
7
+ export { confirm, input, select };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- type Opt = "int" | "float" | "string";
1
+ type Opt = "int" | "float" | "string" | "password" | "email";
2
2
 
3
- declare function input(question: string, opt: Opt): Promise<string | number>;
3
+ declare function input(question: string, opt?: Opt): Promise<string | number>;
4
+ declare function confirm(question: string, defaultValue?: boolean): Promise<boolean>;
5
+ declare function select(question: string, choices: string[]): Promise<string>;
4
6
 
5
- export { input };
7
+ export { confirm, input, select };
package/dist/index.js CHANGED
@@ -1,59 +1,2 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- input: () => input
24
- });
25
- module.exports = __toCommonJS(index_exports);
26
- var import_promises = require("readline/promises");
27
- async function input(question, opt) {
28
- const r1 = (0, import_promises.createInterface)({
29
- input: process.stdin,
30
- output: process.stdout
31
- });
32
- let value;
33
- let parse;
34
- while (true) {
35
- value = await r1.question(question);
36
- r1.close();
37
- value = value.trim();
38
- if (opt === "int") {
39
- parse = parseInt(value, 10);
40
- if (!Number.isNaN(parse)) break;
41
- console.log("Please enter a valid integer");
42
- } else if (opt === "float") {
43
- parse = parseFloat(value);
44
- if (!Number.isNaN(value)) break;
45
- console.log("Please enter a valid number");
46
- } else {
47
- if (value.length > 0) {
48
- parse = value;
49
- break;
50
- }
51
- console.log("Please enter a non-empty string.");
52
- }
53
- }
54
- return parse;
55
- }
56
- // Annotate the CommonJS export names for ESM import in node:
57
- 0 && (module.exports = {
58
- input
59
- });
1
+ "use strict";var a=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var g=(r,e)=>{for(var s in e)a(r,s,{get:e[s],enumerable:!0})},d=(r,e,s,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of p(e))!m.call(r,t)&&t!==s&&a(r,t,{get:()=>e[t],enumerable:!(n=f(e,t))||n.enumerable});return r};var w=r=>d(a({},"__esModule",{value:!0}),r);var P={};g(P,{confirm:()=>b,input:()=>y,select:()=>N});module.exports=w(P);var l=require("readline/promises"),u=require("stream");async function y(r,e="string"){let s=!1,n=new u.Writable({write:(o,i,c)=>{s||process.stdout.write(o,i),c()}}),t=(0,l.createInterface)({input:process.stdin,output:n,terminal:!0});try{for(;;){e==="password"&&(process.stdout.write(r),s=!0);let o=await t.question(e==="password"?"":r);if(e==="password"&&(s=!1,process.stdout.write(`
2
+ `)),o=o.trim(),e==="int"){let i=parseInt(o,10);if(!Number.isNaN(i))return i;console.log("Please enter a valid integer")}else if(e==="float"){let i=parseFloat(o);if(!Number.isNaN(i))return i;console.log("Please enter a valid number")}else if(e==="email"){if(/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(o))return o;console.log("Please enter a valid email address")}else{if(o.length>0)return o;console.log(e==="password"?"Password cannot be empty":"Please enter a non-empty string.")}}}finally{t.close()}}async function b(r,e=!0){let s=(0,l.createInterface)({input:process.stdin,output:process.stdout});try{let n=e?" [Y/n] ":" [y/N] ";for(;;){let t=(await s.question(r+n)).toLowerCase().trim();if(t==="")return e;if(t==="y"||t==="yes"||t==="true")return!0;if(t==="n"||t==="no"||t==="false")return!1;console.log("Please enter 'y' or 'n'")}}finally{s.close()}}async function N(r,e){if(e.length===0)throw new Error("Choices array cannot be empty");let s=(0,l.createInterface)({input:process.stdin,output:process.stdout});try{console.log(r);for(let n=0;n<e.length;n++)console.log(`${n+1}) ${e[n]}`);for(;;){let n=(await s.question("Select an option (number): ")).trim(),t=parseInt(n,10)-1;if(!Number.isNaN(t)&&t>=0&&t<e.length)return e[t];console.log(`Please enter a number between 1 and ${e.length}`)}}finally{s.close()}}0&&(module.exports={confirm,input,select});
package/dist/index.mjs CHANGED
@@ -1,34 +1,2 @@
1
- // src/index.ts
2
- import { createInterface } from "readline/promises";
3
- async function input(question, opt) {
4
- const r1 = createInterface({
5
- input: process.stdin,
6
- output: process.stdout
7
- });
8
- let value;
9
- let parse;
10
- while (true) {
11
- value = await r1.question(question);
12
- r1.close();
13
- value = value.trim();
14
- if (opt === "int") {
15
- parse = parseInt(value, 10);
16
- if (!Number.isNaN(parse)) break;
17
- console.log("Please enter a valid integer");
18
- } else if (opt === "float") {
19
- parse = parseFloat(value);
20
- if (!Number.isNaN(value)) break;
21
- console.log("Please enter a valid number");
22
- } else {
23
- if (value.length > 0) {
24
- parse = value;
25
- break;
26
- }
27
- console.log("Please enter a non-empty string.");
28
- }
29
- }
30
- return parse;
31
- }
32
- export {
33
- input
34
- };
1
+ import{createInterface as l}from"readline/promises";import{Writable as u}from"stream";async function g(i,e="string"){let n=!1,r=new u({write:(s,o,a)=>{n||process.stdout.write(s,o),a()}}),t=l({input:process.stdin,output:r,terminal:!0});try{for(;;){e==="password"&&(process.stdout.write(i),n=!0);let s=await t.question(e==="password"?"":i);if(e==="password"&&(n=!1,process.stdout.write(`
2
+ `)),s=s.trim(),e==="int"){let o=parseInt(s,10);if(!Number.isNaN(o))return o;console.log("Please enter a valid integer")}else if(e==="float"){let o=parseFloat(s);if(!Number.isNaN(o))return o;console.log("Please enter a valid number")}else if(e==="email"){if(/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s))return s;console.log("Please enter a valid email address")}else{if(s.length>0)return s;console.log(e==="password"?"Password cannot be empty":"Please enter a non-empty string.")}}}finally{t.close()}}async function d(i,e=!0){let n=l({input:process.stdin,output:process.stdout});try{let r=e?" [Y/n] ":" [y/N] ";for(;;){let t=(await n.question(i+r)).toLowerCase().trim();if(t==="")return e;if(t==="y"||t==="yes"||t==="true")return!0;if(t==="n"||t==="no"||t==="false")return!1;console.log("Please enter 'y' or 'n'")}}finally{n.close()}}async function w(i,e){if(e.length===0)throw new Error("Choices array cannot be empty");let n=l({input:process.stdin,output:process.stdout});try{console.log(i);for(let r=0;r<e.length;r++)console.log(`${r+1}) ${e[r]}`);for(;;){let r=(await n.question("Select an option (number): ")).trim(),t=parseInt(r,10)-1;if(!Number.isNaN(t)&&t>=0&&t<e.length)return e[t];console.log(`Please enter a number between 1 and ${e.length}`)}}finally{n.close()}}export{d as confirm,g as input,w as select};
package/package.json CHANGED
@@ -1,26 +1,30 @@
1
1
  {
2
- "name": "tinyinput",
3
- "version": "0.0.1",
4
- "description": "A simple library that takes user input the same way it is done in python.",
5
- "main": "./dist/index.js",
6
- "module": "./dist/index.mjs",
7
- "types": "./dist/index.d.ts",
8
- "scripts": {
9
- "build": "tsup"
10
- },
11
- "repository": {
12
- "url": "https://github.com/walonCode/tinyinput/"
13
- },
14
- "keywords": [
15
- "input",
16
- "stdin",
17
- "stdout"
18
- ],
19
- "author": "Mohamed Lamin Walon-Jalloh",
20
- "license": "MIT",
21
- "devDependencies": {
22
- "@types/node": "^24.3.0",
23
- "tsup": "^8.5.0",
24
- "typescript": "^5.9.2"
25
- }
2
+ "name": "tinyinput",
3
+ "version": "0.0.2",
4
+ "description": "A simple library that takes user input the same way it is done in python.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsup",
10
+ "test": "vitest run",
11
+ "lint-format": "biome check --write --unsafe"
12
+ },
13
+ "repository": {
14
+ "url": "https://github.com/walonCode/tinyinput/"
15
+ },
16
+ "keywords": [
17
+ "input",
18
+ "stdin",
19
+ "stdout"
20
+ ],
21
+ "author": "Mohamed Lamin Walon-Jalloh",
22
+ "license": "MIT",
23
+ "devDependencies": {
24
+ "@biomejs/biome": "2.3.13",
25
+ "@types/node": "^24.3.0",
26
+ "tsup": "^8.5.0",
27
+ "typescript": "^5.9.2",
28
+ "vitest": "^4.0.18"
29
+ }
26
30
  }
@@ -0,0 +1,143 @@
1
+ import * as readline from "node:readline/promises";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { confirm, input, select } from "../src/index";
4
+
5
+ // Mock readline/promises
6
+ vi.mock("readline/promises", () => {
7
+ return {
8
+ createInterface: vi.fn(),
9
+ };
10
+ });
11
+
12
+ describe("tinyinput", () => {
13
+ let mockRl: any;
14
+
15
+ beforeEach(() => {
16
+ vi.clearAllMocks();
17
+ mockRl = {
18
+ question: vi.fn(),
19
+ close: vi.fn(),
20
+ };
21
+ (readline.createInterface as any).mockReturnValue(mockRl);
22
+ });
23
+
24
+ describe("input", () => {
25
+ it("should return a string when opt is 'string'", async () => {
26
+ mockRl.question.mockResolvedValueOnce("hello");
27
+ const result = await input("Question?", "string");
28
+ expect(result).toBe("hello");
29
+ expect(mockRl.question).toHaveBeenCalledWith("Question?");
30
+ expect(mockRl.close).toHaveBeenCalled();
31
+ });
32
+
33
+ it("should return an integer when opt is 'int'", async () => {
34
+ mockRl.question.mockResolvedValueOnce("42");
35
+ const result = await input("Age?", "int");
36
+ expect(result).toBe(42);
37
+ expect(mockRl.close).toHaveBeenCalled();
38
+ });
39
+
40
+ it("should return a float when opt is 'float'", async () => {
41
+ mockRl.question.mockResolvedValueOnce("3.14");
42
+ const result = await input("Pi?", "float");
43
+ expect(result).toBe(3.14);
44
+ expect(mockRl.close).toHaveBeenCalled();
45
+ });
46
+
47
+ it("should retry if invalid integer is provided", async () => {
48
+ mockRl.question
49
+ .mockResolvedValueOnce("abc") // Invalid
50
+ .mockResolvedValueOnce("10"); // Valid
51
+
52
+ const result = await input("Number?", "int");
53
+ expect(result).toBe(10);
54
+ expect(mockRl.question).toHaveBeenCalledTimes(2);
55
+ });
56
+
57
+ it("should retry if empty string is provided when opt is 'string'", async () => {
58
+ mockRl.question
59
+ .mockResolvedValueOnce("") // Invalid
60
+ .mockResolvedValueOnce(" ") // Invalid (trimmed)
61
+ .mockResolvedValueOnce("Valid"); // Valid
62
+
63
+ const result = await input("Name?", "string");
64
+ expect(result).toBe("Valid");
65
+ expect(mockRl.question).toHaveBeenCalledTimes(3);
66
+ });
67
+
68
+ it("should default to 'string' if opt is not provided", async () => {
69
+ mockRl.question.mockResolvedValueOnce("default value");
70
+ const result = await input("Prompt?");
71
+ expect(result).toBe("default value");
72
+ });
73
+
74
+ it("should return a trimmed string for email if valid", async () => {
75
+ mockRl.question.mockResolvedValueOnce(" test@example.com ");
76
+ const result = await input("Email?", "email");
77
+ expect(result).toBe("test@example.com");
78
+ });
79
+
80
+ it("should retry if invalid email is provided", async () => {
81
+ mockRl.question
82
+ .mockResolvedValueOnce("invalid-email")
83
+ .mockResolvedValueOnce("valid@email.com");
84
+
85
+ const result = await input("Email?", "email");
86
+ expect(result).toBe("valid@email.com");
87
+ expect(mockRl.question).toHaveBeenCalledTimes(2);
88
+ });
89
+
90
+ it("should return password if non-empty", async () => {
91
+ mockRl.question.mockResolvedValueOnce("secret123");
92
+ const result = await input("Password:", "password");
93
+ expect(result).toBe("secret123");
94
+ });
95
+
96
+ it("should retry if empty password is provided", async () => {
97
+ mockRl.question
98
+ .mockResolvedValueOnce("")
99
+ .mockResolvedValueOnce("password123");
100
+
101
+ const result = await input("Password:", "password");
102
+ expect(result).toBe("password123");
103
+ expect(mockRl.question).toHaveBeenCalledTimes(2);
104
+ });
105
+ });
106
+
107
+ describe("confirm", () => {
108
+ it("should return true for 'y'", async () => {
109
+ mockRl.question.mockResolvedValueOnce("y");
110
+ const result = await confirm("Sure?");
111
+ expect(result).toBe(true);
112
+ });
113
+
114
+ it("should return false for 'n'", async () => {
115
+ mockRl.question.mockResolvedValueOnce("n");
116
+ const result = await confirm("Sure?");
117
+ expect(result).toBe(false);
118
+ });
119
+
120
+ it("should return default value for empty input", async () => {
121
+ mockRl.question.mockResolvedValueOnce("");
122
+ const result = await confirm("Sure?", true);
123
+ expect(result).toBe(true);
124
+ });
125
+ });
126
+
127
+ describe("select", () => {
128
+ it("should return the chosen option", async () => {
129
+ mockRl.question.mockResolvedValueOnce("2");
130
+ const result = await select("Choose:", ["A", "B", "C"]);
131
+ expect(result).toBe("B");
132
+ });
133
+
134
+ it("should retry if index is out of bounds", async () => {
135
+ mockRl.question
136
+ .mockResolvedValueOnce("5") // Invalid
137
+ .mockResolvedValueOnce("1"); // Valid
138
+ const result = await select("Choose:", ["A", "B", "C"]);
139
+ expect(result).toBe("A");
140
+ expect(mockRl.question).toHaveBeenCalledTimes(2);
141
+ });
142
+ });
143
+ });