Skip to content

Commit

Permalink
feat: added WebsiteUrl and functions
Browse files Browse the repository at this point in the history
- added WebsiteUrl and related types and functions
- fixed readEmailDomainFromUrlOrEmailAddress() domain parsing issue
- fixed slashPathType()
  • Loading branch information
dereekb committed Aug 7, 2022
1 parent 0212e63 commit ed3430f
Show file tree
Hide file tree
Showing 10 changed files with 384 additions and 9 deletions.
6 changes: 3 additions & 3 deletions packages/date/src/lib/rrule/date.rrule.parse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TimezoneString, CommaSeparatedString, flattenArray, Maybe } from '@dereekb/util';
import { TimezoneString, CommaSeparatedString, flattenArray, Maybe, splitJoinRemainder } from '@dereekb/util';
import { format } from 'date-fns-tz';
import { DateSet } from '../date';
import { DateTimezoneBaseDateConverter, DateTimezoneUtcNormalInstance } from '../date/date.timezone';
Expand Down Expand Up @@ -225,15 +225,15 @@ export class DateRRuleParseUtility {
}

static parseRawParam(param: RRuleRawParamString): RRuleParam {
const [key, value] = param.split('=', 2);
const [key, value] = splitJoinRemainder(param, '=', 2);
return {
key,
value
};
}

static parseRawLine(line: RRuleLineString): RRuleRawLine {
const [params, values] = line.split(RRuleStringSplitter, 2);
const [params, values] = splitJoinRemainder(line, RRuleStringSplitter, 2);

if (line.length === 1) {
return {
Expand Down
19 changes: 19 additions & 0 deletions packages/util/src/lib/contact/domain.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { readEmailDomainFromUrlOrEmailAddress } from './domain';

describe('readDomainFromUrlOrEmailAddress()', () => {
it('should read the email domain from a url', () => {
const domain = 'dereekb.com';
const testUrl = `https://${domain}/test/place/1`;
const result = readEmailDomainFromUrlOrEmailAddress(testUrl);

expect(result).toBe(domain);
});

it('should ignore the www in the domain', () => {
const domain = 'dereekb.com';
const testUrl = `https://www.${domain}/test/place/1`;
const result = readEmailDomainFromUrlOrEmailAddress(testUrl);

expect(result).toBe(domain);
});
});
9 changes: 6 additions & 3 deletions packages/util/src/lib/contact/domain.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { uniqueCaseInsensitiveStrings } from '../array';
import { websiteDomainAndPathPairFromWebsiteUrl } from '../string/url';
import { EmailAddress } from './email';

export type EmailAddressDomain = string; // Domain name of an email address.
Expand All @@ -24,7 +25,7 @@ export function readDomainFromEmailAddress(address: EmailAddress): EmailAddressD
* @param urlLikeInput
* @returns The domain
*/
export function readDomainFromUrlOrEmailAddress(urlLikeInput: string | EmailAddress | EmailAddressDomain): EmailAddressDomain {
export function readEmailDomainFromUrlOrEmailAddress(urlLikeInput: string | EmailAddress | EmailAddressDomain): EmailAddressDomain {
const emailSplit = urlLikeInput.split('@');
const url = emailSplit[emailSplit.length - 1];

Expand All @@ -33,8 +34,7 @@ export function readDomainFromUrlOrEmailAddress(urlLikeInput: string | EmailAddr
if (emailSplit.length > 1) {
domain = url;
} else {
const httpSplit = urlLikeInput.split('://');
domain = httpSplit[httpSplit.length - 1];
domain = websiteDomainAndPathPairFromWebsiteUrl(url).domain;

// strip out www. if it is provided, as it is a 'special' domain type,
// and emails probably don't come from there.
Expand All @@ -45,3 +45,6 @@ export function readDomainFromUrlOrEmailAddress(urlLikeInput: string | EmailAddr

return domain;
}

// MARK: Compat
export const readDomainFromUrlOrEmailAddress = readEmailDomainFromUrlOrEmailAddress;
34 changes: 32 additions & 2 deletions packages/util/src/lib/model/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { symmetricDifferenceKeys } from '../set/set';
import { findUnique } from '../array/array.unique';
import { ReadKeyFunction, ReadKeysFunction } from '../key';
import { Maybe } from '../value/maybe.type';
import { MapFunction } from '../value/map';

/**
* A string model key
Expand All @@ -13,22 +14,27 @@ export type ModelKey = string;
*/
export type ModelTypeString = string;

export const DEFAULT_UNKNOWN_MODEL_TYPE_STRING: ModelTypeString = 'unknown';

/**
* A model with an identifier on the "id" key.
*/
export interface UniqueModel {
id?: ModelKey;
}

export interface TypedModel {
type: ModelTypeString;
}

export interface NamedUniqueModel extends UniqueModel {
name?: string;
}

export type ModelOrKey<T extends UniqueModel> = T | ModelKey;

export interface ModelKeyTypePair {
export interface ModelKeyTypePair extends TypedModel {
key: ModelKey;
type: ModelTypeString;
}

/**
Expand All @@ -48,6 +54,7 @@ export interface ReadModelKeyParams<T> {
}

export type ReadModelKeyFunction<T> = ReadKeyFunction<T, ModelKey>;
export type ReadModelTypeFunction<T> = ReadKeyFunction<T, ModelTypeString>;
export type ReadRelationKeysFunction<T> = ReadKeysFunction<T, ModelKey>;

export type MultiModelKeyMap<T> = Map<string, T>;
Expand Down Expand Up @@ -205,3 +212,26 @@ export function decodeModelKeyTypePair(linkKey: ModelKey): ModelKeyTypePair {
key: split[1]
};
}

// MARK: Type
/**
* A type and data pair.
*/
export interface ModelTypeDataPair<T = unknown> extends TypedModel {
data: T;
}

/**
* Used for converting the input data into a ModelTypeDataPair value.
*/
export type ModelTypeDataPairFactory<T> = MapFunction<T, ModelTypeDataPair<T>>;

export function modelTypeDataPairFactory<T>(typeReader: ReadModelTypeFunction<T>, defaultType = DEFAULT_UNKNOWN_MODEL_TYPE_STRING): ModelTypeDataPairFactory<T> {
return (data: T) => {
const type: ModelTypeString = typeReader(data) ?? defaultType;
return {
type,
data
};
};
}
6 changes: 6 additions & 0 deletions packages/util/src/lib/path/path.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ describe('slashPathName', () => {
});

describe('slashPathType', () => {
it('files with more than 1 dot should return invalid', () => {
const typedFilePath: SlashPathTypedFile = 'wMNzlhSlp6Gb93.V8u.4.Rs/CCCC_KGML3FKTP.pdf.tmp';
const type = slashPathType(typedFilePath);
expect(type).toBe('invalid');
});

it('should identify a typed file', () => {
const typedFilePath: SlashPathTypedFile = 'wMNzlhSlp6Gb93V8u4Rs/CCCC_KGML3FKTP.pdf';
const type = slashPathType(typedFilePath);
Expand Down
2 changes: 1 addition & 1 deletion packages/util/src/lib/path/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export type SlashPathType = 'folder' | 'file' | 'typedfile' | 'invalid';
* @returns
*/
export function slashPathType(input: SlashPath): SlashPathType {
const dotCount = input.split(SLASH_PATH_FILE_TYPE_SEPARATOR, 2).length - 1;
const dotCount = input.split(SLASH_PATH_FILE_TYPE_SEPARATOR, 3).length - 1;
let type: SlashPathType;

switch (dotCount) {
Expand Down
1 change: 1 addition & 0 deletions packages/util/src/lib/string/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './char';
export * from './string';
export * from './replace';
export * from './transform';
export * from './url';
50 changes: 50 additions & 0 deletions packages/util/src/lib/string/string.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { splitJoinRemainder } from './string';

describe('splitJoinRemainder()', () => {
it('should handle having a single value', () => {
const values = ['a'];
const string = values.join(',');

const result = splitJoinRemainder(string, ',', 3);
expect(result[0]).toBe(values[0]);
});

it('should split the value up to the limit (1) and join the remainder', () => {
const values = ['a,b,c,d,e'];
const string = values.join(',');

const result = splitJoinRemainder(string, ',', 1);
expect(result[0]).toBe(values[0]);
});

it('should split the value up to the limit (2) and join the remainder', () => {
const values = ['a', 'b,c,d,e'];
const string = values.join(',');

const result = splitJoinRemainder(string, ',', 2);
expect(result[0]).toBe(values[0]);
expect(result[1]).toBe(values[1]);
});

it('should split the value up to the limit (3) and join the remainder', () => {
const values = ['a', 'b', 'c,d,e'];
const string = values.join(',');

const result = splitJoinRemainder(string, ',', 3);
expect(result[0]).toBe(values[0]);
expect(result[1]).toBe(values[1]);
expect(result[2]).toBe(values[2]);
});

it('should split the value up to the limit (8) and join the remainder', () => {
const values = ['a', 'b', 'c', 'd', 'e'];
const string = values.join(',');

const result = splitJoinRemainder(string, ',', 8);
expect(result[0]).toBe(values[0]);
expect(result[1]).toBe(values[1]);
expect(result[2]).toBe(values[2]);
expect(result[3]).toBe(values[3]);
expect(result[4]).toBe(values[4]);
});
});
102 changes: 102 additions & 0 deletions packages/util/src/lib/string/url.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { isolateWebsitePathFunction, removeHttpFromUrl, websiteDomainAndPathPairFromWebsiteUrl, websitePathAndQueryPair, websitePathFromWebsiteDomainAndPath, websitePathFromWebsiteUrl } from './url';

const domain = 'dereekb.com';

describe('isolateWebsitePathFunction()', () => {
describe('function', () => {
const pathInner = '/hello/world';
const basePath = '/test';
const path = `${basePath}${pathInner}`;
const fullUrl = `https://${domain}${path}`;

const isolateFn = isolateWebsitePathFunction();

it('should isolate the path from the input', () => {
const result = isolateFn(fullUrl);
expect(result).toBe(path);
});

it('should retain any query parameters', () => {
const query = '?test=true';
const result = isolateFn(fullUrl + query);
expect(result).toBe(path + query);
});

describe('ignoredBasePath', () => {
const isolateFn = isolateWebsitePathFunction({
ignoredBasePath: 'test'
});

it('should isolate the path from the input without the base path', () => {
const result = isolateFn(fullUrl);
expect(result).toBe(pathInner);
});
});

describe('removeQueryParameters', () => {
const isolateFn = isolateWebsitePathFunction({
removeQueryParameters: true
});

it('should isolate the path from the input without the query parameters', () => {
const result = isolateFn(fullUrl + '?test=true');
expect(result).toBe(path);
});
});
});
});

describe('websitePathAndQueryPair()', () => {
it('should return the website path from the input url', () => {
const path = '/test/hello/world';
const query = '?hello=world';

const result = websitePathAndQueryPair(`${path}${query}`);
expect(result.path).toBe(path);
expect(result.query).toBe(query);
});
});

describe('websitePathFromWebsiteUrl()', () => {
it('should return the website path from the input url', () => {
const path = '/test/hello/world';
const result = websitePathFromWebsiteUrl(`https://${domain}${path}`);
expect(result).toBe(path);
});
});

describe('websiteDomainAndPathPairFromWebsiteUrl()', () => {
it('should return the website path from the input url', () => {
const path = '/test/hello/world';
const result = websiteDomainAndPathPairFromWebsiteUrl(`https://${domain}${path}`);
expect(result.domain).toBe(domain);
expect(result.path).toBe(path);
});
});

describe('websitePathFromWebsiteDomainAndPath()', () => {
it('should return the website path from the domain', () => {
const path = '/test/hello/world';
const result = websitePathFromWebsiteDomainAndPath(`${domain}${path}`);
expect(result).toBe(path);
});

it('should return only a slash if the input is a domain', () => {
const result = websitePathFromWebsiteDomainAndPath(domain);
expect(result).toBe('/');
});
});

describe('removeHttpFromUrl()', () => {
const domain = 'dereekb.com';

it('should remove http:// from the string', () => {
const result = removeHttpFromUrl(`http://${domain}`);
expect(result).toBe(domain);
});

it('should remove https:// from the string', () => {
const result = removeHttpFromUrl(`https://${domain}`);
expect(result).toBe(domain);
});
});
Loading

0 comments on commit ed3430f

Please sign in to comment.