@@ -1,11 +1,20 @@
11const prefixes = ['doz', 'mar', 'bin', 'wan', 'sam', 'lit', 'sig', 'hid', 'fid', 'lis', 'sog', 'dir', 'wac', 'sab', 'wis', 'sib', 'rig', 'sol', 'dop', 'mod', 'fog', 'lid', 'hop', 'dar', 'dor', 'lor', 'hod', 'fol', 'rin', 'tog', 'sil', 'mir', 'hol', 'pas', 'lac', 'rov', 'liv', 'dal', 'sat', 'lib', 'tab', 'han', 'tic', 'pid', 'tor', 'bol', 'fos', 'dot', 'los', 'dil', 'for', 'pil', 'ram', 'tir', 'win', 'tad', 'bic', 'dif', 'roc', 'wid', 'bis', 'das', 'mid', 'lop', 'ril', 'nar', 'dap', 'mol', 'san', 'loc', 'nov', 'sit', 'nid', 'tip', 'sic', 'rop', 'wit', 'nat', 'pan', 'min', 'rit', 'pod', 'mot', 'tam', 'tol', 'sav', 'pos', 'nap', 'nop', 'som', 'fin', 'fon', 'ban', 'mor', 'wor', 'sip', 'ron', 'nor', 'bot', 'wic', 'soc', 'wat', 'dol', 'mag', 'pic', 'dav', 'bid', 'bal', 'tim', 'tas', 'mal', 'lig', 'siv', 'tag', 'pad', 'sal', 'div', 'dac', 'tan', 'sid', 'fab', 'tar', 'mon', 'ran', 'nis', 'wol', 'mis', 'pal', 'las', 'dis', 'map', 'rab', 'tob', 'rol', 'lat', 'lon', 'nod', 'nav', 'fig', 'nom', 'nib', 'pag', 'sop', 'ral', 'bil', 'had', 'doc', 'rid', 'moc', 'pac', 'rav', 'rip', 'fal', 'tod', 'til', 'tin', 'hap', 'mic', 'fan', 'pat', 'tac', 'lab', 'mog', 'sim', 'son', 'pin', 'lom', 'ric', 'tap', 'fir', 'has', 'bos', 'bat', 'poc', 'hac', 'tid', 'hav', 'sap', 'lin', 'dib', 'hos', 'dab', 'bit', 'bar', 'rac', 'par', 'lod', 'dos', 'bor', 'toc', 'hil', 'mac', 'tom', 'dig', 'fil', 'fas', 'mit', 'hob', 'har', 'mig', 'hin', 'rad', 'mas', 'hal', 'rag', 'lag', 'fad', 'top', 'mop', 'hab', 'nil', 'nos', 'mil', 'fop', 'fam', 'dat', 'nol', 'din', 'hat', 'nac', 'ris', 'fot', 'rib', 'hoc', 'nim', 'lar', 'fit', 'wal', 'rap', 'sar', 'nal', 'mos', 'lan', 'don', 'dan', 'lad', 'dov', 'riv', 'bac', 'pol', 'lap', 'tal', 'pit', 'nam', 'bon', 'ros', 'ton', 'fod', 'pon', 'sov', 'noc', 'sor', 'lav', 'mat', 'mip', 'fip'];
22const suffixes = ['zod', 'nec', 'bud', 'wes', 'sev', 'per', 'sut', 'let', 'ful', 'pen', 'syt', 'dur', 'wep', 'ser', 'wyl', 'sun', 'ryp', 'syx', 'dyr', 'nup', 'heb', 'peg', 'lup', 'dep', 'dys', 'put', 'lug', 'hec', 'ryt', 'tyv', 'syd', 'nex', 'lun', 'mep', 'lut', 'sep', 'pes', 'del', 'sul', 'ped', 'tem', 'led', 'tul', 'met', 'wen', 'byn', 'hex', 'feb', 'pyl', 'dul', 'het', 'mev', 'rut', 'tyl', 'wyd', 'tep', 'bes', 'dex', 'sef', 'wyc', 'bur', 'der', 'nep', 'pur', 'rys', 'reb', 'den', 'nut', 'sub', 'pet', 'rul', 'syn', 'reg', 'tyd', 'sup', 'sem', 'wyn', 'rec', 'meg', 'net', 'sec', 'mul', 'nym', 'tev', 'web', 'sum', 'mut', 'nyx', 'rex', 'teb', 'fus', 'hep', 'ben', 'mus', 'wyx', 'sym', 'sel', 'ruc', 'dec', 'wex', 'syr', 'wet', 'dyl', 'myn', 'mes', 'det', 'bet', 'bel', 'tux', 'tug', 'myr', 'pel', 'syp', 'ter', 'meb', 'set', 'dut', 'deg', 'tex', 'sur', 'fel', 'tud', 'nux', 'rux', 'ren', 'wyt', 'nub', 'med', 'lyt', 'dus', 'neb', 'rum', 'tyn', 'seg', 'lyx', 'pun', 'res', 'red', 'fun', 'rev', 'ref', 'mec', 'ted', 'rus', 'bex', 'leb', 'dux', 'ryn', 'num', 'pyx', 'ryg', 'ryx', 'fep', 'tyr', 'tus', 'tyc', 'leg', 'nem', 'fer', 'mer', 'ten', 'lus', 'nus', 'syl', 'tec', 'mex', 'pub', 'rym', 'tuc', 'fyl', 'lep', 'deb', 'ber', 'mug', 'hut', 'tun', 'byl', 'sud', 'pem', 'dev', 'lur', 'def', 'bus', 'bep', 'run', 'mel', 'pex', 'dyt', 'byt', 'typ', 'lev', 'myl', 'wed', 'duc', 'fur', 'fex', 'nul', 'luc', 'len', 'ner', 'lex', 'rup', 'ned', 'lec', 'ryd', 'lyd', 'fen', 'wel', 'nyd', 'hus', 'rel', 'rud', 'nes', 'hes', 'fet', 'des', 'ret', 'dun', 'ler', 'nyr', 'seb', 'hul', 'ryl', 'lud', 'rem', 'lys', 'fyn', 'wer', 'ryc', 'sug', 'nys', 'nyl', 'lyn', 'dyn', 'dem', 'lux', 'fed', 'sed', 'bec', 'mun', 'lyr', 'tes', 'mud', 'nyt', 'byr', 'sen', 'weg', 'fyr', 'mur', 'tel', 'rep', 'teg', 'pec', 'nel', 'nev', 'fes'];
3−const illegalCharsRegex = /[^-_*^abcdefghilmnoprstuvwxyz]/g;
3+const illegalIdCharsRegex = /[^-_^~abcdefghilmnoprstuvwxyz]/g;
4+const illegalSearchCharsRegex = /[^-_*^~abcdefghilmnoprstuvwxyz]/g;
5+
6+const wildWord = '******';
7+const moonFiller = `${wildWord}-${wildWord}`;
8+const cometFiller = `-${wildWord}-${wildWord}-${wildWord}--${wildWord}-${wildWord}-${wildWord}-`;
49
510// Error Messages
6−const emptyPatpMessage = "ID cannot be empty."
7−const emptyWordsMessage = "Separators (- ^ _) cannot appear next to each other, or at the beginning or end of an ID."
8−const invalidAbbreviations = "Moon and Comet abbreviations (using ^ or _) can only have two parts."
11+const emptyPatpMessage = "ID cannot be empty.";
12+const improperSigMessage = "There should only be one ~ in an ID, at the beginning.";
13+const emptyWordsMessage = "Separators (- ^ _) cannot appear next to each other, except for a '--' before the last four parts of a comet ID.";
14+const separatorsNeighboringMessage = "Separators (- ^ _) cannot appear next to each other, except for a '--' before the last four parts of a comet ID.";
15+const outsideSeparatorsMessage = "Separators (- ^ _) cannot appear at the beginning or end of an ID.";
16+const invalidCometIdMessage = "Comet IDs should be 16 syllables, in groups of two, separated by '-', and by '--' in the very middle.";;
17+const tooLongAbbreviationMessage = "Moon and Comet abbreviations (using ^ or _) can only have two parts.";
918
1019function illegalCharsMessage(illegalChars: string[]) {
1120 return 'It contains illegal characters: ' + listStrings(illegalChars);
@@ -15,11 +24,11 @@ function multipleWordsLengthMessage(wrongLengthWords: string[]) {
1524 let wordListString = listStrings(wrongLengthWords, true);
1625 let firstHalf = '';
1726 if (wrongLengthWords.length > 1) {
18− firstHalf = `${wordListString} are not valid parts of a multi-part ID.`;
27+ firstHalf = `${wordListString} are the wrong lengths.`;
1928 } else {
20− firstHalf = `${wordListString} is not a valid part of a multi-part ID.`;
29+ firstHalf = `${wordListString} is the wrong length.`;
2130 }
22− return `${firstHalf} Each part must be 6 letters long.`;
31+ return `${firstHalf} Each part of a planet, moon, or comet ID must be 6 letters long.`;
2332}
2433
2534function singleWordLengthMessage(word: string) {
@@ -39,13 +48,32 @@ function invalidSuffixesMessage(syllables: string[]) {
3948 return invalidSyllablesMessage(syllables, 'suffixes');
4049}
4150
42−function stripTilde(str: string): string {
51+function sig(str: string): string {
52+ if (str[0] !== '~') {
53+ return '~' + str;
54+ }
55+ return str;
56+}
57+
58+function desig(str: string): string {
4359 if (str[0] === '~') {
4460 return str.substring(1);
4561 }
4662 return str;
4763}
4864
65+function normalizeId(patp: string): string {
66+ patp = patp.toLowerCase();
67+ patp = sig(patp);
68+ return patp;
69+}
70+
71+function normalizeIdAndDesig(patp: string): string {
72+ patp = patp.toLowerCase();
73+ patp = desig(patp);
74+ return patp;
75+}
76+
4977function listStrings(list: string[], includeAnd: boolean = false): string {
5078 if (includeAnd && list.length > 1) {
5179 return listStrings(list.slice(0, -1)) + ' and ' + listStrings(list.slice(-1));
@@ -54,17 +82,17 @@ function listStrings(list: string[], includeAnd: boolean = false): string {
5482}
5583
5684function splitIdIntoWords(str: string): string[] {
57− return str.split(/[-_^]/);
85+ return str.split(/(?:--|[-_^])/);
5886}
5987
60−function convertSyllableToRegex(str: string): RegExp {
61− str = str.replace(/\*/g, '.');
88+function convertSearchTextToRegex(str: string): RegExp {
89+ str = '^' + str.replace(/\*/g, '.') + '$';
6290 return new RegExp(str);
6391}
6492
6593function syllableSearchValid(search: string, syllables: string[]) {
6694 for (let syllable of syllables) {
67− let regex = convertSyllableToRegex(search);
95+ let regex = convertSearchTextToRegex(search);
6896 if (regex.test(syllable)) {
6997 return true;
7098 }
@@ -80,10 +108,14 @@ function suffixSearchValid(search: string) {
80108 return syllableSearchValid(search, suffixes);
81109}
82110
83−export function validateId(patp: string): string[] {
111+function validateIdSearchText(patp: string): string[] {
112+ return validateId(patp, true);
113+}
114+
115+
116+function validateId(patp: string, isSearchQuery: boolean = false): string[] {
84117 let problems = [];
85− patp = patp.toLowerCase();
86− patp = stripTilde(patp);
118+ patp = normalizeIdAndDesig(patp);
87119
88120 // Validate patp is not empty
89121 if (patp.length === 0) {
@@ -91,12 +123,24 @@ export function validateId(patp: string): string[] {
91123 return problems;
92124 }
93125
126+ // Validate proper use of sig
127+ if (/~/.test(patp)) {
128+ problems.push(improperSigMessage);
129+ }
130+
94131 // Validate all characters are legal
132+ const illegalCharsRegex = isSearchQuery ? illegalSearchCharsRegex : illegalIdCharsRegex;
95133 let illegalChars = patp.match(illegalCharsRegex);
96134 if (illegalChars) {
97135 problems.push(illegalCharsMessage(illegalChars));
98136 }
99137
138+ // Validate proper use of separators
139+ if (/^([-^_].*)?(.*[-^_])?$/.test(patp)) {
140+ problems.push(outsideSeparatorsMessage);
141+ patp = patp.match(/^[-^_]*(.*?)[-^_]*$/)[1];
142+ }
143+
100144 // Validate word lengths
101145 let words = splitIdIntoWords(patp);
102146 if (words.length > 1) {
@@ -124,11 +168,26 @@ export function validateId(patp: string): string[] {
124168 }
125169 }
126170
127− // Validate proper use of separators
128− if (words.length > 2) {
129− let manyPartIdRegex = /^(\w+-)+\w+$/;
171+ // Don't bother checking syllables if there are already problems
172+ if (problems.length > 0) {
173+ return problems;
174+ }
175+
176+ // Validate remaining structural edge cases
177+ if (words.length >= 5) {
178+ let cometIdRegex = /^([\w*]+-)+-([\w*]+-){3}[\w*]+$/;
179+ if (!cometIdRegex.test(patp)) {
180+ problems.push(invalidCometIdMessage);
181+ }
182+ } else if (words.length > 2) {
183+ let manyPartIdRegex = /^([\w*]+-)+[\w*]+$/;
130184 if (!manyPartIdRegex.test(patp)) {
131− problems.push(invalidAbbreviations);
185+ problems.push(tooLongAbbreviationMessage);
186+ }
187+ } else if (words.length == 2) {
188+ let twoPartIdRegex = /^[\w*]+[-^_][\w*]+$/;
189+ if (!twoPartIdRegex.test(patp)) {
190+ problems.push(tooLongAbbreviationMessage);
132191 }
133192 }
134193
@@ -163,4 +222,27 @@ export function validateId(patp: string): string[] {
163222 }
164223
165224 return problems;
166−}
225+}
226+
227+function expandAbbreviatedId(patp: string): string {
228+ patp = normalizeIdAndDesig(patp);
229+ if (/\^/.test(patp)) {
230+ patp = moonFiller + '-' + patp.replace(/\^/, '-');
231+ } else if (/_/.test(patp)) {
232+ patp = patp.replace('_', cometFiller);
233+ }
234+ return patp;
235+}
236+
237+function convertIdSearchTextToQuery(patp: string): RegExp {
238+ return convertSearchTextToRegex(sig(expandAbbreviatedId(patp)));
239+}
240+
241+export {
242+ normalizeId,
243+ normalizeIdAndDesig,
244+ validateIdSearchText,
245+ validateId,
246+ convertIdSearchTextToQuery,
247+ convertSearchTextToRegex,
248+};