Compare commits

..

6 Commits

Author SHA1 Message Date
0eab2630ad added fileText method
All checks were successful
Build / Build NPM Project (push) Successful in 37s
Build / Tag Version (push) Successful in 6s
Build / Publish Documentation (push) Successful in 2m21s
2024-11-09 15:35:05 -05:00
2f59a9d02e improved toCSV
All checks were successful
Build / Build NPM Project (push) Successful in 41s
Build / Tag Version (push) Successful in 7s
Build / Publish Documentation (push) Successful in 1m41s
2024-11-09 13:49:04 -05:00
ce2f32dd45 Fixed PathEvents double slash
All checks were successful
Build / Build NPM Project (push) Successful in 1m10s
Build / Tag Version (push) Successful in 13s
Build / Publish Documentation (push) Successful in 1m54s
2024-11-08 18:49:11 -05:00
ce40b5b1e7 Deprecated old methods
All checks were successful
Build / Build NPM Project (push) Successful in 39s
Build / Tag Version (push) Successful in 7s
Build / Publish Documentation (push) Successful in 1m41s
2024-11-07 10:46:56 -05:00
2500bcdcf1 Added string splice and updated dateFormat to accept custom format stings
All checks were successful
Build / Build NPM Project (push) Successful in 1m6s
Build / Tag Version (push) Successful in 13s
Build / Publish Documentation (push) Successful in 1m50s
2024-11-07 10:30:43 -05:00
db18c010aa Added adjusted interval & set errorFromCode to always return an error even on 200's
All checks were successful
Build / Build NPM Project (push) Successful in 1m16s
Build / Tag Version (push) Successful in 14s
Build / Publish Documentation (push) Successful in 2m1s
2024-10-29 10:41:25 -04:00
10 changed files with 206 additions and 41 deletions

View File

@ -6,9 +6,9 @@
</head> </head>
<body> <body>
<script type="module"> <script type="module">
import {PathEvent} from './dist/index.mjs'; import {toCsv} from './dist/index.mjs';
console.log(PathEvent.has(['data/Inventory:n', 'logs:c'], `logs/client:n`)); console.log(toCsv([{test:123, test2: {test: [1, 2, 3, 4], test3: true, test4: 'okay'}}]));
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,6 +1,6 @@
{ {
"name": "@ztimson/utils", "name": "@ztimson/utils",
"version": "0.21.5", "version": "0.22.4",
"description": "Utility library", "description": "Utility library",
"author": "Zak Timson", "author": "Zak Timson",
"license": "MIT", "license": "MIT",

View File

@ -1,4 +1,5 @@
import {dotNotation, flattenObj} from './objects.ts'; import {ASet} from './aset.ts';
import {dotNotation, flattenObj, JSONAttemptParse, JSONSanitize} from './objects.ts';
/** /**
* Convert an object to a CSV string * Convert an object to a CSV string
@ -7,20 +8,13 @@ import {dotNotation, flattenObj} from './objects.ts';
* @param {boolean} flatten Should nested object be flattened or treated as values * @param {boolean} flatten Should nested object be flattened or treated as values
* @return {string} CSV string * @return {string} CSV string
*/ */
export function csv(target: any[], flatten=true) { export function toCsv(target: any[], flatten=true) {
const headers = target.reduce((acc, row) => { const headers = new ASet(target.reduce((acc, row) => [...acc, ...Object.keys(flatten ? flattenObj(row) : row)], []));
Object.keys(flatten ? flattenObj(row) : row)
.forEach(key => { if(!acc.includes(key)) acc.push(key); });
return acc;
}, []);
return [ return [
headers.join(','), headers.join(','),
...target.map(row => headers.map((h: string) => { ...target.map(row => headers.map((h: string) => {
const value = dotNotation<any>(row, h); const value = dotNotation<any>(row, h);
const type = typeof value; return (typeof value == 'object' && value != null) ? '"' + JSONSanitize(value).replaceAll('"', '""') + '"' : value;
if(type == 'string' && value.includes(',')) return `"${value}"`;
if(type == 'object') return `"${JSON.stringify(value)}"`;
return value;
}).join(',')) }).join(','))
].join('\n'); ].join('\n');
} }

View File

@ -180,7 +180,6 @@ export class GatewayTimeoutError extends CustomError {
* @return {CustomError} The proper error type * @return {CustomError} The proper error type
*/ */
export function errorFromCode(code: number, message?: string) { export function errorFromCode(code: number, message?: string) {
if(code >= 200 && code < 300) return null;
switch(code) { switch(code) {
case 400: case 400:
return new BadRequestError(message); return new BadRequestError(message);

View File

@ -52,6 +52,21 @@ export function fileBrowser(options: {accept?: string, multiple?: boolean} = {})
}); });
} }
/**
* Extract text from a file
*
* @param file File to extract text from
* @return {Promise<string | null>} File contents
*/
export function fileText(file: any): Promise<string | null> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(<string>reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsText(file);
});
}
/** /**
* Create timestamp intended for filenames from a date * Create timestamp intended for filenames from a date
* *

View File

@ -29,10 +29,10 @@ export function clean<T>(obj: T, undefinedOnly = false): Partial<T> {
* Should be replaced by `structuredClone` once released. * Should be replaced by `structuredClone` once released.
* @param {T} value Object to copy * @param {T} value Object to copy
* @returns {T} Type * @returns {T} Type
* @deprecated Please use `structuredClone`
*/ */
export function deepCopy<T>(value: T): T { export function deepCopy<T>(value: T): T {
return structuredClone(value); try {return structuredClone(value); }
catch { return JSON.parse(JSONSanitize(value)); }
} }
/** /**
@ -91,7 +91,6 @@ export function dotNotation<T>(obj: any, prop: string, set?: T): T | undefined {
}, obj); }, obj);
} }
/** /**
* Convert object into URL encoded query string * Convert object into URL encoded query string
* *
@ -128,8 +127,9 @@ export function encodeQuery(data: any): string {
export function flattenObj(obj: any, parent?: any, result: any = {}) { export function flattenObj(obj: any, parent?: any, result: any = {}) {
if(typeof obj === "object" && !Array.isArray(obj)) { if(typeof obj === "object" && !Array.isArray(obj)) {
for(const key of Object.keys(obj)) { for(const key of Object.keys(obj)) {
const propName = parent ? parent + '.' + key : key; const propName = parent ? `${parent}.${key}` : key;
if(typeof obj[key] === 'object') { if(typeof obj[key] === 'object' && obj[key] != null && !Array.isArray(obj[key])) {
console.log(propName, );
flattenObj(obj[key], propName, result); flattenObj(obj[key], propName, result);
} else { } else {
result[propName] = obj[key]; result[propName] = obj[key];
@ -216,11 +216,11 @@ export function mixin(target: any, constructors: any[]) {
/** /**
* Parse JSON but return the original string if it fails * Parse JSON but return the original string if it fails
* *
* @param {string} json JSON string to parse * @param {any} json JSON string to parse
* @return {string | T} Object if successful, original string otherwise * @return {string | T} Object if successful, original string otherwise
*/ */
export function JSONAttemptParse<T>(json: string): T | string { export function JSONAttemptParse<T1, T2>(json: T2): T1 | T2 {
try { return JSON.parse(json); } try { return JSON.parse(<any>json); }
catch { return json; } catch { return json; }
} }
@ -234,10 +234,8 @@ export function JSONAttemptParse<T>(json: string): T | string {
export function JSONSanitize(obj: any, space?: number): string { export function JSONSanitize(obj: any, space?: number): string {
let cache: any[] = []; let cache: any[] = [];
return JSON.stringify(obj, (key, value) => { return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) { if(typeof value === 'object' && value !== null)
if (cache.includes(value)) return; if(!cache.includes(value)) cache.push(value);
cache.push(value);
}
return value; return value;
}, space); }, space);
} }

View File

@ -25,7 +25,7 @@ export type Method = '*' | 'n' | 'c' | 'r' | 'u' | 'd' | 'x';
* @param {string} args * @param {string} args
* @return {PathEvent} Event object * @return {PathEvent} Event object
*/ */
export function PE(str: TemplateStringsArray, ...args: string[]) { export function PE(str: TemplateStringsArray, ...args: any[]) {
const combined = []; const combined = [];
for(let i = 0; i < str.length || i < args.length; i++) { for(let i = 0; i < str.length || i < args.length; i++) {
if(str[i]) combined.push(str[i]); if(str[i]) combined.push(str[i]);
@ -92,7 +92,7 @@ export class PathEvent {
constructor(Event: string | PathEvent) { constructor(Event: string | PathEvent) {
if(typeof Event == 'object') return Object.assign(this, Event); if(typeof Event == 'object') return Object.assign(this, Event);
let [p, scope, method] = Event.split(':'); let [p, scope, method] = Event.replaceAll(/\/{2,}/g, '/').split(':');
if(!method) method = scope || '*'; if(!method) method = scope || '*';
if(p == '*' || !p && method == '*') { if(p == '*' || !p && method == '*') {
p = ''; p = '';

View File

@ -1,5 +1,3 @@
import {dotNotation, flattenObj} from './objects.ts';
/** /**
* String of all letters * String of all letters
*/ */
@ -44,7 +42,7 @@ export function formatBytes(bytes: number, decimals = 2) {
export function formatPhoneNumber(number: string) { export function formatPhoneNumber(number: string) {
const parts = /(\+?1)?.*?(\d{3}).*?(\d{3}).*?(\d{4})/g.exec(number); const parts = /(\+?1)?.*?(\d{3}).*?(\d{3}).*?(\d{4})/g.exec(number);
if(!parts) throw new Error(`Number cannot be parsed: ${number}`); if(!parts) throw new Error(`Number cannot be parsed: ${number}`);
return `${parts[1] ?? ''} (${parts[2]}) ${parts[3]}-${parts[4]}`.trim(); return `${parts[1] ? '+1' : ''} (${parts[2]}) ${parts[3]}-${parts[4]}`.trim();
} }
/** /**
@ -60,6 +58,7 @@ export function formatPhoneNumber(number: string) {
* @param {string} str - Value that will be injected to parent * @param {string} str - Value that will be injected to parent
* @param {number} index - Position to inject string at * @param {number} index - Position to inject string at
* @returns {string} - New string * @returns {string} - New string
* @deprecated use `strSplice()`
*/ */
export function insertAt(target: string, str: string, index: number): String { export function insertAt(target: string, str: string, index: number): String {
return `${target.slice(0, index)}${str}${target.slice(index + 1)}`; return `${target.slice(0, index)}${str}${target.slice(index + 1)}`;
@ -150,6 +149,21 @@ export function randomStringBuilder(length: number, letters = false, numbers = f
}).join(''); }).join('');
} }
/**
* Splice a string together (Similar to Array.splice)
*
* @param {string} str String that will be modified
* @param {number} start Start index of splice
* @param {number} deleteCount Number of characters to remove in splice
* @param {string} insert Insert new string into splice
* @return {string} Returned spliced string
*/
export function strSplice(str: string, start: number, deleteCount: number, insert = '') {
const before = str.slice(0, start);
const after = str.slice(start + deleteCount);
return before + insert + after;
}
/** /**
* Find all substrings that match a given pattern. * Find all substrings that match a given pattern.
* *
@ -195,6 +209,7 @@ export type ParsedUrl = {
* *
* @param {string} url URL string that will be parsed * @param {string} url URL string that will be parsed
* @returns {RegExpExecArray} Parts of URL * @returns {RegExpExecArray} Parts of URL
* @deprecated Use built-in URL object: `new URL('...')`;
*/ */
export function parseUrl(url: string): ParsedUrl { export function parseUrl(url: string): ParsedUrl {
const processed = new RegExp( const processed = new RegExp(

View File

@ -1,17 +1,161 @@
import {strSplice} from './string.ts';
/** /**
* Return date formated highest to lowest: YYYY-MM-DD H:mm AM * Like setInterval but will adjust the timeout value to account for runtime
* @param {Function} cb Callback function that will be ran
* @param {number} ms Run function ever x seconds
* @return {() => void}
*/
export function adjustedInterval(cb: Function, ms: number) {
let cancel = false, timeout: any = null;
const p = async () => {
if (cancel) return;
const start = new Date().getTime();
await cb();
const end = new Date().getTime();
timeout = setTimeout(() => p(), ms - (end - start) || 1);
};
p();
return () => {
cancel = true;
if(timeout) clearTimeout(timeout);
}
}
/**
* Format date
* *
* @param {Date | number | string} date Date or timestamp to convert to string * @param {Date | number | string} date Date or timestamp to convert to string
* @param {string} format How date string will be formatted, default: `YYYY-MM-DD H:mm A`
* @return {string} Formated date * @return {string} Formated date
*/ */
export function formatDate(date: Date | number | string): string { export function formatDate(date: Date | number | string, format = 'YYYY-MM-DD H:mm '): string {
if(typeof date == 'number' || typeof date == 'string') date = new Date(date); if(typeof date == 'number' || typeof date == 'string') date = new Date(date);
let hours = date.getHours(), postfix = 'AM';
if(hours >= 12) { function day(num: number): string {
if(hours > 12) hours -= 12; switch(num) {
postfix = 'PM'; case(0): return 'Sunday';
} else if(hours == 0) hours = 12; case(1): return 'Monday';
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}, ${hours}:${date.getMinutes().toString().padStart(2, '0')} ${postfix}`; case(2): return 'Tuesday';
case(3): return 'Wednesday';
case(4): return 'Thursday';
case(5): return 'Friday';
case(6): return 'Saturday';
default: return 'Unknown';
}
}
function doy(date: Date) {
const start = new Date(`${date.getFullYear()}-01-01 0:00:00`);
return Math.ceil((date.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
}
function month(num: number): string {
switch(num) {
case(0): return 'January';
case(1): return 'February';
case(2): return 'March';
case(3): return 'April';
case(4): return 'May';
case(5): return 'June';
case(6): return 'July';
case(7): return 'August';
case(8): return 'September';
case(9): return 'October';
case(10): return 'November';
case(11): return 'December';
default: return 'Unknown';
}
}
function suffix(num: number) {
let n = num.toString();
switch(n.at(-1)) {
case('1'): return num + 'st';
case('2'): return num + 'nd';
case('3'): return num + 'rd';
default: return num + 'th';
}
}
function tzOffset(offset: number) {
const hours = ~~(offset / 60);
const minutes = offset % 60;
return (offset > 0 ? '-' : '') + `${hours}:${minutes.toString().padStart(2, '0')}`;
}
function timezone(offset: number) {
const hours = offset / 60;
switch (hours) {
case -12: return "IDLW";
case -11: return "SST";
case -10: return "HST";
case -9: return "AKST";
case -8: return "PST";
case -7: return "MST";
case -6: return "CST";
case -5: return "EST";
case -4: return "AST";
case -3: return "ART";
case -2: return "FNT";
case -1: return "AZOT";
case 0: return "UTC";
case 1: return "CET";
case 2: return "EET";
case 3: return "MSK";
case 4: return "SAMT";
case 5: return "YEKT";
case 6: return "OMST";
case 7: return "KRAT";
case 8: return "CST";
case 9: return "JST";
case 10: return "AEST";
case 11: return "SBT";
case 12: return "NZST";
default: return '';
}
}
return format
// Year
.replaceAll('YYYY', date.getFullYear().toString())
.replaceAll('YY', date.getFullYear().toString().slice(2))
// Month
.replaceAll('MMMM', month(date.getMonth()))
.replaceAll('MMM', month(date.getMonth()).slice(0, 2))
.replaceAll('MM', (date.getMonth() + 1).toString().padStart(2, '0'))
.replaceAll('M', (date.getMonth() + 1).toString())
// Day
.replaceAll('DDD', doy(date).toString())
.replaceAll('DD', date.getDate().toString().padStart(2, '0'))
.replaceAll('Do', suffix(date.getDate()))
.replaceAll('D', date.getDate().toString())
.replaceAll('dddd', day(date.getDay()))
.replaceAll('ddd', day(date.getDay()).slice(0, 2))
.replaceAll('dd', date.getDate().toString().padStart(2, '0'))
.replaceAll('d', date.getDay().toString())
// Hour
.replaceAll('HH', date.getHours().toString().padStart(2, '0'))
.replaceAll('H', date.getHours().toString())
.replaceAll('hh', (date.getHours() > 12 ? date.getHours() - 12 : date.getHours()).toString().padStart(2, '0'))
.replaceAll('h', (date.getHours() > 12 ? date.getHours() - 12 : date.getHours()).toString())
// Minute
.replaceAll('mm', date.getMinutes().toString().padStart(2, '0'))
.replaceAll('m', date.getMinutes().toString())
// Second
.replaceAll('ss', date.getSeconds().toString().padStart(2, '0'))
.replaceAll('s', date.getSeconds().toString())
// Millisecond
.replaceAll('SSS', date.getMilliseconds().toString())
.replaceAll('SS', date.getMilliseconds().toString().slice(0, 1))
.replaceAll('S', date.getMilliseconds().toString()[0])
// Period/Meridian (AM/PM)
.replaceAll('A', date.getHours() >= 12 ? 'PM' : 'AM')
.replaceAll('a', date.getHours() >= 12 ? 'pm' : 'am')
// Timezone
.replaceAll('ZZ', tzOffset(date.getTimezoneOffset()).replace(':', ''))
.replaceAll('Z', tzOffset(date.getTimezoneOffset()))
.replaceAll('z', timezone(date.getTimezoneOffset()));
} }
/** /**

View File

@ -15,6 +15,6 @@
* *
* @return {Array<keyof T>} Available keys * @return {Array<keyof T>} Available keys
*/ */
export function tyoeKeys<T extends object>() { export function typeKeys<T extends object>() {
return Object.keys(<T>{}) as Array<keyof T>; return Object.keys(<T>{}) as Array<keyof T>;
} }