poutine-ipsum 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 felixlechat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # poutine-ipsum
2
+
3
+ [![CI](https://github.com/felixlechat/poutine-ipsum/actions/workflows/ci.yml/badge.svg)](https://github.com/felixlechat/poutine-ipsum/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/poutine-ipsum)](https://www.npmjs.com/package/poutine-ipsum)
5
+ [![license](https://img.shields.io/npm/l/poutine-ipsum)](./LICENSE)
6
+
7
+ `poutine-ipsum` is an npm library that generates Lorem Ipsum-like placeholder text about poutine.
8
+
9
+ It mimics the API style of the most-starred comparable JavaScript lorem library (`knicklabs/lorem-ipsum.js`) by exposing:
10
+
11
+ - A class API for repeated generation with shared options.
12
+ - A function API for one-off text generation.
13
+
14
+ Supported languages:
15
+
16
+ - `en` (English)
17
+ - `fr` (French)
18
+
19
+ All generated outputs always begin with:
20
+
21
+ `Poutine ipsum dolor sit amet`
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install poutine-ipsum
27
+ ```
28
+
29
+ ## Using the Class
30
+
31
+ ```js
32
+ import { PoutineIpsum } from "poutine-ipsum";
33
+
34
+ const poutine = new PoutineIpsum({
35
+ language: "fr",
36
+ sentencesPerParagraph: {
37
+ min: 2,
38
+ max: 4,
39
+ },
40
+ wordsPerSentence: {
41
+ min: 4,
42
+ max: 10,
43
+ },
44
+ });
45
+
46
+ console.log(poutine.generateWords(5));
47
+ console.log(poutine.generateSentences(2));
48
+ console.log(poutine.generateParagraphs(3));
49
+ ```
50
+
51
+ ### Class Options
52
+
53
+ - `language`: `"en" | "fr"` (default: `"fr"`)
54
+ - `vocabulary`: `"classic" | "extended" | "all"` (default: `"all"`) — `"classic"` uses only traditional poutine terms (fries, gravy, curds, …), `"extended"` uses only toppings and variations (smoked meat, foie gras, merguez, …), `"all"` combines both
55
+ - `sentencesPerParagraph.min`: number (default: `3`)
56
+ - `sentencesPerParagraph.max`: number (default: `7`)
57
+ - `wordsPerSentence.min`: number (default: `5`)
58
+ - `wordsPerSentence.max`: number (default: `15`)
59
+ - `random`: PRNG function (default: `Math.random`)
60
+ - `suffix`: paragraph separator string (default: OS-specific line ending)
61
+ - `words`: optional custom word list array (overrides language dictionary)
62
+
63
+ ## Using the Function
64
+
65
+ ```js
66
+ import { poutineIpsum } from "poutine-ipsum";
67
+
68
+ const text = poutineIpsum({
69
+ count: 2,
70
+ units: "paragraphs",
71
+ language: "en",
72
+ });
73
+
74
+ console.log(text);
75
+ ```
76
+
77
+ ### Function Options
78
+
79
+ - `count`: number of units to generate (default: `1`)
80
+ - `units`: `"word(s)" | "sentence(s)" | "paragraph(s)"` (default: `"sentences"`)
81
+ - `format`: `"plain" | "html"` (HTML wraps paragraphs in `<p>` tags)
82
+ - `language`: `"en" | "fr"` (default: `"fr"`)
83
+ - `vocabulary`: `"classic" | "extended" | "all"` (default: `"all"`)
84
+ - `paragraphLowerBound`: min sentences per paragraph (default: `3`)
85
+ - `paragraphUpperBound`: max sentences per paragraph (default: `7`)
86
+ - `sentenceLowerBound`: min words per sentence (default: `5`)
87
+ - `sentenceUpperBound`: max words per sentence (default: `15`)
88
+ - `suffix`: separator between generated units (default: OS-specific line ending)
89
+ - `random`: PRNG function (default: `Math.random`)
90
+ - `words`: optional custom words array
91
+
92
+ ## Examples
93
+
94
+ English words:
95
+
96
+ ```js
97
+ poutineIpsum({ count: 8, units: "words", language: "en" });
98
+ // "Poutine ipsum dolor sit amet curds saucebrune ..."
99
+ ```
100
+
101
+ French sentence (default language):
102
+
103
+ ```js
104
+ poutineIpsum({ count: 1, units: "sentences" });
105
+ // "Poutine ipsum dolor sit amet ..."
106
+ ```
107
+
108
+ Classic vocabulary only:
109
+
110
+ ```js
111
+ poutineIpsum({ count: 1, units: "sentences", vocabulary: "classic" });
112
+ // "Poutine ipsum dolor sit amet crispy golden cheddar ..."
113
+ ```
114
+
115
+ HTML paragraphs:
116
+
117
+ ```js
118
+ poutineIpsum({ count: 2, units: "paragraphs", format: "html" });
119
+ // "<p>...</p>\n<p>...</p>"
120
+ ```
121
+
122
+ ## Contributing
123
+
124
+ See [CONTRIBUTING.md](./CONTRIBUTING.md).
125
+
126
+ ## License
127
+
128
+ [MIT](./LICENSE)
129
+
130
+ ## Author
131
+
132
+ Created by [felixlechat](https://github.com/felixlechat).
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "poutine-ipsum",
3
+ "version": "0.1.0",
4
+ "description": "Generate Lorem Ipsum-like placeholder text about poutine in French or English.",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "types": "src/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.d.ts",
11
+ "default": "./src/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "src"
16
+ ],
17
+ "scripts": {
18
+ "test": "node --test",
19
+ "lint": "eslint .",
20
+ "prepare": "husky"
21
+ },
22
+ "lint-staged": {
23
+ "*.js": "eslint"
24
+ },
25
+ "keywords": [
26
+ "lorem",
27
+ "ipsum",
28
+ "poutine",
29
+ "generator"
30
+ ],
31
+ "author": "felixlechat",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/felixlechat/poutine-ipsum.git"
35
+ },
36
+ "homepage": "https://github.com/felixlechat/poutine-ipsum#readme",
37
+ "bugs": "https://github.com/felixlechat/poutine-ipsum/issues",
38
+ "license": "MIT",
39
+ "devDependencies": {
40
+ "@eslint/js": "^10.0.1",
41
+ "eslint": "^10.1.0",
42
+ "husky": "^9.1.7",
43
+ "lint-staged": "^16.4.0",
44
+ "typescript": "^5.9.3"
45
+ }
46
+ }
@@ -0,0 +1,171 @@
1
+ export const DICTIONARIES = {
2
+ en: {
3
+ classic: [
4
+ "poutine",
5
+ "fries",
6
+ "gravy",
7
+ "curd",
8
+ "curds",
9
+ "cheddar",
10
+ "squeaky",
11
+ "brown sauce",
12
+ "velvety",
13
+ "peppery",
14
+ "stock",
15
+ "beef stock",
16
+ "chicken stock",
17
+ "casse-croute",
18
+ "cantine",
19
+ "chip wagon",
20
+ "poutinerie",
21
+ "quebec",
22
+ "quebecois",
23
+ "montreal",
24
+ "drummondville",
25
+ "warwick",
26
+ "victoriaville",
27
+ "classic",
28
+ "authentic",
29
+ "fresh cut",
30
+ "crispy",
31
+ "golden",
32
+ "double fried",
33
+ "steamy",
34
+ "melty",
35
+ "savory",
36
+ "comfort",
37
+ "street food",
38
+ "festival",
39
+ "hearty",
40
+ "hot",
41
+ "messy",
42
+ "forkful",
43
+ "bite",
44
+ "late hour",
45
+ ],
46
+ extended: [
47
+ "galvaude",
48
+ "italian",
49
+ "smoked meat",
50
+ "sausage",
51
+ "spicy sausage",
52
+ "onion",
53
+ "lardon",
54
+ "bacon",
55
+ "mushroom",
56
+ "green peas",
57
+ "peppers",
58
+ "tomatoes",
59
+ "pickles",
60
+ "onion rings",
61
+ "ground steak",
62
+ "pepperoni",
63
+ "merguez",
64
+ "grilled chicken",
65
+ "hot dog",
66
+ "pogo",
67
+ "shredded pork",
68
+ "brisket",
69
+ "duck",
70
+ "foie gras",
71
+ "truffle",
72
+ "demi-glace",
73
+ "au jus",
74
+ "guacamole",
75
+ "sour cream",
76
+ "Swiss cheese",
77
+ "loaded",
78
+ "garnished",
79
+ ],
80
+ },
81
+ fr: {
82
+ classic: [
83
+ "poutine",
84
+ "frites",
85
+ "sauce",
86
+ "saucebrune",
87
+ "fromage",
88
+ "fromage en grain",
89
+ "squick",
90
+ "croquante",
91
+ "croustillante",
92
+ "dorée",
93
+ "doublecuisson",
94
+ "poivrée",
95
+ "veloutée",
96
+ "bouillon",
97
+ "boeuf",
98
+ "poulet",
99
+ "cassecroûte",
100
+ "cantine",
101
+ "poutinerie",
102
+ "montréal",
103
+ "québec",
104
+ "drummondville",
105
+ "warwick",
106
+ "victoriaville",
107
+ "authentique",
108
+ "classique",
109
+ "réconfort",
110
+ "vapeur",
111
+ "fumante",
112
+ "fondante",
113
+ "salée",
114
+ "savoureuse",
115
+ "riche",
116
+ "copieuse",
117
+ "gourmande",
118
+ "fringale",
119
+ "après bar",
120
+ "minuit",
121
+ "bouchée",
122
+ "fourchette",
123
+ "tablée",
124
+ "québécoise",
125
+ "terroir",
126
+ "rustique",
127
+ "artisanale",
128
+ "fromage patate sauce",
129
+ "savoureuse",
130
+ "tradition",
131
+ "patate",
132
+ "pomme de terre",
133
+ "fumée",
134
+ "poivre",
135
+ "maison",
136
+ ],
137
+ extended: [
138
+ "galvaude",
139
+ "italienne",
140
+ "all dressed",
141
+ "viande fumée",
142
+ "saucisse",
143
+ "saucisse épicée",
144
+ "lardon",
145
+ "champignon",
146
+ "petits pois",
147
+ "poivrons",
148
+ "oignons",
149
+ "tomates",
150
+ "cornichons",
151
+ "rondelles d'oignon",
152
+ "steak haché",
153
+ "pepperoni",
154
+ "merguez",
155
+ "poulet grillé",
156
+ "hot dog",
157
+ "pogo",
158
+ "porc effiloché",
159
+ "canard confit",
160
+ "foie gras",
161
+ "demi-glace",
162
+ "trois poivres",
163
+ "guacamole",
164
+ "crème sure",
165
+ "fromage suisse",
166
+ "garniture",
167
+ "extra fromage",
168
+ "mixte",
169
+ ],
170
+ },
171
+ };
package/src/index.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ export interface Range {
2
+ min?: number;
3
+ max?: number;
4
+ }
5
+
6
+ export type Vocabulary = "classic" | "extended" | "all";
7
+
8
+ export interface PoutineIpsumOptions {
9
+ language?: "en" | "fr";
10
+ vocabulary?: Vocabulary;
11
+ sentencesPerParagraph?: Range;
12
+ wordsPerSentence?: Range;
13
+ random?: () => number;
14
+ suffix?: string;
15
+ words?: string[];
16
+ }
17
+
18
+ export class PoutineIpsum {
19
+ constructor(options?: PoutineIpsumOptions);
20
+ generateWords(count?: number): string;
21
+ generateSentences(count?: number): string;
22
+ generateParagraphs(count?: number): string;
23
+ }
24
+
25
+ export type Units =
26
+ | "word"
27
+ | "words"
28
+ | "sentence"
29
+ | "sentences"
30
+ | "paragraph"
31
+ | "paragraphs";
32
+
33
+ export interface PoutineIpsumFunctionOptions {
34
+ count?: number;
35
+ units?: Units;
36
+ format?: "plain" | "html";
37
+ language?: "en" | "fr";
38
+ vocabulary?: Vocabulary;
39
+ paragraphLowerBound?: number;
40
+ paragraphUpperBound?: number;
41
+ sentenceLowerBound?: number;
42
+ sentenceUpperBound?: number;
43
+ suffix?: string;
44
+ random?: () => number;
45
+ words?: string[];
46
+ }
47
+
48
+ export function poutineIpsum(options?: PoutineIpsumFunctionOptions): string;
49
+
50
+ export default poutineIpsum;
package/src/index.js ADDED
@@ -0,0 +1,278 @@
1
+ import os from "node:os";
2
+ import { DICTIONARIES } from "./dictionaries.js";
3
+
4
+ const DEFAULT_LANGUAGE = "fr";
5
+ const STARTER_TEXT = "Poutine ipsum dolor sit amet";
6
+ const STARTER_WORDS = STARTER_TEXT.split(" ");
7
+
8
+ const DEFAULT_CLASS_OPTIONS = {
9
+ language: DEFAULT_LANGUAGE,
10
+ sentencesPerParagraph: {
11
+ min: 3,
12
+ max: 7,
13
+ },
14
+ wordsPerSentence: {
15
+ min: 5,
16
+ max: 15,
17
+ },
18
+ random: Math.random,
19
+ suffix: os.EOL,
20
+ };
21
+
22
+ const DEFAULT_FUNCTION_OPTIONS = {
23
+ count: 1,
24
+ format: "plain",
25
+ paragraphLowerBound: 3,
26
+ paragraphUpperBound: 7,
27
+ sentenceLowerBound: 5,
28
+ sentenceUpperBound: 15,
29
+ suffix: os.EOL,
30
+ units: "sentences",
31
+ random: Math.random,
32
+ language: DEFAULT_LANGUAGE,
33
+ words: undefined,
34
+ };
35
+
36
+ function normalizeLanguage(language) {
37
+ return language === "fr" ? "fr" : "en";
38
+ }
39
+
40
+ function normalizeVocabulary(vocabulary) {
41
+ if (vocabulary === "classic" || vocabulary === "extended") {
42
+ return vocabulary;
43
+ }
44
+ return "all";
45
+ }
46
+
47
+ function getWords(language, customWords, vocabulary) {
48
+ if (Array.isArray(customWords) && customWords.length > 0) {
49
+ return customWords;
50
+ }
51
+
52
+ const dict = DICTIONARIES[normalizeLanguage(language)];
53
+ const v = normalizeVocabulary(vocabulary);
54
+
55
+ if (v === "classic") {
56
+ return dict.classic;
57
+ }
58
+ if (v === "extended") {
59
+ return dict.extended;
60
+ }
61
+ return [...dict.classic, ...dict.extended];
62
+ }
63
+
64
+ function randomInt(min, max, random) {
65
+ return Math.floor(random() * (max - min + 1)) + min;
66
+ }
67
+
68
+ function randomWord(words, random, previousWord) {
69
+ if (words.length === 1) {
70
+ return words[0];
71
+ }
72
+
73
+ const firstIndex = randomInt(0, words.length - 1, random);
74
+ let candidate = words[firstIndex];
75
+
76
+ if (candidate === previousWord) {
77
+ const nextIndex = (firstIndex + 1) % words.length;
78
+ candidate = words[nextIndex];
79
+ }
80
+
81
+ return candidate;
82
+ }
83
+
84
+ function toSentence(words, random) {
85
+ if (words.length === 0) {
86
+ return "";
87
+ }
88
+
89
+ const punct = [".", ".", ".", "!", "?"];
90
+ const ending = punct[randomInt(0, punct.length - 1, random)];
91
+
92
+ const firstWord = words[0];
93
+ words[0] = firstWord.charAt(0).toUpperCase() + firstWord.slice(1);
94
+
95
+ return `${words.join(" ")}${ending}`;
96
+ }
97
+
98
+ function generateWordArray(count, words, random) {
99
+ const result = [];
100
+
101
+ while (result.length < count) {
102
+ const previousWord = result[result.length - 1];
103
+ result.push(randomWord(words, random, previousWord));
104
+ }
105
+
106
+ return result;
107
+ }
108
+
109
+ function normalizeUnits(units) {
110
+ const value = String(units).toLowerCase();
111
+
112
+ if (value === "word" || value === "words") {
113
+ return "words";
114
+ }
115
+
116
+ if (value === "paragraph" || value === "paragraphs") {
117
+ return "paragraphs";
118
+ }
119
+
120
+ return "sentences";
121
+ }
122
+
123
+ function buildLeadingWords(count, words, random) {
124
+ const safeCount = Math.max(STARTER_WORDS.length, count);
125
+ const tailCount = Math.max(0, safeCount - STARTER_WORDS.length);
126
+ const tailWords = generateWordArray(tailCount, words, random);
127
+ return [...STARTER_WORDS, ...tailWords];
128
+ }
129
+
130
+ function createGenerator(config) {
131
+ return {
132
+ generateWords(count = 1) {
133
+ const leadingWords = buildLeadingWords(
134
+ count,
135
+ config.words,
136
+ config.random,
137
+ );
138
+ return leadingWords.join(" ");
139
+ },
140
+
141
+ generateSentence(includeStarter = true) {
142
+ const count = randomInt(
143
+ config.wordsPerSentence.min,
144
+ config.wordsPerSentence.max,
145
+ config.random,
146
+ );
147
+
148
+ if (includeStarter) {
149
+ return toSentence(
150
+ buildLeadingWords(count, config.words, config.random),
151
+ config.random,
152
+ );
153
+ }
154
+
155
+ return toSentence(
156
+ generateWordArray(count, config.words, config.random),
157
+ config.random,
158
+ );
159
+ },
160
+
161
+ generateSentences(count = 1, includeStarter = true) {
162
+ const safeCount = Math.max(1, count);
163
+ const output = [];
164
+
165
+ for (let i = 0; i < safeCount; i += 1) {
166
+ output.push(this.generateSentence(includeStarter && i === 0));
167
+ }
168
+
169
+ return output.join(" ");
170
+ },
171
+
172
+ generateParagraph(includeStarter = true) {
173
+ const count = randomInt(
174
+ config.sentencesPerParagraph.min,
175
+ config.sentencesPerParagraph.max,
176
+ config.random,
177
+ );
178
+
179
+ return this.generateSentences(count, includeStarter);
180
+ },
181
+
182
+ generateParagraphs(count = 1) {
183
+ const safeCount = Math.max(1, count);
184
+ const output = [];
185
+
186
+ for (let i = 0; i < safeCount; i += 1) {
187
+ output.push(this.generateParagraph(i === 0));
188
+ }
189
+
190
+ return output.join(config.suffix);
191
+ },
192
+ };
193
+ }
194
+
195
+ export class PoutineIpsum {
196
+ constructor(options = {}) {
197
+ const mergedOptions = {
198
+ ...DEFAULT_CLASS_OPTIONS,
199
+ ...options,
200
+ sentencesPerParagraph: {
201
+ ...DEFAULT_CLASS_OPTIONS.sentencesPerParagraph,
202
+ ...(options.sentencesPerParagraph || {}),
203
+ },
204
+ wordsPerSentence: {
205
+ ...DEFAULT_CLASS_OPTIONS.wordsPerSentence,
206
+ ...(options.wordsPerSentence || {}),
207
+ },
208
+ };
209
+
210
+ this.options = mergedOptions;
211
+ this.options.language = normalizeLanguage(mergedOptions.language);
212
+ this.words = getWords(this.options.language, mergedOptions.words, mergedOptions.vocabulary);
213
+ this.generator = createGenerator({
214
+ words: this.words,
215
+ random: this.options.random,
216
+ wordsPerSentence: this.options.wordsPerSentence,
217
+ sentencesPerParagraph: this.options.sentencesPerParagraph,
218
+ suffix: this.options.suffix,
219
+ });
220
+ }
221
+
222
+ generateWords(count = 1) {
223
+ return this.generator.generateWords(count);
224
+ }
225
+
226
+ generateSentences(count = 1) {
227
+ return this.generator.generateSentences(count);
228
+ }
229
+
230
+ generateParagraphs(count = 1) {
231
+ return this.generator.generateParagraphs(count);
232
+ }
233
+ }
234
+
235
+ export function poutineIpsum(options = {}) {
236
+ const config = {
237
+ ...DEFAULT_FUNCTION_OPTIONS,
238
+ ...options,
239
+ };
240
+
241
+ const words = getWords(config.language, config.words, config.vocabulary);
242
+ const generator = createGenerator({
243
+ words,
244
+ random: config.random,
245
+ wordsPerSentence: {
246
+ min: config.sentenceLowerBound,
247
+ max: config.sentenceUpperBound,
248
+ },
249
+ sentencesPerParagraph: {
250
+ min: config.paragraphLowerBound,
251
+ max: config.paragraphUpperBound,
252
+ },
253
+ suffix: config.suffix,
254
+ });
255
+
256
+ const units = normalizeUnits(config.units);
257
+
258
+ if (units === "words") {
259
+ return generator.generateWords(config.count);
260
+ }
261
+
262
+ if (units === "sentences") {
263
+ return generator.generateSentences(config.count);
264
+ }
265
+
266
+ const paragraphs = generator.generateParagraphs(config.count);
267
+
268
+ if (config.format === "html") {
269
+ return paragraphs
270
+ .split(config.suffix)
271
+ .map((line) => `<p>${line}</p>`)
272
+ .join(config.suffix);
273
+ }
274
+
275
+ return paragraphs;
276
+ }
277
+
278
+ export default poutineIpsum;