xw-devtool-cli 1.0.2 → 1.0.3
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.
- package/package.json +2 -1
- package/src/commands/color.js +56 -0
- package/src/commands/timeFormat.js +24 -1
- package/src/commands/variableFormat.js +94 -0
- package/src/index.js +40 -17
- package/src/utils/clipboard.js +9 -0
- package/src/commands/timestamp.js +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xw-devtool-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "基于node的开发者助手cli",
|
|
6
6
|
"main": "index.js",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"lorem-ipsum": "^2.0.8",
|
|
53
53
|
"pinyin": "^4.0.0",
|
|
54
54
|
"sharp": "^0.33.5",
|
|
55
|
+
"tinycolor2": "^1.6.0",
|
|
55
56
|
"uuid": "^13.0.0"
|
|
56
57
|
}
|
|
57
58
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import tinycolor from 'tinycolor2';
|
|
3
|
+
import { copy } from '../utils/clipboard.js';
|
|
4
|
+
import { selectFromMenu } from '../utils/menu.js';
|
|
5
|
+
|
|
6
|
+
export async function colorHandler() {
|
|
7
|
+
const { input } = await inquirer.prompt([
|
|
8
|
+
{
|
|
9
|
+
type: 'input',
|
|
10
|
+
name: 'input',
|
|
11
|
+
message: 'Enter color (Hex, RGB, HSL, or Name):',
|
|
12
|
+
validate: (input) => {
|
|
13
|
+
const color = tinycolor(input);
|
|
14
|
+
return color.isValid() || 'Invalid color format';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const color = tinycolor(input);
|
|
20
|
+
|
|
21
|
+
const results = [];
|
|
22
|
+
results.push(`Hex: ${color.toHexString().toUpperCase()}`);
|
|
23
|
+
results.push(`RGB: ${color.toRgbString()}`);
|
|
24
|
+
results.push(`HSL: ${color.toHslString()}`);
|
|
25
|
+
results.push(`HSV: ${color.toHsvString()}`);
|
|
26
|
+
|
|
27
|
+
// CMYK conversion (manual, as tinycolor doesn't support it directly)
|
|
28
|
+
const rgb = color.toRgb();
|
|
29
|
+
const r = rgb.r / 255;
|
|
30
|
+
const g = rgb.g / 255;
|
|
31
|
+
const b = rgb.b / 255;
|
|
32
|
+
let k = 1 - Math.max(r, g, b);
|
|
33
|
+
let c = (1 - r - k) / (1 - k) || 0;
|
|
34
|
+
let m = (1 - g - k) / (1 - k) || 0;
|
|
35
|
+
let y = (1 - b - k) / (1 - k) || 0;
|
|
36
|
+
|
|
37
|
+
// Round to 2 decimal places
|
|
38
|
+
const toPercent = (n) => Math.round(n * 100);
|
|
39
|
+
const cmyk = `cmyk(${toPercent(c)}%, ${toPercent(m)}%, ${toPercent(y)}%, ${toPercent(k)}%)`;
|
|
40
|
+
results.push(`CMYK: ${cmyk}`);
|
|
41
|
+
|
|
42
|
+
console.log('\n=== Conversion Results ===');
|
|
43
|
+
results.forEach(res => console.log(res));
|
|
44
|
+
console.log('==========================\n');
|
|
45
|
+
|
|
46
|
+
// Allow user to copy one of the formats
|
|
47
|
+
const copyChoice = await selectFromMenu('Select format to copy', [
|
|
48
|
+
{ name: 'Hex', value: color.toHexString().toUpperCase() },
|
|
49
|
+
{ name: 'RGB', value: color.toRgbString() },
|
|
50
|
+
{ name: 'HSL', value: color.toHslString() },
|
|
51
|
+
{ name: 'HSV', value: color.toHsvString() },
|
|
52
|
+
{ name: 'CMYK', value: cmyk }
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
await copy(copyChoice);
|
|
56
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
2
|
import dayjs from 'dayjs';
|
|
3
|
+
import 'dayjs/locale/zh-cn.js';
|
|
3
4
|
import { copy } from '../utils/clipboard.js';
|
|
5
|
+
import { selectFromMenu } from '../utils/menu.js';
|
|
6
|
+
|
|
7
|
+
dayjs.locale('zh-cn');
|
|
4
8
|
|
|
5
9
|
export async function timeFormatHandler() {
|
|
6
10
|
const { input } = await inquirer.prompt([
|
|
@@ -35,7 +39,26 @@ export async function timeFormatHandler() {
|
|
|
35
39
|
return;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
const
|
|
42
|
+
const formatPattern = await selectFromMenu('Select Output Format', [
|
|
43
|
+
{ name: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss' },
|
|
44
|
+
{ name: 'YYYY-MM-DD HH:mm', value: 'YYYY-MM-DD HH:mm' },
|
|
45
|
+
{ name: 'YYYY-MM-DD', value: 'YYYY-MM-DD' },
|
|
46
|
+
{ name: 'HH:mm:ss', value: 'HH:mm:ss' },
|
|
47
|
+
{ name: 'YYYY-MM-DD HH:mm:ss dddd', value: 'YYYY-MM-DD HH:mm:ss dddd' },
|
|
48
|
+
{ name: 'dddd', value: 'dddd' },
|
|
49
|
+
{ name: 'Timestamp (ms)', value: 'timestamp-ms' },
|
|
50
|
+
{ name: 'Timestamp (s)', value: 'timestamp-s' }
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
let formatted;
|
|
54
|
+
if (formatPattern === 'timestamp-ms') {
|
|
55
|
+
formatted = String(date.valueOf());
|
|
56
|
+
} else if (formatPattern === 'timestamp-s') {
|
|
57
|
+
formatted = String(date.unix());
|
|
58
|
+
} else {
|
|
59
|
+
formatted = date.format(formatPattern);
|
|
60
|
+
}
|
|
61
|
+
|
|
39
62
|
console.log(`\nFormatted: ${formatted}\n`);
|
|
40
63
|
await copy(formatted);
|
|
41
64
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { copy, read } from '../utils/clipboard.js';
|
|
3
|
+
import { selectFromMenu } from '../utils/menu.js';
|
|
4
|
+
|
|
5
|
+
export async function variableFormatHandler() {
|
|
6
|
+
const { input } = await inquirer.prompt([
|
|
7
|
+
{
|
|
8
|
+
type: 'input',
|
|
9
|
+
name: 'input',
|
|
10
|
+
message: 'Enter variable name to convert (Press Enter to paste from clipboard):',
|
|
11
|
+
}
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
let textToConvert = input;
|
|
15
|
+
|
|
16
|
+
if (!textToConvert || textToConvert.trim().length === 0) {
|
|
17
|
+
textToConvert = await read();
|
|
18
|
+
if (!textToConvert || textToConvert.trim().length === 0) {
|
|
19
|
+
console.log('Clipboard is empty or could not be read.');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.log(`\nUsing clipboard content: "${textToConvert}"`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const words = splitIntoWords(textToConvert);
|
|
26
|
+
|
|
27
|
+
if (words.length === 0) {
|
|
28
|
+
console.log('Could not parse any words from input.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const results = {
|
|
33
|
+
'camelCase': toCamelCase(words),
|
|
34
|
+
'PascalCase': toPascalCase(words),
|
|
35
|
+
'snake_case': toSnakeCase(words),
|
|
36
|
+
'kebab-case': toKebabCase(words),
|
|
37
|
+
'CONSTANT_CASE': toConstantCase(words)
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
console.log('\n=== Conversion Results ===');
|
|
41
|
+
Object.entries(results).forEach(([key, value]) => {
|
|
42
|
+
console.log(`${key.padEnd(15)}: ${value}`);
|
|
43
|
+
});
|
|
44
|
+
console.log('==========================\n');
|
|
45
|
+
|
|
46
|
+
const copyChoice = await selectFromMenu('Select format to copy',
|
|
47
|
+
Object.entries(results).map(([key, value]) => ({
|
|
48
|
+
name: `${key} (${value})`,
|
|
49
|
+
value: value
|
|
50
|
+
}))
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
await copy(copyChoice);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function splitIntoWords(str) {
|
|
57
|
+
// 1. Handle CamelCase/camelCase by inserting space before uppercase letters that follow lowercase letters
|
|
58
|
+
let temp = str.replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
59
|
+
|
|
60
|
+
// 2. Handle consecutive uppercase followed by lowercase (e.g. JSONParser -> JSON Parser)
|
|
61
|
+
temp = temp.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
|
|
62
|
+
|
|
63
|
+
// 3. Replace non-alphanumeric characters with spaces
|
|
64
|
+
temp = temp.replace(/[^a-zA-Z0-9]+/g, ' ');
|
|
65
|
+
|
|
66
|
+
// 4. Split by whitespace and filter empty strings
|
|
67
|
+
return temp
|
|
68
|
+
.trim()
|
|
69
|
+
.split(/\s+/)
|
|
70
|
+
.filter(w => w.length > 0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function toCamelCase(words) {
|
|
74
|
+
return words.map((w, i) => {
|
|
75
|
+
if (i === 0) return w.toLowerCase();
|
|
76
|
+
return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();
|
|
77
|
+
}).join('');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function toPascalCase(words) {
|
|
81
|
+
return words.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function toSnakeCase(words) {
|
|
85
|
+
return words.map(w => w.toLowerCase()).join('_');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function toKebabCase(words) {
|
|
89
|
+
return words.map(w => w.toLowerCase()).join('-');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function toConstantCase(words) {
|
|
93
|
+
return words.map(w => w.toUpperCase()).join('_');
|
|
94
|
+
}
|
package/src/index.js
CHANGED
|
@@ -5,10 +5,11 @@ import { base64Handler } from './commands/base64.js';
|
|
|
5
5
|
import { imgBase64Handler } from './commands/imgBase64.js';
|
|
6
6
|
import { imgConvertHandler } from './commands/imgConvert.js';
|
|
7
7
|
import { timeFormatHandler } from './commands/timeFormat.js';
|
|
8
|
-
import { timestampHandler } from './commands/timestamp.js';
|
|
9
8
|
import { mockHandler } from './commands/mock.js';
|
|
10
9
|
import { uuidHandler } from './commands/uuid.js';
|
|
11
10
|
import { pinyinHandler } from './commands/pinyin.js';
|
|
11
|
+
import { colorHandler } from './commands/color.js';
|
|
12
|
+
import { variableFormatHandler } from './commands/variableFormat.js';
|
|
12
13
|
|
|
13
14
|
process.on('SIGINT', () => {
|
|
14
15
|
console.log('\nBye!');
|
|
@@ -30,11 +31,12 @@ const features = [
|
|
|
30
31
|
{ name: 'String Encode/Decode (Base64)', value: 'base64' },
|
|
31
32
|
{ name: 'Image <-> Base64', value: 'imgBase64' },
|
|
32
33
|
{ name: 'Image Format Convert', value: 'imgConvert' },
|
|
33
|
-
{ name: 'Time Format', value: 'timeFormat' },
|
|
34
|
-
{ name: 'Get Current Timestamp', value: 'timestamp' },
|
|
34
|
+
{ name: 'Time Format / Timestamp', value: 'timeFormat' },
|
|
35
35
|
{ name: 'Mock Text', value: 'mock' },
|
|
36
36
|
{ name: 'Get UUID', value: 'uuid' },
|
|
37
|
-
{ name: 'Chinese to Pinyin', value: 'pinyin' }
|
|
37
|
+
{ name: 'Chinese to Pinyin', value: 'pinyin' },
|
|
38
|
+
{ name: 'Color Converter (Hex <-> RGB)', value: 'color' },
|
|
39
|
+
{ name: 'Variable Format Converter', value: 'variableFormat' }
|
|
38
40
|
];
|
|
39
41
|
|
|
40
42
|
async function main() {
|
|
@@ -48,12 +50,29 @@ async function main() {
|
|
|
48
50
|
program.parse(process.argv);
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
function getFeatureKey(index) {
|
|
54
|
+
if (index < 9) {
|
|
55
|
+
return String(index + 1);
|
|
56
|
+
}
|
|
57
|
+
return String.fromCharCode('a'.charCodeAt(0) + (index - 9));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getFeatureIndex(key) {
|
|
61
|
+
if (/^[1-9]$/.test(key)) {
|
|
62
|
+
return parseInt(key) - 1;
|
|
63
|
+
}
|
|
64
|
+
if (/^[a-z]$/.test(key.toLowerCase())) {
|
|
65
|
+
return key.toLowerCase().charCodeAt(0) - 'a'.charCodeAt(0) + 9;
|
|
66
|
+
}
|
|
67
|
+
return -1;
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
async function showMenu() {
|
|
52
71
|
console.log('\n=================================');
|
|
53
72
|
console.log(' xw-devtool-cli Menu');
|
|
54
73
|
console.log('=================================');
|
|
55
74
|
features.forEach((feature, index) => {
|
|
56
|
-
console.log(`${index
|
|
75
|
+
console.log(`${getFeatureKey(index)}. ${feature.name}`);
|
|
57
76
|
});
|
|
58
77
|
console.log('0. Exit');
|
|
59
78
|
console.log('=================================\n');
|
|
@@ -62,25 +81,26 @@ async function showMenu() {
|
|
|
62
81
|
{
|
|
63
82
|
type: 'input',
|
|
64
83
|
name: 'choice',
|
|
65
|
-
message:
|
|
84
|
+
message: `Please enter the feature key (0-9, a-z):`,
|
|
66
85
|
validate: (input) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
86
|
+
if (input === '0') return true;
|
|
87
|
+
|
|
88
|
+
const index = getFeatureIndex(input);
|
|
89
|
+
if (index >= 0 && index < features.length) {
|
|
90
|
+
return true;
|
|
70
91
|
}
|
|
71
|
-
return
|
|
92
|
+
return 'Invalid selection. Please enter a valid menu key.';
|
|
72
93
|
}
|
|
73
94
|
}
|
|
74
95
|
]);
|
|
75
96
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (index === 0) {
|
|
97
|
+
if (choice === '0') {
|
|
79
98
|
console.log('Bye!');
|
|
80
99
|
process.exit(0);
|
|
81
100
|
}
|
|
82
101
|
|
|
83
|
-
const
|
|
102
|
+
const index = getFeatureIndex(choice);
|
|
103
|
+
const selectedFeature = features[index];
|
|
84
104
|
|
|
85
105
|
try {
|
|
86
106
|
await handleAction(selectedFeature.value);
|
|
@@ -110,9 +130,6 @@ async function handleAction(action) {
|
|
|
110
130
|
case 'timeFormat':
|
|
111
131
|
await timeFormatHandler();
|
|
112
132
|
break;
|
|
113
|
-
case 'timestamp':
|
|
114
|
-
await timestampHandler();
|
|
115
|
-
break;
|
|
116
133
|
case 'mock':
|
|
117
134
|
await mockHandler();
|
|
118
135
|
break;
|
|
@@ -122,6 +139,12 @@ async function handleAction(action) {
|
|
|
122
139
|
case 'pinyin':
|
|
123
140
|
await pinyinHandler();
|
|
124
141
|
break;
|
|
142
|
+
case 'color':
|
|
143
|
+
await colorHandler();
|
|
144
|
+
break;
|
|
145
|
+
case 'variableFormat':
|
|
146
|
+
await variableFormatHandler();
|
|
147
|
+
break;
|
|
125
148
|
default:
|
|
126
149
|
console.log('Feature not implemented yet.');
|
|
127
150
|
}
|
package/src/utils/clipboard.js
CHANGED
|
@@ -8,3 +8,12 @@ export async function copy(text) {
|
|
|
8
8
|
console.error('Failed to copy to clipboard (might not be supported in this environment):', e.message);
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
export async function read() {
|
|
13
|
+
try {
|
|
14
|
+
return await clipboardy.read();
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.error('Failed to read from clipboard:', e.message);
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
}
|