Skip to content

Instantly share code, notes, and snippets.

@honboubao
Last active July 17, 2023 16:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save honboubao/149b6bf2450eb03c28bfc05fe1f623fc to your computer and use it in GitHub Desktop.
Save honboubao/149b6bf2450eb03c28bfc05fe1f623fc to your computer and use it in GitHub Desktop.
concatNullable
/**
* Ein fixer String, der in {@link Template} mit anderen Werten verknüpft wird und abhängig von der Gültigkeit der anderen Werte mitangezeigt wird.
*
* Das Trennzeichen darf kein Empty-String sein!
*/
export type Trennzeichen = string; // Empty string als Trennzeichen nicht erlaubt
/**
* Ein Wert, z.B. von einer Variable, der konkateniert werden soll. Ungültige Werte (null, undefined, Empty-String) werden nicht ausgegeben.
* Ungültige Werte beeinflussen ggf. auch die Ausgabe von Trennzeichen im gleichen {@link Template}
*/
export type Wert = string | number | null | undefined;
/**
* Ein Wert oder ein verschachteltes Template, das in einem Template mit anderen Elementen verknüpft wird.
*/
export type Einsetzung = Wert | Template;
/**
* Wird in {@link ConcatTemplate} verwendet, um einzelne Werte direkt zugehörigen Trennzeichen zu konkatenieren.
*/
export type WertTemplate = [Wert];
/**
* Wenn die Einsetzung einen gültigen Wert hat (nicht null, undefined oder Empty-String), wird der Wert mit den Trennzeichen vorangestellt ausgegeben.
* Ansonsten wird nichts (Empty-String) ausgegeben.
*/
export type PrefixTemplate = [Trennzeichen, Einsetzung];
/**
* Wenn die Einsetzung einen gültigen Wert hat (nicht null, undefined oder Empty-String), wird der Wert mit den Trennzeichen nachgestellt ausgegeben.
* Ansonsten wird nichts (Empty-String) ausgegeben.
*/
export type SuffixTemplate = [Einsetzung, Trennzeichen];
/**
* Wenn beide Einsetzungen einen gültigen Wert haben (nicht null, undefined oder Empty-String), werden die Wert mit den Trennzeichen verbunden.
* Wenn nur eine Einsetzung einen gültigen Wert hat, wird dieser unverändert augegeben.
* Ansonsten wird nichts (Empty-String) ausgegeben.
*/
export type InfixTemplate = [Einsetzung, Trennzeichen, Einsetzung];
/**
* Wenn die Einsetzung einen gültigen Wert hat (nicht null, undefined oder Empty-String), wird der Wert mit den Trennzeichen umgeben.
* Ansonsten wird nichts (Empty-String) ausgegeben.
*/
export type CircumfixTemplate = [Trennzeichen, Einsetzung, Trennzeichen];
/**
* Konkateniert alle Templates im Array, die einen gültigen Wert ergeben (nicht null, undefined oder Empty-String).
*
* Da das Array nur Templates beinhalten kann, muss für einen Wert, der direkt konkateniert werden soll, dieser
* als Single-Element-Array (als {@link WertTemplate}) angegeben werden.
*/
export type ConcatTemplate = Template[];
/**
* Template für {@link concatNullable} mit Werten und Trennzeichen. Der Aufbau des Templates (welche Templates genau, wie verschachtelt angegeben werden)
* bestimmt, welche Werte und Trennzeichen und welchen Bedingungen angezeigt werden.
*
* Trennzeichen sind dabei non-nullable Strings. Empty-Strings sind als Trennzeichen nicht erlaubt, das Verhalten mit Empty-Strings als Leerzeichen ist nicht definiert.
* Werte können string, number, null und undefined sein.
*/
export type Template = WertTemplate | PrefixTemplate | SuffixTemplate | InfixTemplate | CircumfixTemplate | ConcatTemplate;
function isBlank(s: unknown): boolean {
return s == null || s === '';
}
/**
* Konkateniert Werte und Trennzeichen zu einem String, wobei null, undefined und Empty-String Werte nicht ausgegeben werden und
* je nach Template-Aufbau zugehörige Trennzeichen ebenfalls ausgelassen werden.
*
* Siehe auch {@link Template}
*/
export function concatNullable(template: Template): string {
// verschachtelte Templates rekursiv zu Strings auflösen
const parts = template.map((p) => {
if (!Array.isArray(p)) {
return isBlank(p) ? '' : String(p);
}
return concatNullable(p);
});
// Concat Template, i.e. nur Einsetzungen (oder leeres Array) -> alles übernehmen, was nicht null ist
if (template.every(Array.isArray)) {
return parts.join('');
}
const [blank0, blank1, blank2] = parts.map(isBlank);
if (parts.length === 1) {
// Value Einsetzung innerhalb eines Concatss -> einfach den Wert übernehmen, wenn es nicht null ist
return blank0 ? '' : parts[0];
}
if (parts.length === 2) {
// Prefix oder Suffix Template -> wenn die Einsetzung null ist, wird auch das Trennzeichen verworfen...
if (blank0 || blank1) {
return '';
}
// ...ansonsten wird die Einsetzung zusammen mit dem Trennzeichen angezeigt
return parts.join('');
} else if (parts.length === 3) {
// Infix Template beide Einsetzungen null oder Circumfix Template Einsetzung leer -> Einsetzungen mitsamt Trennzeichen verwerfen
if ((blank0 && blank2) || blank1) {
return '';
}
// Infix Template eine der beiden Einsetzungen null -> die jeweils andere Einsetzung ohne Trennzeichen anzeigen
if (blank0) {
return parts[2];
}
if (blank2) {
return parts[0];
}
// Infix Template mit beiden Einsetzungen oder Circumfix mit Einsetzung vorhanden -> Einsetzungen mit Trennzeichen anzeigen
return parts.join('');
} else {
// eslint-disable-next-line no-console
console.warn('Invalid arguments for concatNullable:', parts);
return '';
}
}
describe('string-utils: concatNullable', () => {
describe('mit Benutzer-String', () => {
let abteilung: string | null;
let vorname: string | null;
let nachname: string | null;
let durchwahl: string | null;
let userId: string | null;
let vsnr: string | null;
let alter: number | null;
const expectStringToBe = (expected: string): void => {
// Abteilung, Vorname Nachname, Durchwahl (UserID) / VSNR Alter
expect(concatNullable([[[[abteilung, ', ', [vorname, ' ', nachname]], ', ', durchwahl], ' ', ['(', userId, ')']], ' / ', [vsnr, ' ', [alter, ' Jahre']]])).toBe(
expected
);
};
beforeEach(() => {
abteilung = 'HSWE';
vorname = 'Hans';
nachname = 'Huber';
durchwahl = '123';
userId = '02u12345';
vsnr = '1111111111';
alter = 32;
});
// Abteilung, Vorname Nachname, Durchwahl (UserID) / VSNR Alter
it('voller String', () => {
expectStringToBe('HSWE, Hans Huber, 123 (02u12345) / 1111111111 32 Jahre');
});
it('ohne vsnr', () => {
vsnr = null;
expectStringToBe('HSWE, Hans Huber, 123 (02u12345) / 32 Jahre');
});
it('ohne alter', () => {
alter = null;
expectStringToBe('HSWE, Hans Huber, 123 (02u12345) / 1111111111');
});
it('ohne vsnr, alter', () => {
vsnr = alter = null;
expectStringToBe('HSWE, Hans Huber, 123 (02u12345)');
});
it('ohne abteilung', () => {
vsnr = alter = abteilung = null;
expectStringToBe('Hans Huber, 123 (02u12345)');
});
it('ohne vorname', () => {
vsnr = alter = vorname = null;
expectStringToBe('HSWE, Huber, 123 (02u12345)');
});
it('ohne nachname', () => {
vsnr = alter = nachname = null;
expectStringToBe('HSWE, Hans, 123 (02u12345)');
});
it('ohne durchwahl', () => {
vsnr = alter = durchwahl = null;
expectStringToBe('HSWE, Hans Huber (02u12345)');
});
it('ohne name, userId', () => {
vsnr = alter = vorname = nachname = userId = null;
expectStringToBe('HSWE, 123');
});
it('ohne abteilung, name', () => {
vsnr = alter = abteilung = vorname = nachname = null;
expectStringToBe('123 (02u12345)');
});
it('ohne name, durchwahl', () => {
vsnr = alter = vorname = nachname = durchwahl = null;
expectStringToBe('HSWE (02u12345)');
});
it('ohne abteilung, durchwahl', () => {
vsnr = alter = abteilung = durchwahl = null;
expectStringToBe('Hans Huber (02u12345)');
});
it('ohne durchwahl, userId', () => {
vsnr = alter = durchwahl = userId = null;
expectStringToBe('HSWE, Hans Huber');
});
it('ohne name, userId', () => {
vsnr = alter = vorname = nachname = userId = null;
expectStringToBe('HSWE, 123');
});
it('ohne abteilung, nachname, durchwahl', () => {
vsnr = alter = abteilung = nachname = durchwahl = null;
expectStringToBe('Hans (02u12345)');
});
it('ohne abteilung, durchwahl, userId', () => {
vsnr = alter = abteilung = durchwahl = userId = null;
expectStringToBe('Hans Huber');
});
});
describe('mit Adress-String', () => {
let strasse: string | null;
let haus: number | null;
let stiege: number | null;
let tuer: number | null;
let plz: string | null;
let ort: string | null;
const expectStringToBe = (expected: string): void => {
// Straße Hausnummer/Stiege/Türnummer, PLZ - Ort
expect(
concatNullable([
[
[strasse, ' ', haus],
['/', stiege],
['/', tuer ?? (stiege ? '-' : '')],
],
', ',
[plz, ' - ', ort],
])
).toBe(expected);
};
beforeEach(() => {
strasse = 'Bambuslichtung';
haus = 9;
stiege = 2;
tuer = 14;
plz = '1234';
ort = 'Hinterwaldingen';
});
// Abteilung, Vorname Nachname, Durchwahl (UserID) / VSNR Alter
it('voller String', () => {
expectStringToBe('Bambuslichtung 9/2/14, 1234 - Hinterwaldingen');
});
it('ohne haus', () => {
haus = null;
expectStringToBe('Bambuslichtung/2/14, 1234 - Hinterwaldingen');
});
it('ohne stiege', () => {
stiege = null;
expectStringToBe('Bambuslichtung 9/14, 1234 - Hinterwaldingen');
});
it('ohne tuer', () => {
tuer = null;
expectStringToBe('Bambuslichtung 9/2/-, 1234 - Hinterwaldingen');
});
it('ohne plz', () => {
plz = null;
expectStringToBe('Bambuslichtung 9/2/14, Hinterwaldingen');
});
it('ohne ort', () => {
ort = null;
expectStringToBe('Bambuslichtung 9/2/14, 1234');
});
it('ohne haus, stiege', () => {
haus = stiege = null;
expectStringToBe('Bambuslichtung/14, 1234 - Hinterwaldingen');
});
it('ohne stiege, tuer', () => {
stiege = tuer = null;
expectStringToBe('Bambuslichtung 9, 1234 - Hinterwaldingen');
});
it('ohne haus, tuer', () => {
haus = tuer = null;
expectStringToBe('Bambuslichtung/2/-, 1234 - Hinterwaldingen');
});
it('ohne haus, stiege, tuer', () => {
haus = stiege = tuer = null;
expectStringToBe('Bambuslichtung, 1234 - Hinterwaldingen');
});
it('ohne plz, ort', () => {
plz = ort = null;
expectStringToBe('Bambuslichtung 9/2/14');
});
it('ohne haus, stiege, tuer, plz, ort', () => {
haus = stiege = tuer = plz = ort = null;
expectStringToBe('Bambuslichtung');
});
it('ohne strasse, haus, stiege, tuer', () => {
strasse = haus = stiege = tuer = null;
expectStringToBe('1234 - Hinterwaldingen');
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment