1+ CREATE SCHEMA inflection ;
2+
3+ GRANT USAGE ON SCHEMA inflection TO PUBLIC;
4+
5+ ALTER DEFAULT PRIVILEGES IN SCHEMA inflection
6+ GRANT EXECUTE ON FUNCTIONS TO PUBLIC;
7+
8+ CREATE FUNCTION inflection .no_consecutive_caps_till_end ( str text ) RETURNS text AS $EOFCODE$
9+ DECLARE
10+ result text [];
11+ temp text ;
12+ BEGIN
13+ FOR result IN
14+ SELECT regexp_matches(str, E' ([A-Z])([A-Z]+$)' , ' g' )
15+ LOOP
16+ temp = result[1 ] || lower (result[2 ]);
17+ str = replace(str, result[1 ] || result[2 ], temp);
18+ END LOOP;
19+ return str;
20+ END;
21+ $EOFCODE$ LANGUAGE plpgsql STABLE;
22+
23+ CREATE FUNCTION inflection .no_consecutive_caps_till_lower ( str text ) RETURNS text AS $EOFCODE$
24+ DECLARE
25+ result text [];
26+ temp text ;
27+ BEGIN
28+ FOR result IN
29+ SELECT regexp_matches(str, E' ([A-Z])([A-Z]+)[A-Z][a-z]' , ' g' )
30+ LOOP
31+ temp = result[1 ] || lower (result[2 ]);
32+ str = replace(str, result[1 ] || result[2 ], temp);
33+ END LOOP;
34+
35+ return str;
36+ END;
37+ $EOFCODE$ LANGUAGE plpgsql STABLE;
38+
39+ CREATE FUNCTION inflection .no_consecutive_caps ( str text ) RETURNS text AS $EOFCODE$
40+ select inflection .no_consecutive_caps_till_lower (inflection .no_consecutive_caps_till_end (str));
41+ $EOFCODE$ LANGUAGE sql STABLE;
42+
43+ CREATE FUNCTION inflection .pg_slugify ( value text , allow_unicode boolean ) RETURNS text AS $EOFCODE$
44+ WITH normalized AS (
45+ SELECT
46+ CASE WHEN allow_unicode THEN
47+ value
48+ ELSE
49+ unaccent (value)
50+ END AS value
51+ ),
52+ no_consecutive_caps AS (
53+ SELECT
54+ inflection .no_consecutive_caps (value) AS value
55+ FROM
56+ normalized
57+ ),
58+ remove_chars AS (
59+ SELECT
60+ regexp_replace(value, E' [^\\ w\\ s-]' , ' ' , ' gi' ) AS value
61+ FROM
62+ no_consecutive_caps
63+ ),
64+ trimmed AS (
65+ SELECT
66+ trim (value) AS value
67+ FROM
68+ remove_chars
69+ ),
70+ hyphenated AS (
71+ SELECT
72+ regexp_replace(value, E' [-\\ s]+' , ' -' , ' gi' ) AS value
73+ FROM
74+ trimmed
75+ ),
76+ underscored AS (
77+ SELECT
78+ regexp_replace(value, E' [-]+' , ' _' , ' gi' ) AS value
79+ FROM
80+ hyphenated
81+ ),
82+ removedups AS (
83+ SELECT
84+ regexp_replace(value, E' [_]+' , ' _' , ' gi' ) AS value
85+ FROM
86+ underscored
87+ )
88+ SELECT
89+ value
90+ FROM
91+ removedups;
92+ $EOFCODE$ LANGUAGE sql STRICT IMMUTABLE;
93+
94+ CREATE FUNCTION inflection .pg_slugify ( text ) RETURNS text AS $EOFCODE$SELECT inflection .pg_slugify ($1 , false)$EOFCODE$ LANGUAGE sql IMMUTABLE;
95+
96+ CREATE FUNCTION inflection .no_single_underscores_in_beginning ( str text ) RETURNS text AS $EOFCODE$
97+ DECLARE
98+ result text [];
99+ temp text ;
100+ BEGIN
101+ FOR result IN
102+ SELECT regexp_matches(str, E' (^[a-z])(_)' , ' g' )
103+ LOOP
104+ str = replace(str, result[1 ] || result[2 ], result[1 ]);
105+ END LOOP;
106+ return str;
107+ END;
108+ $EOFCODE$ LANGUAGE plpgsql STABLE;
109+
110+ CREATE FUNCTION inflection .no_single_underscores_at_end ( str text ) RETURNS text AS $EOFCODE$
111+ DECLARE
112+ result text [];
113+ temp text ;
114+ BEGIN
115+ FOR result IN
116+ SELECT regexp_matches(str, E' (_)([a-z]$)' , ' g' )
117+ LOOP
118+ str = replace(str, result[1 ] || result[2 ], result[2 ]);
119+ END LOOP;
120+
121+ return str;
122+ END;
123+ $EOFCODE$ LANGUAGE plpgsql STABLE;
124+
125+ CREATE FUNCTION inflection .no_single_underscores_in_middle ( str text ) RETURNS text AS $EOFCODE$
126+ DECLARE
127+ result text [];
128+ temp text ;
129+ BEGIN
130+ FOR result IN
131+ SELECT regexp_matches(str, E' (_)([a-z]_)' , ' g' )
132+ LOOP
133+ str = replace(str, result[1 ] || result[2 ], result[2 ]);
134+ END LOOP;
135+
136+ return str;
137+ END;
138+ $EOFCODE$ LANGUAGE plpgsql STABLE;
139+
140+ CREATE FUNCTION inflection .no_single_underscores ( str text ) RETURNS text AS $EOFCODE$
141+ select
142+ inflection .no_single_underscores_in_middle (inflection .no_single_underscores_at_end (inflection .no_single_underscores_in_beginning (str)));
143+ $EOFCODE$ LANGUAGE sql STABLE;
144+
145+ CREATE FUNCTION inflection .underscore ( str text ) RETURNS text AS $EOFCODE$
146+ WITH slugged AS (
147+ SELECT
148+ inflection .pg_slugify (str) AS value
149+ ),
150+ convertedupper AS (
151+ SELECT
152+ lower (regexp_replace(value, E' ([A-Z])' , E' \_\\ 1' , ' g' )) AS value
153+ FROM
154+ slugged
155+ ),
156+ noprefix AS (
157+ SELECT
158+ regexp_replace(value, E' ^_' , ' ' , ' g' ) AS value
159+ FROM
160+ convertedupper
161+ ),
162+ removedups AS (
163+ SELECT
164+ regexp_replace(value, E' [_]+' , ' _' , ' gi' ) AS value
165+ FROM
166+ noprefix
167+ ),
168+ stripedges AS (
169+ SELECT
170+ regexp_replace(regexp_replace(value, E' ([A-Z])_$' , E' \\ 1' , ' gi' ), E' ^_([A-Z])' , E' \\ 1' , ' gi' ) AS value
171+ FROM
172+ removedups
173+ ),
174+ nosingles AS (
175+ SELECT
176+ inflection .no_single_underscores (value) AS value
177+ FROM
178+ stripedges
179+ )
180+ SELECT
181+ value
182+ FROM
183+ nosingles;
184+ $EOFCODE$ LANGUAGE sql IMMUTABLE;
185+
186+ CREATE FUNCTION inflection .camel ( str text ) RETURNS text AS $EOFCODE$
187+ DECLARE
188+ result text [];
189+ BEGIN
190+ str = inflection .underscore (str);
191+ FOR result IN
192+ SELECT regexp_matches(str, E' (_[a-zA-Z0-9])' , ' g' )
193+ LOOP
194+ str = replace(str, result[1 ], upper (result[1 ]));
195+ END LOOP;
196+ return regexp_replace(substring (str FROM 1 FOR 1 ) || substring (str FROM 2 FOR length(str)), E' [_]+' , ' ' , ' gi' );
197+ END;
198+ $EOFCODE$ LANGUAGE plpgsql STABLE;
199+
200+ CREATE FUNCTION inflection .dashed ( str text ) RETURNS text AS $EOFCODE$
201+ WITH underscored AS (
202+ SELECT
203+ inflection .underscore (str) AS value
204+ ),
205+ dashes AS (
206+ SELECT
207+ regexp_replace(value, ' _' , ' -' , ' gi' ) AS value
208+ FROM
209+ underscored
210+ )
211+ SELECT
212+ value
213+ FROM
214+ dashes;
215+ $EOFCODE$ LANGUAGE sql IMMUTABLE;
216+
217+ CREATE FUNCTION inflection .pascal ( str text ) RETURNS text AS $EOFCODE$
218+ DECLARE
219+ result text [];
220+ BEGIN
221+ str = inflection .camel (str);
222+ return upper (substring (str FROM 1 FOR 1 )) || substring (str FROM 2 FOR length(str));
223+ END;
224+ $EOFCODE$ LANGUAGE plpgsql STABLE;
225+
226+ CREATE TABLE inflection .inflection_rules (
227+ id uuid PRIMARY KEY DEFAULT ( uuid_generate_v4() ),
228+ type text ,
229+ test text ,
230+ replacement text
231+ );
232+
233+ GRANT SELECT ON TABLE inflection .inflection_rules TO PUBLIC;
234+
235+ CREATE FUNCTION inflection .plural ( str text ) RETURNS text AS $EOFCODE$
236+ DECLARE
237+ result record;
238+ matches text [];
239+ BEGIN
240+ FOR result IN
241+ SELECT * FROM inflection .inflection_rules where type= ' plural'
242+ LOOP
243+ matches = regexp_matches(str, result .test , ' gi' );
244+ IF (array_length(matches, 1 ) > 0 ) THEN
245+ IF (result .replacement IS NULL ) THEN
246+ return str;
247+ END IF;
248+ str = regexp_replace(str, result .test , result .replacement , ' gi' );
249+ return str;
250+ END IF;
251+ END LOOP;
252+ return str;
253+ END;
254+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
255+
256+ CREATE FUNCTION inflection .uncountable_words ( ) RETURNS text [] AS $EOFCODE$
257+ select ARRAY[ 'accommodation', 'adulthood', 'advertising', 'advice', 'aggression', 'aid', 'air', 'aircraft', 'alcohol', 'anger', 'applause', 'arithmetic', 'assistance', 'athletics', 'bacon', 'baggage', 'beef', 'biology', 'blood', 'botany', 'bread', 'butter', 'carbon', 'cardboard', 'cash', 'chalk', 'chaos', 'chess', 'crossroads', 'countryside', 'dancing', 'deer', 'dignity', 'dirt', 'dust', 'economics', 'education', 'electricity', 'engineering', 'enjoyment', 'envy', 'equipment', 'ethics', 'evidence', 'evolution', 'fame', 'fiction', 'flour', 'flu', 'food', 'fuel', 'fun', 'furniture', 'gallows', 'garbage', 'garlic', 'genetics', 'gold', 'golf', 'gossip', 'grammar', 'gratitude', 'grief', 'guilt', 'gymnastics', 'happiness', 'hardware', 'harm', 'hate', 'hatred', 'health', 'heat', 'help', 'homework', 'honesty', 'honey', 'hospitality', 'housework', 'humour', 'hunger', 'hydrogen', 'ice', 'importance', 'inflation', 'information', 'innocence', 'iron', 'irony', 'jam', 'jewelry', 'judo', 'karate', 'knowledge', 'lack', 'laughter', 'lava', 'leather', 'leisure', 'lightning', 'linguine', 'linguini', 'linguistics', 'literature', 'litter', 'livestock', 'logic', 'loneliness', 'luck', 'luggage', 'macaroni', 'machinery', 'magic', 'management', 'mankind', 'marble', 'mathematics', 'mayonnaise', 'measles', 'methane', 'milk', 'minus', 'money', 'mud', 'music', 'mumps', 'nature', 'news', 'nitrogen', 'nonsense', 'nurture', 'nutrition', 'obedience', 'obesity', 'oxygen', 'pasta', 'patience', 'physics', 'poetry', 'pollution', 'poverty', 'pride', 'psychology', 'publicity', 'punctuation', 'quartz', 'racism', 'relaxation', 'reliability', 'research', 'respect', 'revenge', 'rice', 'rubbish', 'rum', 'safety', 'scenery', 'seafood', 'seaside', 'series', 'shame', 'sheep', 'shopping', 'sleep', 'smoke', 'smoking', 'snow', 'soap', 'software', 'soil', 'spaghetti', 'species', 'steam', 'stuff', 'stupidity', 'sunshine', 'symmetry', 'tennis', 'thirst', 'thunder', 'timber', 'traffic', 'transportation', 'trust', 'underwear', 'unemployment', 'unity', 'validity', 'veal', 'vegetation', 'vegetarianism', 'vengeance', 'violence', 'vitality', 'warmth', 'wealth', 'weather', 'welfare', 'wheat', 'wildlife', 'wisdom', 'yoga', 'zinc', 'zoology' ];
258+ $EOFCODE$ LANGUAGE sql IMMUTABLE;
259+
260+ CREATE FUNCTION inflection .should_skip_uncountable ( str text ) RETURNS boolean AS $EOFCODE$
261+ SELECT
262+ str = ANY (inflection .uncountable_words ());
263+ $EOFCODE$ LANGUAGE sql IMMUTABLE;
264+
265+ CREATE FUNCTION inflection .singular ( str text ) RETURNS text AS $EOFCODE$
266+ DECLARE
267+ result record;
268+ matches text [];
269+ BEGIN
270+ FOR result IN
271+ SELECT * FROM inflection .inflection_rules where type= ' singular'
272+ LOOP
273+ matches = regexp_matches(str, result .test , ' gi' );
274+ IF (array_length(matches, 1 ) > 0 ) THEN
275+ IF (result .replacement IS NULL ) THEN
276+ return str;
277+ END IF;
278+ str = regexp_replace(str, result .test , result .replacement , ' gi' );
279+ return str;
280+ END IF;
281+ END LOOP;
282+ return str;
283+ END;
284+ $EOFCODE$ LANGUAGE plpgsql IMMUTABLE;
285+
286+ CREATE FUNCTION inflection .slugify ( value text , allow_unicode boolean ) RETURNS text AS $EOFCODE$
287+ WITH normalized AS (
288+ SELECT
289+ CASE WHEN allow_unicode THEN
290+ value
291+ ELSE
292+ unaccent (value)
293+ END AS value
294+ ),
295+ remove_chars AS (
296+ SELECT
297+ regexp_replace(value, E' [^\\ w\\ s-]' , ' ' , ' gi' ) AS value
298+ FROM
299+ normalized
300+ ),
301+ lowercase AS (
302+ SELECT
303+ lower (value) AS value
304+ FROM
305+ remove_chars
306+ ),
307+ trimmed AS (
308+ SELECT
309+ trim (value) AS value
310+ FROM
311+ lowercase
312+ ),
313+ hyphenated AS (
314+ SELECT
315+ regexp_replace(value, E' [-\\ s]+' , ' -' , ' gi' ) AS value
316+ FROM
317+ trimmed
318+ )
319+ SELECT
320+ value
321+ FROM
322+ hyphenated;
323+ $EOFCODE$ LANGUAGE sql STRICT IMMUTABLE;
324+
325+ CREATE FUNCTION inflection .slugify ( text ) RETURNS text AS $EOFCODE$SELECT inflection .slugify ($1 , false)$EOFCODE$ LANGUAGE sql IMMUTABLE;
326+
327+ INSERT INTO inflection.inflection_rules ( type, test, replacement ) VALUES ('plural', '^(m|wom)en$', NULL), ('plural', '(pe)ople$', NULL), ('plural', '(child)ren$', NULL), ('plural', '([ti])a$', NULL), ('plural', '((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$', NULL), ('plural', '(hi|ti)ves$', NULL), ('plural', '(curve)s$', NULL), ('plural', '([lr])ves$', NULL), ('plural', '([^fo])ves$', NULL), ('plural', '([^aeiouy]|qu)ies$', NULL), ('plural', '(s)eries$', NULL), ('plural', '(m)ovies$', NULL), ('plural', '(x|ch|ss|sh)es$', NULL), ('plural', '([m|l])ice$', NULL), ('plural', '(bus)es$', NULL), ('plural', '(o)es$', NULL), ('plural', '(shoe)s$', NULL), ('plural', '(cris|ax|test)es$', NULL), ('plural', '(octop|vir)i$', NULL), ('plural', '(alias|canvas|status|campus)es$', NULL), ('plural', '^(summons)es$', NULL), ('plural', '^(ox)en', NULL), ('plural', '(matr)ices$', NULL), ('plural', '^feet$', NULL), ('plural', '^teeth$', NULL), ('plural', '^geese$', NULL), ('plural', '(quiz)zes$', NULL), ('plural', '^(whereas)es$', NULL), ('plural', '^(criteri)a$', NULL), ('plural', '^genera$', NULL), ('plural', '^(m|wom)an$', '\1en'), ('plural', '(pe)rson$', '\1ople'), ('plural', '(child)$', '\1ren'), ('plural', '^(ox)$', '\1en'), ('plural', '(ax|test)is$', '\1es'), ('plural', '(octop|vir)us$', '\1i'), ('plural', '(alias|status|canvas|campus)$', '\1es'), ('plural', '^(summons)$', '\1es'), ('plural', '(bu)s$', '\1ses'), ('plural', '(buffal|tomat|potat)o$', '\1oes'), ('plural', '([ti])um$', '\1a'), ('plural', 'sis$', 'ses'), ('plural', '(?:([^f])fe|([lr])f)$', '\1\2ves'), ('plural', '(hi|ti)ve$', '\1ves'), ('plural', '([^aeiouy]|qu)y$', '\1ies'), ('plural', '(matr)ix$', '\1ices'), ('plural', '(vert|ind)ex$', '\1ices'), ('plural', '(x|ch|ss|sh)$', '\1es'), ('plural', '([m|l])ouse$', '\1ice'), ('plural', '^foot$', 'feet'), ('plural', '^tooth$', 'teeth'), ('plural', '^goose$', 'geese'), ('plural', '(quiz)$', '\1zes'), ('plural', '^(whereas)$', '\1es'), ('plural', '^(criteri)on$', '\1a'), ('plural', '^genus$', 'genera'), ('plural', 's$', 's'), ('plural', '$', 's'), ('singular', '^(m|wom)an$', NULL), ('singular', '(pe)rson$', NULL), ('singular', '(child)$', NULL), ('singular', '^(ox)$', NULL), ('singular', '(ax|test)is$', NULL), ('singular', '(octop|vir)us$', NULL), ('singular', '(alias|status|canvas|campus)$', NULL), ('singular', '^(summons)$', NULL), ('singular', '(bu)s$', NULL), ('singular', '(buffal|tomat|potat)o$', NULL), ('singular', '([ti])um$', NULL), ('singular', 'sis$', NULL), ('singular', '(?:([^f])fe|([lr])f)$', NULL), ('singular', '(hi|ti)ve$', NULL), ('singular', '([^aeiouy]|qu)y$', NULL), ('singular', '(x|ch|ss|sh)$', NULL), ('singular', '(matr)ix$', NULL), ('singular', '([m|l])ouse$', NULL), ('singular', '^foot$', NULL), ('singular', '^tooth$', NULL), ('singular', '^goose$', NULL), ('singular', '(quiz)$', NULL), ('singular', '^(whereas)$', NULL), ('singular', '^(criteri)on$', NULL), ('singular', '^genus$', NULL), ('singular', '^(m|wom)en$', '\1an'), ('singular', '(pe)ople$', '\1rson'), ('singular', '(child)ren$', '\1'), ('singular', '^genera$', 'genus'), ('singular', '^(criteri)a$', '\1on'), ('singular', '([ti])a$', '\1um'), ('singular', '((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$', '\1\2sis'), ('singular', '(hi|ti)ves$', '\1ve'), ('singular', '(curve)s$', '\1'), ('singular', '([lr])ves$', '\1f'), ('singular', '([a])ves$', '\1ve'), ('singular', '([^fo])ves$', '\1fe'), ('singular', '(m)ovies$', '\1ovie'), ('singular', '([^aeiouy]|qu)ies$', '\1y'), ('singular', '(s)eries$', '\1eries'), ('singular', '(x|ch|ss|sh)es$', '\1'), ('singular', '([m|l])ice$', '\1ouse'), ('singular', '(bus)es$', '\1'), ('singular', '(o)es$', '\1'), ('singular', '(shoe)s$', '\1'), ('singular', '(cris|ax|test)es$', '\1is'), ('singular', '(octop|vir)i$', '\1us'), ('singular', '(alias|canvas|status|campus)es$', '\1'), ('singular', '^(summons)es$', '\1'), ('singular', '^(ox)en', '\1'), ('singular', '(matr)ices$', '\1ix'), ('singular', '(vert|ind)ices$', '\1ex'), ('singular', '^feet$', 'foot'), ('singular', '^teeth$', 'tooth'), ('singular', '^geese$', 'goose'), ('singular', '(quiz)zes$', '\1'), ('singular', '^(whereas)es$', '\1'), ('singular', 'ss$', 'ss'), ('singular', 's$', '');
328+
329+ CREATE INDEX inflection_rules_type_idx ON inflection .inflection_rules ( type );
0 commit comments