Compare commits

..

5 Commits

Author SHA1 Message Date
341ef37205 Added fromCsv
All checks were successful
Build / Build NPM Project (push) Successful in 37s
Build / Tag Version (push) Successful in 7s
Build / Publish Documentation (push) Successful in 1m43s
2024-11-09 16:30:05 -05:00
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
8 changed files with 65 additions and 36 deletions

View File

@ -6,9 +6,14 @@
</head>
<body>
<script type="module">
import {PathEvent} from './dist/index.mjs';
import {fromCsv, toCsv} from './dist/index.mjs';
console.log(PathEvent.has(['data/Inventory:n', 'logs:c'], `logs/client:n`));
const csv = '' +
'_id,any,boolean,date,file,foreignKey,formula,javaScript,number,string,_createdBy,_createdDate,_updatedBy,_updatedDate\n' +
'48,,,,,,,,,,system,2024-11-09T19:05:04.932Z,system,2024-11-09T19:05:04.932Z\n' +
'49,,,,,,,,,,system,2024-11-09T19:05:04.933Z,system,2024-11-09T19:05:04.933Z';
console.log(fromCsv(csv));
</script>
</body>
</html>

View File

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

View File

@ -1,4 +1,22 @@
import {dotNotation, flattenObj} from './objects.ts';
import {ASet} from './aset.ts';
import {dotNotation, flattenObj, JSONSanitize} from './objects.ts';
export function fromCsv<T = any>(csv: string, hasHeaders=true): T[] {
const row = csv.split('\n');
let headers: any = hasHeaders ? row.splice(0, 1)[0] : null;
if(headers) headers = headers.match(/(?:[^,"']+|"[^"]*"|'[^']*')+/g);
return <T[]>row.map(r => {
const props: string[] = <any>r.match(/(?:[^,"']+|"[^"]*"|'[^']*')+/g);
const h = headers || (Array(props.length).fill(null).map((r, i) => {
let letter = '';
const first = i / 26;
if(first > 1) letter +=
i % 26
i
}));
return h.reduce((acc: any, h: any, i: number) => ({...acc, [h]: props[i]}), {})
})
}
/**
* Convert an object to a CSV string
@ -7,20 +25,13 @@ import {dotNotation, flattenObj} from './objects.ts';
* @param {boolean} flatten Should nested object be flattened or treated as values
* @return {string} CSV string
*/
export function csv(target: any[], flatten=true) {
const headers = target.reduce((acc, row) => {
Object.keys(flatten ? flattenObj(row) : row)
.forEach(key => { if(!acc.includes(key)) acc.push(key); });
return acc;
}, []);
export function toCsv(target: any[], flatten=true) {
const headers = new ASet(target.reduce((acc, row) => [...acc, ...Object.keys(flatten ? flattenObj(row) : row)], []));
return [
headers.join(','),
...target.map(row => headers.map((h: string) => {
const value = dotNotation<any>(row, h);
const type = typeof value;
if(type == 'string' && value.includes(',')) return `"${value}"`;
if(type == 'object') return `"${JSON.stringify(value)}"`;
return value;
return (typeof value == 'object' && value != null) ? '"' + JSONSanitize(value).replaceAll('"', '""') + '"' : value;
}).join(','))
].join('\n');
}

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
*

View File

@ -29,10 +29,10 @@ export function clean<T>(obj: T, undefinedOnly = false): Partial<T> {
* Should be replaced by `structuredClone` once released.
* @param {T} value Object to copy
* @returns {T} Type
* @deprecated Please use `structuredClone`
*/
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);
}
/**
* 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 = {}) {
if(typeof obj === "object" && !Array.isArray(obj)) {
for(const key of Object.keys(obj)) {
const propName = parent ? parent + '.' + key : key;
if(typeof obj[key] === 'object') {
const propName = parent ? `${parent}.${key}` : key;
if(typeof obj[key] === 'object' && obj[key] != null && !Array.isArray(obj[key])) {
console.log(propName, );
flattenObj(obj[key], propName, result);
} else {
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
*
* @param {string} json JSON string to parse
* @param {any} json JSON string to parse
* @return {string | T} Object if successful, original string otherwise
*/
export function JSONAttemptParse<T>(json: string): T | string {
try { return JSON.parse(json); }
export function JSONAttemptParse<T1, T2>(json: T2): T1 | T2 {
try { return JSON.parse(<any>json); }
catch { return json; }
}
@ -234,10 +234,8 @@ export function JSONAttemptParse<T>(json: string): T | string {
export function JSONSanitize(obj: any, space?: number): string {
let cache: any[] = [];
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (cache.includes(value)) return;
cache.push(value);
}
if(typeof value === 'object' && value !== null)
if(!cache.includes(value)) cache.push(value);
return value;
}, space);
}

View File

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

View File

@ -1,24 +1,22 @@
import {dotNotation, flattenObj} from './objects.ts';
/**
* String of all letters
*/
const LETTER_LIST = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
export const LETTER_LIST = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
/**
* String of all numbers
*/
const NUMBER_LIST = '0123456789';
export const NUMBER_LIST = '0123456789';
/**
* String of all symbols
*/
const SYMBOL_LIST = '~`!@#$%^&*()_-+={[}]|\\:;"\'<,>.?/';
export const SYMBOL_LIST = '~`!@#$%^&*()_-+={[}]|\\:;"\'<,>.?/';
/**
* String of all letters, numbers & symbols
*/
const CHAR_LIST = LETTER_LIST + NUMBER_LIST + SYMBOL_LIST;
export const CHAR_LIST = LETTER_LIST + LETTER_LIST.toLowerCase() + NUMBER_LIST + SYMBOL_LIST;
/**
* Convert number of bytes into a human-readable size
@ -44,7 +42,7 @@ export function formatBytes(bytes: number, decimals = 2) {
export function formatPhoneNumber(number: string) {
const parts = /(\+?1)?.*?(\d{3}).*?(\d{3}).*?(\d{4})/g.exec(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 {number} index - Position to inject string at
* @returns {string} - New string
* @deprecated use `strSplice()`
*/
export function insertAt(target: string, str: string, index: number): String {
return `${target.slice(0, index)}${str}${target.slice(index + 1)}`;
@ -210,6 +209,7 @@ export type ParsedUrl = {
*
* @param {string} url URL string that will be parsed
* @returns {RegExpExecArray} Parts of URL
* @deprecated Use built-in URL object: `new URL('...')`;
*/
export function parseUrl(url: string): ParsedUrl {
const processed = new RegExp(

View File

@ -15,6 +15,6 @@
*
* @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>;
}