mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 10:54:44 +00:00
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# ide
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# temp
|
||||||
|
node_modules
|
||||||
|
**/*~
|
||||||
|
**/*.log
|
||||||
|
**/.DS_Store
|
||||||
|
**/Thumbs.db
|
||||||
|
|
||||||
|
# build
|
||||||
|
.vitepress/dist
|
||||||
|
.vitepress/cache
|
||||||
|
|
||||||
|
.output
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
out
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
|
||||||
|
# test
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# env
|
||||||
|
.env*
|
||||||
20
.vitepress/config.ts
Normal file
20
.vitepress/config.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { defineConfig } from 'vitepress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
lang: 'ru-RU',
|
||||||
|
title: "Tools",
|
||||||
|
description: "A set of tools and utilities for web development",
|
||||||
|
rewrites: {
|
||||||
|
'packages/:pkg/README.md': 'packages/:pkg/index.md'
|
||||||
|
},
|
||||||
|
themeConfig: {
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
text: 'Пакеты',
|
||||||
|
items: [
|
||||||
|
{ text: '@robonen/tsconfig', link: '/packages/tsconfig/' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# toolkit
|
|
||||||
My most frequently used tools in programming
|
|
||||||
|
|
||||||
- [Plural](/plural) (TS, PHP)
|
|
||||||
132
cli.ts
Normal file
132
cli.ts
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import { mkdir, writeFile } from 'node:fs/promises';
|
||||||
|
import { defineCommand, runMain } from 'citty';
|
||||||
|
import { resolve } from 'pathe';
|
||||||
|
import { splitByCase } from 'scule';
|
||||||
|
|
||||||
|
const PACKAGE_MANAGER = 'pnpm@8.15.6';
|
||||||
|
const NODE_VERSION = '>=18.0.0';
|
||||||
|
const VITE_VERSION = '^5.2.8';
|
||||||
|
const VITE_DTS_VERSION = '^3.8.1';
|
||||||
|
const DEFAULT_DIR = 'packages';
|
||||||
|
|
||||||
|
const generatePackageJson = (name: string, path: string, hasVite: boolean) => {
|
||||||
|
const data = {
|
||||||
|
name,
|
||||||
|
private: true,
|
||||||
|
version: '1.0.0',
|
||||||
|
license: 'UNLICENSED',
|
||||||
|
description: '',
|
||||||
|
keywords: [],
|
||||||
|
author: 'Robonen Andrew <robonenandrew@gmail.com>',
|
||||||
|
repository: {
|
||||||
|
type: 'git',
|
||||||
|
url: 'git+https://github.com/robonen/tools.git',
|
||||||
|
directory: path,
|
||||||
|
},
|
||||||
|
packageManager: PACKAGE_MANAGER,
|
||||||
|
engines: {
|
||||||
|
node: NODE_VERSION,
|
||||||
|
},
|
||||||
|
type: 'module',
|
||||||
|
files: ['dist'],
|
||||||
|
main: './dist/index.cjs',
|
||||||
|
module: './dist/index.js',
|
||||||
|
types: './dist/index.d.ts',
|
||||||
|
exports: {
|
||||||
|
'.': {
|
||||||
|
import: './dist/index.js',
|
||||||
|
require: './dist/index.cjs',
|
||||||
|
types: './dist/index.d.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scripts: {
|
||||||
|
test: 'echo \"Error: no test specified\" && exit 1',
|
||||||
|
...(hasVite && {
|
||||||
|
dev: 'vite',
|
||||||
|
build: 'vite build',
|
||||||
|
preview: 'vite preview',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
devDependencies: {
|
||||||
|
'@robonen/tsconfig': 'workspace:*',
|
||||||
|
...(hasVite && {
|
||||||
|
vite: VITE_VERSION,
|
||||||
|
'vite-plugin-dts': VITE_DTS_VERSION,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return JSON.stringify(data, null, 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateViteConfig = () => `import { defineConfig } from 'vite';
|
||||||
|
import dts from 'vite-plugin-dts';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
dts({ insertTypesEntry: true }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const generateTsConfig = () => {
|
||||||
|
const data = {
|
||||||
|
extends: '@robonen/tsconfig/tsconfig.json',
|
||||||
|
};
|
||||||
|
|
||||||
|
return JSON.stringify(data, null, 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateReadme = (name: string) => `# ${name}`;
|
||||||
|
|
||||||
|
const createCommand = defineCommand({
|
||||||
|
meta: {
|
||||||
|
name: "create",
|
||||||
|
version: "1.0.0",
|
||||||
|
description: "Command to create a new project",
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
name: {
|
||||||
|
type: 'positional',
|
||||||
|
description: "Name of the project",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
type: 'positional',
|
||||||
|
description: "Relative path to the project folder",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: "Add Vite to the project",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async run({ args }) {
|
||||||
|
const path = args.path ?? `./${DEFAULT_DIR}/${splitByCase(args.name).at(-1)}`;
|
||||||
|
const resolvedPath = resolve(path);
|
||||||
|
const hasVite = args.vite ?? false;
|
||||||
|
|
||||||
|
console.log(`Creating project in ${resolvedPath}`);
|
||||||
|
|
||||||
|
await mkdir(resolvedPath, { recursive: true });
|
||||||
|
|
||||||
|
writeFile(`${resolvedPath}/package.json`, generatePackageJson(args.name, path, hasVite));
|
||||||
|
writeFile(`${resolvedPath}/tsconfig.json`, generateTsConfig());
|
||||||
|
writeFile(`${resolvedPath}/README.md`, generateReadme(args.name));
|
||||||
|
|
||||||
|
if (hasVite) {
|
||||||
|
mkdir(`${resolvedPath}/src`, { recursive: true });
|
||||||
|
writeFile(`${resolvedPath}/vite.config.ts`, generateViteConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Project created successfully`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
runMain(createCommand);
|
||||||
37
package.json
Normal file
37
package.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "tools",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"description": "Set of useful tools for web development",
|
||||||
|
"keywords": [
|
||||||
|
"tools",
|
||||||
|
"web",
|
||||||
|
"ui",
|
||||||
|
"utilities"
|
||||||
|
],
|
||||||
|
"author": "Robonen Andrew <robonenandrew@gmail.com>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/robonen/tools.git"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@8.15.6",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.12.6",
|
||||||
|
"citty": "^0.1.6",
|
||||||
|
"jiti": "^1.21.0",
|
||||||
|
"pathe": "^1.1.2",
|
||||||
|
"scule": "^1.3.0",
|
||||||
|
"vitepress": "^1.1.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"create": "jiti ./cli.ts",
|
||||||
|
"docs:dev": "vitepress dev .",
|
||||||
|
"docs:build": "vitepress build .",
|
||||||
|
"docs:preview": "vitepress preview ."
|
||||||
|
}
|
||||||
|
}
|
||||||
45
packages/tsconfig/README.md
Normal file
45
packages/tsconfig/README.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# @robonen/tsconfig
|
||||||
|
|
||||||
|
Базовый конфигурационный файл для TypeScript
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install -D @robonen/tsconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"extends": "@robonen/tsconfig/tsconfig.json"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Описание основных параметров
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"module": "Preserve", // использовать ту же версию модуля, что и сборщик
|
||||||
|
"noEmit": true, // не генерировать файлы
|
||||||
|
"moduleResolution": "Bundler", // разрешение модулей на основе сборщика
|
||||||
|
"target": "ESNext", // целевая версия JavaScript
|
||||||
|
|
||||||
|
|
||||||
|
"skipLibCheck": true, // не проверять типы, заданные во всех файлах описания типов (*.d.ts)
|
||||||
|
"esModuleInterop": true, // создать хелперы __importStar и __importDefault для обеспечения совместимости с экосистемой Babel и включить allowSyntheticDefaultImports для совместимости с системой типов
|
||||||
|
"allowSyntheticDefaultImports": true, // разрешить импортировать модули не имеющие внутри себя "import default"
|
||||||
|
"allowJs": true, // разрешить импортировать файлы JavaScript
|
||||||
|
"resolveJsonModule": true, // разрешить импортировать файлы JSON
|
||||||
|
"moduleDetection": "force", // заставляет TypeScript рассматривать все файлы как модули. Это помогает избежать ошибок cannot redeclare block-scoped variable»
|
||||||
|
"isolatedModules": true, // орабатывать каждый файл, как отдельный изолированный модуль
|
||||||
|
"removeComments": true, // удалять комментарии из исходного кода
|
||||||
|
"verbatimModuleSyntax": true, // сохранять синтаксис модулей в исходном коде (важно при импорте типов)
|
||||||
|
"useDefineForClassFields": true, // использование классов стандарта TC39, а не TypeScript
|
||||||
|
"strict": true, // включить все строгие проверки (noImplicitAny, noImplicitThis, alwaysStrict, strictNullChecks, strictFunctionTypes, strictPropertyInitialization)
|
||||||
|
"noUncheckedIndexedAccess": true, // запрещает доступ к массиву или объекту без предварительной проверки того, определен ли он
|
||||||
|
"declaration": true, // генерировать файлы описания типов (*.d.ts)
|
||||||
|
|
||||||
|
"composite": true, // указывает TypeScript создавать файлы .tsbuildinfo. Это сообщает TypeScript, что ваш проект является частью монорепозитория, а также помогает кэшировать сборки для более быстрой работы
|
||||||
|
"sourceMap": true, // генерировать карту исходного кода
|
||||||
|
"declarationMap": true // генерировать карту исходного кода для файлов описания типов (*.d.ts)
|
||||||
|
}
|
||||||
|
```
|
||||||
26
packages/tsconfig/package.json
Normal file
26
packages/tsconfig/package.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "@robonen/tsconfig",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"description": "",
|
||||||
|
"keywords": [
|
||||||
|
"tsconfig",
|
||||||
|
"typescript",
|
||||||
|
"ts",
|
||||||
|
"config"
|
||||||
|
],
|
||||||
|
"author": "Robonen Andrew <robonenandrew@gmail.com>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/robonen/tools.git",
|
||||||
|
"directory": "packages/tsconfig"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@8.15.6",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"**tsconfig.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
36
packages/tsconfig/tsconfig.json
Normal file
36
packages/tsconfig/tsconfig.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"display": "Base TypeScript Configuration",
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Basic Options */
|
||||||
|
"module": "Preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"sourceMap": true,
|
||||||
|
"target": "ESNext",
|
||||||
|
|
||||||
|
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
|
||||||
|
/* Library transpiling */
|
||||||
|
"declaration": true,
|
||||||
|
|
||||||
|
/* Library in monorepo */
|
||||||
|
"composite": true,
|
||||||
|
"declarationMap": true
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Функция изменения формы слов в зависимости от числительного
|
|
||||||
|
|
||||||
Часто встречается задача вывода правильного окончания слова с предшествующим ему числом.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
- 1 депутат
|
|
||||||
- 2–4 депутат**а**
|
|
||||||
- 0, 5-9 или 10 депутат**ов**
|
|
||||||
|
|
||||||
## Примеры исользования
|
|
||||||
|
|
||||||
### Typescript
|
|
||||||
```typescript
|
|
||||||
import { plural } from 'plural';
|
|
||||||
|
|
||||||
const totalOrders = 2;
|
|
||||||
|
|
||||||
const words = ['заказ', 'заказа', 'заказов'];
|
|
||||||
|
|
||||||
const result = `${totalOrders} ${plural(totalOrders, words)}`;
|
|
||||||
|
|
||||||
console.log(result); // 2 заказа
|
|
||||||
```
|
|
||||||
|
|
||||||
### PHP
|
|
||||||
```php
|
|
||||||
include 'plural.php';
|
|
||||||
|
|
||||||
$totalOrders = 2;
|
|
||||||
|
|
||||||
$words = ['заказ', 'заказа', 'заказов'];
|
|
||||||
|
|
||||||
$result = "{$totalOrders} {plural($totalOrders, $words)}";
|
|
||||||
|
|
||||||
echo result; // 2 заказа
|
|
||||||
```
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
function plural(int $count, array $words)
|
|
||||||
{
|
|
||||||
$cases = [2, 0, 1, 1, 1, 2];
|
|
||||||
return $words[
|
|
||||||
$count % 100 > 4 && $count % 100 < 20 ? 2 : $cases[min($count % 10, 5)]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export type WordForms = [string, string, string];
|
|
||||||
|
|
||||||
export const plural = (count: number, words: WordForms): string => {
|
|
||||||
const cases = [2, 0, 1, 1, 1, 2];
|
|
||||||
return words[
|
|
||||||
count % 100 > 4 && count % 100 < 20 ? 2 : cases[Math.min(count % 10, 5)]
|
|
||||||
];
|
|
||||||
};
|
|
||||||
1138
pnpm-lock.yaml
generated
Normal file
1138
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspaces.yaml
Normal file
2
pnpm-workspaces.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
packages:
|
||||||
|
- packages/*
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
type Split<S extends string, D extends string> = S extends ''
|
|
||||||
? []
|
|
||||||
: S extends `${infer T}${D}${infer U}`
|
|
||||||
? [T, ...Split<U, D>]
|
|
||||||
: [S];
|
|
||||||
|
|
||||||
type UnwrapNumbers<T extends string | string[] | number | number[]> = T extends number | number[]
|
|
||||||
? T
|
|
||||||
: T extends string
|
|
||||||
? T extends `${infer N extends number}`
|
|
||||||
? N
|
|
||||||
: never
|
|
||||||
: T extends [infer H extends string, ...infer T extends string[]]
|
|
||||||
? UnwrapNumbers<H> extends never
|
|
||||||
? never
|
|
||||||
: [UnwrapNumbers<H>, ...UnwrapNumbers<T>]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
type IPv4Octets = [number, number, number, number];
|
|
||||||
type IPv6Octets = string[];
|
|
||||||
|
|
||||||
type IPv4<CIDR extends string> = Split<CIDR, '/'> extends [
|
|
||||||
infer IP extends string,
|
|
||||||
infer SUBNET extends string,
|
|
||||||
]
|
|
||||||
? UnwrapNumbers<Split<IP, '.'>> extends infer OCTETS
|
|
||||||
? OCTETS extends never
|
|
||||||
? never
|
|
||||||
: OCTETS extends IPv4Octets
|
|
||||||
? UnwrapNumbers<SUBNET> extends never
|
|
||||||
? never
|
|
||||||
: CIDR
|
|
||||||
: never
|
|
||||||
: never
|
|
||||||
: never;
|
|
||||||
|
|
||||||
type IPv6<CIDR extends string> = Split<CIDR, '/'> extends [
|
|
||||||
infer IP extends string,
|
|
||||||
infer SUBNET extends string,
|
|
||||||
]
|
|
||||||
? Split<IP, ':'> extends IPv6Octets
|
|
||||||
? UnwrapNumbers<SUBNET> extends never
|
|
||||||
? never
|
|
||||||
: CIDR
|
|
||||||
: never
|
|
||||||
: never;
|
|
||||||
|
|
||||||
type Test_1 = IPv4<'0.0.0.0/0'>;
|
|
||||||
type Test_2 = IPv4<'255.255.255.255/32'>;
|
|
||||||
type Test_3 = IPv4<'127.0.0.0.1/32'>;
|
|
||||||
type Test_4 = IPv4<'a'>;
|
|
||||||
type Test_5 = IPv4<'1.1.1.a/32'>;
|
|
||||||
type Test_6 = IPv4<'1.1.1.1'>;
|
|
||||||
type Test_7 = IPv4<'1.1.1.1/'>;
|
|
||||||
type Test_8 = IPv4<'1.1.1.1/a'>;
|
|
||||||
|
|
||||||
type Test_9 = IPv6<'::1/0'>;
|
|
||||||
type Test_10 = IPv6<'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128'>;
|
|
||||||
type Test_11 = IPv6<'::1/128'>;
|
|
||||||
type Test_12 = IPv6<'a'>;
|
|
||||||
type Test_13 = IPv6<'1:1:1:1:1:1:1:1/128'>;
|
|
||||||
type Test_14 = IPv6<'1:1:1:1:1:1:1:1'>;
|
|
||||||
type Test_15 = IPv6<'1:1:1:1:1:1:1:1/'>;
|
|
||||||
type Test_16 = IPv6<'1:1:1:1:1:1:1:1/a'>;
|
|
||||||
|
|
||||||
// TODO: fully-typed ipv6 (unwrap hex, full and abbreviated address representations)
|
|
||||||
Reference in New Issue
Block a user