@helloao/tools 0.0.4 → 0.0.6

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/README.md CHANGED
@@ -16,6 +16,7 @@ $ npm install @helloao/tools
16
16
  ### Usage
17
17
 
18
18
  #### Parse a USX File
19
+
19
20
  ```typescript
20
21
  import { parser } from '@helloao/tools';
21
22
  // Used to parse XML
@@ -36,7 +37,6 @@ const domParser = new DOMParser();
36
37
  // Each input file needs some metadata about the translation that it is associated with
37
38
  const translation: generation.ParseTreeMetadata = {
38
39
  translation: {
39
-
40
40
  // The ID of the translation
41
41
  // this should be unique for the translation
42
42
  id: 'my translation id',
@@ -60,7 +60,7 @@ const translation: generation.ParseTreeMetadata = {
60
60
  // The direction that the text is written in.
61
61
  // "ltr" means "left to right" and "rtl" means "right to left"
62
62
  direction: 'ltr',
63
- }
63
+ },
64
64
  };
65
65
 
66
66
  // The list of files that should be processed.
@@ -74,14 +74,14 @@ const files: generation.InputFile[] = [
74
74
 
75
75
  // The type of the file.
76
76
  // One of "usx", "usfm", and "json"
77
- fileType: 'usx'
78
- }
77
+ fileType: 'usx',
78
+ },
79
79
  ];
80
80
 
81
81
  // Generate a dataset from the files
82
82
  // Datasets organize all the files and their content
83
83
  // by translation, book, chapter, and verse
84
- const dataset = generation.dataset.generateDataset(files, parser);
84
+ const dataset = generation.dataset.generateDataset(files, domParser);
85
85
 
86
86
  // Generate an API representation from the files
87
87
  // This adds links between chapters and additional metadata.
@@ -95,4 +95,4 @@ const outputFiles = generation.api.generateFilesForApi(api);
95
95
  for (let file of outputFiles) {
96
96
  console.log(file.path, file.content);
97
97
  }
98
- ```
98
+ ```
@@ -1,5 +1,5 @@
1
- import { OutputFile, Translation, TranslationBook, TranslationBookChapter, TranslationBookChapterAudioLinks } from "./common-types";
2
- import { DatasetOutput } from "./dataset";
1
+ import { OutputFile, Translation, TranslationBook, TranslationBookChapter, TranslationBookChapterAudioLinks } from './common-types';
2
+ import { DatasetOutput } from './dataset';
3
3
  /**
4
4
  * Defines the output of the API generation.
5
5
  */
@@ -27,6 +27,10 @@ export interface ApiOutput {
27
27
  * - /api/:translationId/:bookId/:chapterNumber.:reader.mp3
28
28
  */
29
29
  translationBookChapterAudio: ApiTranslationBookChapterAudio[];
30
+ /**
31
+ * The path prefix that the API should use.
32
+ */
33
+ pathPrefix: string;
30
34
  }
31
35
  /**
32
36
  * The list of available translations.
@@ -168,6 +172,9 @@ export interface ApiTranslationBookChapterAudio {
168
172
  */
169
173
  originalUrl: string;
170
174
  }
175
+ /**
176
+ * The options for generating the API.
177
+ */
171
178
  export interface GenerateApiOptions {
172
179
  /**
173
180
  * Whether to use the common name for the book chapter API link. If false, then book IDs are used.
@@ -182,6 +189,22 @@ export interface GenerateApiOptions {
182
189
  * Defaults to false.
183
190
  */
184
191
  generateAudioFiles?: boolean;
192
+ /**
193
+ * Gets the english name of the given language.
194
+ * If not provided, then the english name for the language will be unknown and omitted.
195
+ * @param language The language to get the english name for.
196
+ */
197
+ getEnglishName?: (language: string) => string | null | undefined;
198
+ /**
199
+ * Gets the native name of the given language.
200
+ * If not provided, then the native name for the language will be unknown and omitted.
201
+ * @param language The language to get the native name for.
202
+ */
203
+ getNativeName?: (language: string) => string | null | undefined;
204
+ /**
205
+ * The prefix that should be added to paths that are generated.
206
+ */
207
+ pathPrefix?: string;
185
208
  }
186
209
  /**
187
210
  * Generates the API output for the given dataset.
@@ -194,9 +217,27 @@ export declare function generateApiForDataset(dataset: DatasetOutput, options?:
194
217
  * @param api The API that the files should be generated for.
195
218
  */
196
219
  export declare function generateFilesForApi(api: ApiOutput): OutputFile[];
197
- export declare function listOfBooksApiLink(translationId: string): string;
198
- export declare function bookChapterApiLink(translationId: string, commonName: string, chapterNumber: number, extension: string): string;
199
- export declare function bookChapterAudioApiLink(translationId: string, bookId: string, chapterNumber: number, reader: string): string;
220
+ /**
221
+ * Generates the output files for the given datasets.
222
+ * @param datasets The datasets to generate the output files for.
223
+ * @param options The options for generating the API files.
224
+ */
225
+ export declare function generateOutputFilesFromDatasets(datasets: AsyncIterable<DatasetOutput>, options?: GenerateApiOptions): AsyncGenerator<OutputFile[]>;
226
+ /**
227
+ * Gets the API Link for the list of books endpoint for a translation.
228
+ * @param translationId The ID of the translation.
229
+ * @returns
230
+ */
231
+ export declare function listOfBooksApiLink(translationId: string, prefix?: string): string;
232
+ /**
233
+ * Getes the API link for a book chapter.
234
+ * @param translationId The ID of the translation.
235
+ * @param commonName The name of the book.
236
+ * @param chapterNumber The number of the book.
237
+ * @param extension The extension of the file.
238
+ */
239
+ export declare function bookChapterApiLink(translationId: string, commonName: string, chapterNumber: number, extension: string, prefix?: string): string;
240
+ export declare function bookChapterAudioApiLink(translationId: string, bookId: string, chapterNumber: number, reader: string, prefix?: string): string;
200
241
  export declare function jsonFile(path: string, content: any, mergable?: boolean): OutputFile;
201
242
  export declare function downloadedFile(path: string, url: string): OutputFile;
202
243
  export declare function replaceSpacesWithUnderscores(str: string): string;
package/generation/api.js CHANGED
@@ -2,20 +2,21 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateApiForDataset = generateApiForDataset;
4
4
  exports.generateFilesForApi = generateFilesForApi;
5
+ exports.generateOutputFilesFromDatasets = generateOutputFilesFromDatasets;
5
6
  exports.listOfBooksApiLink = listOfBooksApiLink;
6
7
  exports.bookChapterApiLink = bookChapterApiLink;
7
8
  exports.bookChapterAudioApiLink = bookChapterAudioApiLink;
8
9
  exports.jsonFile = jsonFile;
9
10
  exports.downloadedFile = downloadedFile;
10
11
  exports.replaceSpacesWithUnderscores = replaceSpacesWithUnderscores;
11
- const all_iso_language_codes_1 = require("all-iso-language-codes");
12
12
  /**
13
13
  * Generates the API output for the given dataset.
14
14
  * @param dataset The dataset to generate the API for.
15
15
  * @param options The options for generating the API.
16
16
  */
17
17
  function generateApiForDataset(dataset, options = {}) {
18
- const { useCommonName } = options;
18
+ const { useCommonName, pathPrefix } = options;
19
+ const apiPathPrefix = pathPrefix ? pathPrefix : '';
19
20
  let api = {
20
21
  availableTranslations: {
21
22
  translations: [],
@@ -23,17 +24,24 @@ function generateApiForDataset(dataset, options = {}) {
23
24
  translationBooks: [],
24
25
  translationBookChapters: [],
25
26
  translationBookChapterAudio: [],
27
+ pathPrefix: apiPathPrefix,
26
28
  };
29
+ const getNativeName = options.getNativeName;
30
+ const getEnglishName = options.getEnglishName;
27
31
  for (let { books, ...translation } of dataset.translations) {
28
32
  const apiTranslation = {
29
33
  ...translation,
30
34
  availableFormats: ['json'],
31
- listOfBooksApiLink: listOfBooksApiLink(translation.id),
35
+ listOfBooksApiLink: listOfBooksApiLink(translation.id, apiPathPrefix),
32
36
  numberOfBooks: books.length,
33
37
  totalNumberOfChapters: 0,
34
38
  totalNumberOfVerses: 0,
35
- languageName: (0, all_iso_language_codes_1.getNativeName)(translation.language) ?? undefined,
36
- languageEnglishName: (0, all_iso_language_codes_1.getEnglishName)(translation.language) ?? undefined,
39
+ languageName: getNativeName
40
+ ? getNativeName(translation.language) ?? undefined
41
+ : undefined,
42
+ languageEnglishName: getEnglishName
43
+ ? getEnglishName(translation.language) ?? undefined
44
+ : undefined,
37
45
  };
38
46
  const translationBooks = {
39
47
  translation: apiTranslation,
@@ -43,8 +51,8 @@ function generateApiForDataset(dataset, options = {}) {
43
51
  for (let { chapters, ...book } of books) {
44
52
  const apiBook = {
45
53
  ...book,
46
- firstChapterApiLink: bookChapterApiLink(translation.id, getBookLink(book), 1, 'json'),
47
- lastChapterApiLink: bookChapterApiLink(translation.id, getBookLink(book), chapters.length, 'json'),
54
+ firstChapterApiLink: bookChapterApiLink(translation.id, getBookLink(book), 1, 'json', apiPathPrefix),
55
+ lastChapterApiLink: bookChapterApiLink(translation.id, getBookLink(book), chapters.length, 'json', apiPathPrefix),
48
56
  numberOfChapters: chapters.length,
49
57
  totalNumberOfVerses: 0,
50
58
  };
@@ -54,7 +62,7 @@ function generateApiForDataset(dataset, options = {}) {
54
62
  translation: apiTranslation,
55
63
  book: apiBook,
56
64
  chapter: chapter,
57
- thisChapterLink: bookChapterApiLink(translation.id, getBookLink(book), chapter.number, 'json'),
65
+ thisChapterLink: bookChapterApiLink(translation.id, getBookLink(book), chapter.number, 'json', apiPathPrefix),
58
66
  thisChapterAudioLinks: audio,
59
67
  nextChapterApiLink: null,
60
68
  nextChapterAudioLinks: null,
@@ -66,7 +74,7 @@ function generateApiForDataset(dataset, options = {}) {
66
74
  if (options.generateAudioFiles) {
67
75
  const apiAudio = {
68
76
  chapter: apiBookChapter,
69
- link: bookChapterAudioApiLink(translation.id, getBookLink(book), chapter.number, reader),
77
+ link: bookChapterAudioApiLink(translation.id, getBookLink(book), chapter.number, reader, apiPathPrefix),
70
78
  originalUrl: thisChapterAudioLinks[reader],
71
79
  };
72
80
  audio[reader] = apiAudio.link;
@@ -91,12 +99,15 @@ function generateApiForDataset(dataset, options = {}) {
91
99
  }
92
100
  for (let i = 0; i < translationChapters.length; i++) {
93
101
  if (i > 0) {
94
- translationChapters[i].previousChapterApiLink = bookChapterApiLink(translation.id, getBookLink(translationChapters[i - 1].book), translationChapters[i - 1].chapter.number, 'json');
95
- translationChapters[i].previousChapterAudioLinks = translationChapters[i - 1].thisChapterAudioLinks;
102
+ translationChapters[i].previousChapterApiLink =
103
+ bookChapterApiLink(translation.id, getBookLink(translationChapters[i - 1].book), translationChapters[i - 1].chapter.number, 'json', apiPathPrefix);
104
+ translationChapters[i].previousChapterAudioLinks =
105
+ translationChapters[i - 1].thisChapterAudioLinks;
96
106
  }
97
107
  if (i < translationChapters.length - 1) {
98
- translationChapters[i].nextChapterApiLink = bookChapterApiLink(translation.id, getBookLink(translationChapters[i + 1].book), translationChapters[i + 1].chapter.number, 'json');
99
- translationChapters[i].nextChapterAudioLinks = translationChapters[i + 1].thisChapterAudioLinks;
108
+ translationChapters[i].nextChapterApiLink = bookChapterApiLink(translation.id, getBookLink(translationChapters[i + 1].book), translationChapters[i + 1].chapter.number, 'json', apiPathPrefix);
109
+ translationChapters[i].nextChapterAudioLinks =
110
+ translationChapters[i + 1].thisChapterAudioLinks;
100
111
  }
101
112
  }
102
113
  api.availableTranslations.translations.push(apiTranslation);
@@ -113,7 +124,7 @@ function generateApiForDataset(dataset, options = {}) {
113
124
  */
114
125
  function generateFilesForApi(api) {
115
126
  let files = [];
116
- files.push(jsonFile('/api/available_translations.json', api.availableTranslations, true));
127
+ files.push(jsonFile(`${api.pathPrefix}/api/available_translations.json`, api.availableTranslations, true));
117
128
  for (let translationBooks of api.translationBooks) {
118
129
  files.push(jsonFile(translationBooks.translation.listOfBooksApiLink, translationBooks));
119
130
  }
@@ -125,27 +136,50 @@ function generateFilesForApi(api) {
125
136
  }
126
137
  return files;
127
138
  }
128
- ;
129
- function listOfBooksApiLink(translationId) {
130
- return `/api/${translationId}/books.json`;
139
+ /**
140
+ * Generates the output files for the given datasets.
141
+ * @param datasets The datasets to generate the output files for.
142
+ * @param options The options for generating the API files.
143
+ */
144
+ async function* generateOutputFilesFromDatasets(datasets, options) {
145
+ for await (let dataset of datasets) {
146
+ const api = generateApiForDataset(dataset, options);
147
+ const files = generateFilesForApi(api);
148
+ yield files;
149
+ }
150
+ }
151
+ /**
152
+ * Gets the API Link for the list of books endpoint for a translation.
153
+ * @param translationId The ID of the translation.
154
+ * @returns
155
+ */
156
+ function listOfBooksApiLink(translationId, prefix = '') {
157
+ return `${prefix}/api/${translationId}/books.json`;
131
158
  }
132
- function bookChapterApiLink(translationId, commonName, chapterNumber, extension) {
133
- return `/api/${translationId}/${replaceSpacesWithUnderscores(commonName)}/${chapterNumber}.${extension}`;
159
+ /**
160
+ * Getes the API link for a book chapter.
161
+ * @param translationId The ID of the translation.
162
+ * @param commonName The name of the book.
163
+ * @param chapterNumber The number of the book.
164
+ * @param extension The extension of the file.
165
+ */
166
+ function bookChapterApiLink(translationId, commonName, chapterNumber, extension, prefix = '') {
167
+ return `${prefix}/api/${translationId}/${replaceSpacesWithUnderscores(commonName)}/${chapterNumber}.${extension}`;
134
168
  }
135
- function bookChapterAudioApiLink(translationId, bookId, chapterNumber, reader) {
136
- return `/api/${translationId}/${replaceSpacesWithUnderscores(bookId)}/${chapterNumber}.${reader}.mp3`;
169
+ function bookChapterAudioApiLink(translationId, bookId, chapterNumber, reader, prefix = '') {
170
+ return `${prefix}/api/${translationId}/${replaceSpacesWithUnderscores(bookId)}/${chapterNumber}.${reader}.mp3`;
137
171
  }
138
172
  function jsonFile(path, content, mergable) {
139
173
  return {
140
174
  path,
141
175
  content,
142
- mergable
176
+ mergable,
143
177
  };
144
178
  }
145
179
  function downloadedFile(path, url) {
146
180
  return {
147
181
  path,
148
- content: () => fetch(url).then(response => response.body),
182
+ content: () => fetch(url).then((response) => response.body),
149
183
  };
150
184
  }
151
185
  function replaceSpacesWithUnderscores(str) {
@@ -28,23 +28,23 @@ describe('generateApiForDataset()', () => {
28
28
  language: 'eng',
29
29
  direction: 'ltr',
30
30
  licenseUrl: 'https://berean.bible/terms.htm',
31
- website: 'https://berean.bible'
31
+ website: 'https://berean.bible',
32
32
  };
33
33
  let inputFiles = [
34
34
  {
35
35
  fileType: 'usfm',
36
36
  metadata: {
37
- translation: translation1
37
+ translation: translation1,
38
38
  },
39
- content: firstXLines(_01GENBSB_usfm_1.default, 13)
39
+ content: firstXLines(_01GENBSB_usfm_1.default, 13),
40
40
  },
41
41
  {
42
42
  fileType: 'usfm',
43
43
  metadata: {
44
- translation: translation1
44
+ translation: translation1,
45
45
  },
46
- content: firstXLines(_02EXOBSB_usfm_1.default, 14)
47
- }
46
+ content: firstXLines(_02EXOBSB_usfm_1.default, 14),
47
+ },
48
48
  ];
49
49
  const dataset = (0, dataset_1.generateDataset)(inputFiles, new linkedom_1.DOMParser());
50
50
  const generated = (0, api_1.generateApiForDataset)(dataset);
@@ -59,9 +59,7 @@ describe('generateApiForDataset()', () => {
59
59
  textDirection: 'ltr',
60
60
  licenseUrl: 'https://berean.bible/terms.htm',
61
61
  website: 'https://berean.bible',
62
- availableFormats: [
63
- 'json'
64
- ],
62
+ availableFormats: ['json'],
65
63
  listOfBooksApiLink: '/api/bsb/books.json',
66
64
  numberOfBooks: 2,
67
65
  totalNumberOfChapters: 2,
@@ -71,9 +69,7 @@ describe('generateApiForDataset()', () => {
71
69
  };
72
70
  expect(tree).toEqual({
73
71
  '/api/available_translations.json': {
74
- translations: [
75
- expectedTranslation
76
- ]
72
+ translations: [expectedTranslation],
77
73
  },
78
74
  '/api/bsb/books.json': {
79
75
  translation: expectedTranslation,
@@ -99,8 +95,8 @@ describe('generateApiForDataset()', () => {
99
95
  totalNumberOfVerses: 2,
100
96
  firstChapterApiLink: '/api/bsb/EXO/1.json',
101
97
  lastChapterApiLink: '/api/bsb/EXO/1.json',
102
- }
103
- ]
98
+ },
99
+ ],
104
100
  },
105
101
  '/api/bsb/GEN/1.json': {
106
102
  translation: expectedTranslation,
@@ -113,7 +109,7 @@ describe('generateApiForDataset()', () => {
113
109
  numberOfChapters: 1,
114
110
  totalNumberOfVerses: 2,
115
111
  firstChapterApiLink: '/api/bsb/GEN/1.json',
116
- lastChapterApiLink: '/api/bsb/GEN/1.json'
112
+ lastChapterApiLink: '/api/bsb/GEN/1.json',
117
113
  },
118
114
  thisChapterLink: '/api/bsb/GEN/1.json',
119
115
  thisChapterAudioLinks: {},
@@ -127,33 +123,31 @@ describe('generateApiForDataset()', () => {
127
123
  content: [
128
124
  {
129
125
  type: 'heading',
130
- content: [
131
- 'The Creation'
132
- ]
126
+ content: ['The Creation'],
133
127
  },
134
128
  {
135
- type: 'line_break'
129
+ type: 'line_break',
136
130
  },
137
131
  {
138
132
  type: 'verse',
139
133
  number: 1,
140
134
  content: [
141
- 'In the beginning God created the heavens and the earth.'
135
+ 'In the beginning God created the heavens and the earth.',
142
136
  ],
143
137
  },
144
138
  {
145
- type: 'line_break'
139
+ type: 'line_break',
146
140
  },
147
141
  {
148
142
  type: 'verse',
149
143
  number: 2,
150
144
  content: [
151
- 'Now the earth was formless and void, and darkness was over the surface of the deep. And the Spirit of God was hovering over the surface of the waters.'
145
+ 'Now the earth was formless and void, and darkness was over the surface of the deep. And the Spirit of God was hovering over the surface of the waters.',
152
146
  ],
153
147
  },
154
148
  ],
155
- footnotes: []
156
- }
149
+ footnotes: [],
150
+ },
157
151
  },
158
152
  '/api/bsb/EXO/1.json': {
159
153
  translation: expectedTranslation,
@@ -166,7 +160,7 @@ describe('generateApiForDataset()', () => {
166
160
  numberOfChapters: 1,
167
161
  totalNumberOfVerses: 2,
168
162
  firstChapterApiLink: '/api/bsb/EXO/1.json',
169
- lastChapterApiLink: '/api/bsb/EXO/1.json'
163
+ lastChapterApiLink: '/api/bsb/EXO/1.json',
170
164
  },
171
165
  thisChapterLink: '/api/bsb/EXO/1.json',
172
166
  thisChapterAudioLinks: {},
@@ -180,34 +174,30 @@ describe('generateApiForDataset()', () => {
180
174
  content: [
181
175
  {
182
176
  type: 'heading',
183
- content: [
184
- 'The Israelites Multiply in Egypt'
185
- ]
177
+ content: ['The Israelites Multiply in Egypt'],
186
178
  },
187
179
  {
188
- type: 'line_break'
180
+ type: 'line_break',
189
181
  },
190
182
  {
191
183
  type: 'verse',
192
184
  number: 1,
193
185
  content: [
194
- 'These are the names of the sons of Israel who went to Egypt with Jacob, each with his family:'
186
+ 'These are the names of the sons of Israel who went to Egypt with Jacob, each with his family:',
195
187
  ],
196
188
  },
197
189
  {
198
- type: 'line_break'
190
+ type: 'line_break',
199
191
  },
200
192
  {
201
193
  type: 'verse',
202
194
  number: 2,
203
- content: [
204
- 'Reuben, Simeon, Levi, and Judah;'
205
- ],
195
+ content: ['Reuben, Simeon, Levi, and Judah;'],
206
196
  },
207
197
  ],
208
- footnotes: []
209
- }
210
- }
198
+ footnotes: [],
199
+ },
200
+ },
211
201
  });
212
202
  // expect(availableTranslations).toEqual({
213
203
  // translations: [
@@ -215,6 +205,189 @@ describe('generateApiForDataset()', () => {
215
205
  // ]
216
206
  // });
217
207
  });
208
+ it('should use the given path prefix', () => {
209
+ let translation1 = {
210
+ id: 'bsb',
211
+ name: 'Berean Standard Bible',
212
+ englishName: 'Berean Standard Bible',
213
+ shortName: 'BSB',
214
+ language: 'eng',
215
+ direction: 'ltr',
216
+ licenseUrl: 'https://berean.bible/terms.htm',
217
+ website: 'https://berean.bible',
218
+ };
219
+ let inputFiles = [
220
+ {
221
+ fileType: 'usfm',
222
+ metadata: {
223
+ translation: translation1,
224
+ },
225
+ content: firstXLines(_01GENBSB_usfm_1.default, 13),
226
+ },
227
+ {
228
+ fileType: 'usfm',
229
+ metadata: {
230
+ translation: translation1,
231
+ },
232
+ content: firstXLines(_02EXOBSB_usfm_1.default, 14),
233
+ },
234
+ ];
235
+ const dataset = (0, dataset_1.generateDataset)(inputFiles, new linkedom_1.DOMParser());
236
+ const generated = (0, api_1.generateApiForDataset)(dataset, {
237
+ pathPrefix: '/hello',
238
+ });
239
+ const files = (0, api_1.generateFilesForApi)(generated);
240
+ const tree = fileTree(files);
241
+ const expectedTranslation = {
242
+ id: 'bsb',
243
+ name: 'Berean Standard Bible',
244
+ englishName: 'Berean Standard Bible',
245
+ shortName: 'BSB',
246
+ language: 'eng',
247
+ textDirection: 'ltr',
248
+ licenseUrl: 'https://berean.bible/terms.htm',
249
+ website: 'https://berean.bible',
250
+ availableFormats: ['json'],
251
+ listOfBooksApiLink: '/hello/api/bsb/books.json',
252
+ numberOfBooks: 2,
253
+ totalNumberOfChapters: 2,
254
+ totalNumberOfVerses: 4,
255
+ languageName: 'English',
256
+ languageEnglishName: 'English',
257
+ };
258
+ expect(tree).toEqual({
259
+ '/hello/api/available_translations.json': {
260
+ translations: [expectedTranslation],
261
+ },
262
+ '/hello/api/bsb/books.json': {
263
+ translation: expectedTranslation,
264
+ books: [
265
+ {
266
+ id: 'GEN',
267
+ name: 'Genesis',
268
+ commonName: 'Genesis',
269
+ title: 'Genesis',
270
+ order: 1,
271
+ numberOfChapters: 1,
272
+ totalNumberOfVerses: 2,
273
+ firstChapterApiLink: '/hello/api/bsb/GEN/1.json',
274
+ lastChapterApiLink: '/hello/api/bsb/GEN/1.json',
275
+ },
276
+ {
277
+ id: 'EXO',
278
+ name: 'Exodus',
279
+ commonName: 'Exodus',
280
+ title: 'Exodus',
281
+ order: 2,
282
+ numberOfChapters: 1,
283
+ totalNumberOfVerses: 2,
284
+ firstChapterApiLink: '/hello/api/bsb/EXO/1.json',
285
+ lastChapterApiLink: '/hello/api/bsb/EXO/1.json',
286
+ },
287
+ ],
288
+ },
289
+ '/hello/api/bsb/GEN/1.json': {
290
+ translation: expectedTranslation,
291
+ book: {
292
+ id: 'GEN',
293
+ name: 'Genesis',
294
+ commonName: 'Genesis',
295
+ title: 'Genesis',
296
+ order: 1,
297
+ numberOfChapters: 1,
298
+ totalNumberOfVerses: 2,
299
+ firstChapterApiLink: '/hello/api/bsb/GEN/1.json',
300
+ lastChapterApiLink: '/hello/api/bsb/GEN/1.json',
301
+ },
302
+ thisChapterLink: '/hello/api/bsb/GEN/1.json',
303
+ thisChapterAudioLinks: {},
304
+ nextChapterApiLink: '/hello/api/bsb/EXO/1.json',
305
+ nextChapterAudioLinks: {},
306
+ previousChapterApiLink: null,
307
+ previousChapterAudioLinks: null,
308
+ numberOfVerses: 2,
309
+ chapter: {
310
+ number: 1,
311
+ content: [
312
+ {
313
+ type: 'heading',
314
+ content: ['The Creation'],
315
+ },
316
+ {
317
+ type: 'line_break',
318
+ },
319
+ {
320
+ type: 'verse',
321
+ number: 1,
322
+ content: [
323
+ 'In the beginning God created the heavens and the earth.',
324
+ ],
325
+ },
326
+ {
327
+ type: 'line_break',
328
+ },
329
+ {
330
+ type: 'verse',
331
+ number: 2,
332
+ content: [
333
+ 'Now the earth was formless and void, and darkness was over the surface of the deep. And the Spirit of God was hovering over the surface of the waters.',
334
+ ],
335
+ },
336
+ ],
337
+ footnotes: [],
338
+ },
339
+ },
340
+ '/hello/api/bsb/EXO/1.json': {
341
+ translation: expectedTranslation,
342
+ book: {
343
+ id: 'EXO',
344
+ name: 'Exodus',
345
+ commonName: 'Exodus',
346
+ title: 'Exodus',
347
+ order: 2,
348
+ numberOfChapters: 1,
349
+ totalNumberOfVerses: 2,
350
+ firstChapterApiLink: '/hello/api/bsb/EXO/1.json',
351
+ lastChapterApiLink: '/hello/api/bsb/EXO/1.json',
352
+ },
353
+ thisChapterLink: '/hello/api/bsb/EXO/1.json',
354
+ thisChapterAudioLinks: {},
355
+ nextChapterApiLink: null,
356
+ nextChapterAudioLinks: null,
357
+ previousChapterApiLink: '/hello/api/bsb/GEN/1.json',
358
+ previousChapterAudioLinks: {},
359
+ numberOfVerses: 2,
360
+ chapter: {
361
+ number: 1,
362
+ content: [
363
+ {
364
+ type: 'heading',
365
+ content: ['The Israelites Multiply in Egypt'],
366
+ },
367
+ {
368
+ type: 'line_break',
369
+ },
370
+ {
371
+ type: 'verse',
372
+ number: 1,
373
+ content: [
374
+ 'These are the names of the sons of Israel who went to Egypt with Jacob, each with his family:',
375
+ ],
376
+ },
377
+ {
378
+ type: 'line_break',
379
+ },
380
+ {
381
+ type: 'verse',
382
+ number: 2,
383
+ content: ['Reuben, Simeon, Levi, and Judah;'],
384
+ },
385
+ ],
386
+ footnotes: [],
387
+ },
388
+ },
389
+ });
390
+ });
218
391
  it('should use underscores for spaces in the book name', () => {
219
392
  let translation1 = {
220
393
  id: 'bsb',
@@ -224,15 +397,15 @@ describe('generateApiForDataset()', () => {
224
397
  language: 'eng',
225
398
  direction: 'ltr',
226
399
  licenseUrl: 'https://berean.bible/terms.htm',
227
- website: 'https://berean.bible'
400
+ website: 'https://berean.bible',
228
401
  };
229
402
  let inputFiles = [
230
403
  {
231
404
  fileType: 'usfm',
232
405
  metadata: {
233
- translation: translation1
406
+ translation: translation1,
234
407
  },
235
- content: firstXLines(_131CHBSB_usfm_1.default, 11)
408
+ content: firstXLines(_131CHBSB_usfm_1.default, 11),
236
409
  },
237
410
  ];
238
411
  const dataset = (0, dataset_1.generateDataset)(inputFiles, new linkedom_1.DOMParser());
@@ -250,9 +423,7 @@ describe('generateApiForDataset()', () => {
250
423
  textDirection: 'ltr',
251
424
  licenseUrl: 'https://berean.bible/terms.htm',
252
425
  website: 'https://berean.bible',
253
- availableFormats: [
254
- 'json'
255
- ],
426
+ availableFormats: ['json'],
256
427
  listOfBooksApiLink: '/api/bsb/books.json',
257
428
  numberOfBooks: 1,
258
429
  totalNumberOfChapters: 1,
@@ -262,9 +433,7 @@ describe('generateApiForDataset()', () => {
262
433
  };
263
434
  expect(tree).toEqual({
264
435
  '/api/available_translations.json': {
265
- translations: [
266
- expectedTranslation
267
- ]
436
+ translations: [expectedTranslation],
268
437
  },
269
438
  '/api/bsb/books.json': {
270
439
  translation: expectedTranslation,
@@ -280,7 +449,7 @@ describe('generateApiForDataset()', () => {
280
449
  firstChapterApiLink: '/api/bsb/1_Chronicles/1.json',
281
450
  lastChapterApiLink: '/api/bsb/1_Chronicles/1.json',
282
451
  },
283
- ]
452
+ ],
284
453
  },
285
454
  '/api/bsb/1_Chronicles/1.json': {
286
455
  translation: expectedTranslation,
@@ -293,9 +462,9 @@ describe('generateApiForDataset()', () => {
293
462
  totalNumberOfVerses: 1,
294
463
  order: 13,
295
464
  firstChapterApiLink: '/api/bsb/1_Chronicles/1.json',
296
- lastChapterApiLink: '/api/bsb/1_Chronicles/1.json'
465
+ lastChapterApiLink: '/api/bsb/1_Chronicles/1.json',
297
466
  },
298
- thisChapterLink: "/api/bsb/1_Chronicles/1.json",
467
+ thisChapterLink: '/api/bsb/1_Chronicles/1.json',
299
468
  thisChapterAudioLinks: {},
300
469
  nextChapterApiLink: null,
301
470
  nextChapterAudioLinks: null,
@@ -307,23 +476,19 @@ describe('generateApiForDataset()', () => {
307
476
  content: [
308
477
  {
309
478
  type: 'heading',
310
- content: [
311
- 'From Adam to Abraham'
312
- ]
479
+ content: ['From Adam to Abraham'],
313
480
  },
314
481
  {
315
- type: 'line_break'
482
+ type: 'line_break',
316
483
  },
317
484
  {
318
485
  type: 'verse',
319
486
  number: 1,
320
- content: [
321
- 'Adam, Seth, Enosh,'
322
- ],
487
+ content: ['Adam, Seth, Enosh,'],
323
488
  },
324
489
  ],
325
- footnotes: []
326
- }
490
+ footnotes: [],
491
+ },
327
492
  },
328
493
  });
329
494
  // expect(availableTranslations).toEqual({
@@ -341,15 +506,15 @@ describe('generateApiForDataset()', () => {
341
506
  language: 'spa',
342
507
  direction: 'ltr',
343
508
  licenseUrl: 'https://berean.bible/terms.htm',
344
- website: 'https://berean.bible'
509
+ website: 'https://berean.bible',
345
510
  };
346
511
  let inputFiles = [
347
512
  {
348
513
  fileType: 'usfm',
349
514
  metadata: {
350
- translation: translation1
515
+ translation: translation1,
351
516
  },
352
- content: firstXLines(_131CHBSB_usfm_1.default, 11)
517
+ content: firstXLines(_131CHBSB_usfm_1.default, 11),
353
518
  },
354
519
  ];
355
520
  const dataset = (0, dataset_1.generateDataset)(inputFiles, new linkedom_1.DOMParser());
@@ -367,9 +532,7 @@ describe('generateApiForDataset()', () => {
367
532
  textDirection: 'ltr',
368
533
  licenseUrl: 'https://berean.bible/terms.htm',
369
534
  website: 'https://berean.bible',
370
- availableFormats: [
371
- 'json'
372
- ],
535
+ availableFormats: ['json'],
373
536
  listOfBooksApiLink: '/api/bsb/books.json',
374
537
  numberOfBooks: 1,
375
538
  totalNumberOfChapters: 1,
@@ -379,9 +542,7 @@ describe('generateApiForDataset()', () => {
379
542
  };
380
543
  expect(tree).toEqual({
381
544
  '/api/available_translations.json': {
382
- translations: [
383
- expectedTranslation
384
- ]
545
+ translations: [expectedTranslation],
385
546
  },
386
547
  '/api/bsb/books.json': {
387
548
  translation: expectedTranslation,
@@ -397,7 +558,7 @@ describe('generateApiForDataset()', () => {
397
558
  firstChapterApiLink: '/api/bsb/1_Chronicles/1.json',
398
559
  lastChapterApiLink: '/api/bsb/1_Chronicles/1.json',
399
560
  },
400
- ]
561
+ ],
401
562
  },
402
563
  '/api/bsb/1_Chronicles/1.json': {
403
564
  translation: expectedTranslation,
@@ -410,9 +571,9 @@ describe('generateApiForDataset()', () => {
410
571
  totalNumberOfVerses: 1,
411
572
  order: 13,
412
573
  firstChapterApiLink: '/api/bsb/1_Chronicles/1.json',
413
- lastChapterApiLink: '/api/bsb/1_Chronicles/1.json'
574
+ lastChapterApiLink: '/api/bsb/1_Chronicles/1.json',
414
575
  },
415
- thisChapterLink: "/api/bsb/1_Chronicles/1.json",
576
+ thisChapterLink: '/api/bsb/1_Chronicles/1.json',
416
577
  thisChapterAudioLinks: {},
417
578
  nextChapterApiLink: null,
418
579
  nextChapterAudioLinks: null,
@@ -424,23 +585,19 @@ describe('generateApiForDataset()', () => {
424
585
  content: [
425
586
  {
426
587
  type: 'heading',
427
- content: [
428
- 'From Adam to Abraham'
429
- ]
588
+ content: ['From Adam to Abraham'],
430
589
  },
431
590
  {
432
- type: 'line_break'
591
+ type: 'line_break',
433
592
  },
434
593
  {
435
594
  type: 'verse',
436
595
  number: 1,
437
- content: [
438
- 'Adam, Seth, Enosh,'
439
- ],
596
+ content: ['Adam, Seth, Enosh,'],
440
597
  },
441
598
  ],
442
- footnotes: []
443
- }
599
+ footnotes: [],
600
+ },
444
601
  },
445
602
  });
446
603
  // expect(availableTranslations).toEqual({
@@ -1,4 +1,4 @@
1
- import { InputFile, Translation, TranslationBook, TranslationBookChapter } from "./common-types";
1
+ import { InputFile, Translation, TranslationBook, TranslationBookChapter } from './common-types';
2
2
  /**
3
3
  * Defines an interface that contains generated dataset info.
4
4
  */
@@ -63,7 +63,7 @@ function generateDataset(files, parser = new globalThis.DOMParser()) {
63
63
  translation = {
64
64
  ...(0, lodash_1.omit)(file.metadata.translation, 'direction'),
65
65
  textDirection: file.metadata.translation.direction,
66
- books: []
66
+ books: [],
67
67
  };
68
68
  output.translations.push(translation);
69
69
  parsedTranslations.set(file.metadata.translation.id, translation);
@@ -88,14 +88,14 @@ function generateDataset(files, parser = new globalThis.DOMParser()) {
88
88
  chapter: {
89
89
  number: content.number,
90
90
  content: content.content,
91
- footnotes: content.footnotes
91
+ footnotes: content.footnotes,
92
92
  },
93
- thisChapterAudioLinks: (0, audio_1.getAudioUrlsForChapter)(translation.id, id, content.number)
93
+ thisChapterAudioLinks: (0, audio_1.getAudioUrlsForChapter)(translation.id, id, content.number),
94
94
  });
95
95
  }
96
96
  }
97
- book.chapters = (0, lodash_1.sortBy)(book.chapters, c => c.chapter.number);
98
- const index = (0, lodash_1.sortedIndexBy)(translation.books, book, b => b.order);
97
+ book.chapters = (0, lodash_1.sortBy)(book.chapters, (c) => c.chapter.number);
98
+ const index = (0, lodash_1.sortedIndexBy)(translation.books, book, (b) => b.order);
99
99
  translation.books.splice(index, 0, book);
100
100
  }
101
101
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helloao/tools",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "A set of tools for managing HelloAO's Free Bible API",
5
5
  "module": "index.js",
6
6
  "types": "index.d.ts",
@@ -8,7 +8,6 @@
8
8
  "license": "MIT",
9
9
  "dependencies": {
10
10
  "lodash": "4.17.21",
11
- "all-iso-language-codes": "1.0.17",
12
11
  "zod": "^3.23.8"
13
12
  },
14
13
  "devDependencies": {
@@ -1,5 +1,5 @@
1
- import { Chapter, ChapterContent, FootnoteReference, ParseTree, Verse, Text, HebrewSubtitle, InlineLineBreak } from "./types";
2
- import { RewindableIterator } from "./iterators";
1
+ import { Chapter, ChapterContent, FootnoteReference, ParseTree, Verse, Text, HebrewSubtitle, InlineLineBreak } from './types';
2
+ import { RewindableIterator } from './iterators';
3
3
  /**
4
4
  * The version of the parser.
5
5
  * Used to determine whether input files need to be re-parsed.
@@ -32,7 +32,7 @@ class USXParser {
32
32
  const usxElement = doc.documentElement;
33
33
  let root = {
34
34
  type: 'root',
35
- content: []
35
+ content: [],
36
36
  };
37
37
  const bookElement = usxElement.querySelector('book[code]');
38
38
  if (!bookElement) {
@@ -51,7 +51,10 @@ class USXParser {
51
51
  // const title2 = usxElement.querySelector('para[style="mt2"]');
52
52
  // const title3 = usxElement.querySelector('para[style="mt3"]');
53
53
  if (titles.length > 0) {
54
- root.title = [...titles].map(t => t.textContent).filter(t => t).join(' ');
54
+ root.title = [...titles]
55
+ .map((t) => t.textContent)
56
+ .filter((t) => t)
57
+ .join(' ');
55
58
  }
56
59
  for (let content of this.iterateRootContent(usxElement)) {
57
60
  root.content.push(content);
@@ -85,10 +88,13 @@ class USXParser {
85
88
  }
86
89
  else if (child.nodeName === 'para') {
87
90
  const style = child.getAttribute('style');
88
- if (style === 's1' || style === 's2' || style === 's3' || style === 's4') {
91
+ if (style === 's1' ||
92
+ style === 's2' ||
93
+ style === 's3' ||
94
+ style === 's4') {
89
95
  yield {
90
96
  type: 'heading',
91
- content: child.textContent ? [child.textContent] : []
97
+ content: child.textContent ? [child.textContent] : [],
92
98
  };
93
99
  }
94
100
  }
@@ -108,10 +114,15 @@ class USXParser {
108
114
  }
109
115
  else if (element.nodeName === 'para') {
110
116
  const style = element.getAttribute('style');
111
- if (style === 's1' || style === 's2' || style === 's3' || style === 's4') {
117
+ if (style === 's1' ||
118
+ style === 's2' ||
119
+ style === 's3' ||
120
+ style === 's4') {
112
121
  yield {
113
122
  type: 'heading',
114
- content: element.textContent ? [element.textContent] : []
123
+ content: element.textContent
124
+ ? [element.textContent]
125
+ : [],
115
126
  };
116
127
  }
117
128
  else if (style === 'b') {
@@ -145,8 +156,18 @@ class USXParser {
145
156
  let descriptive = null;
146
157
  if (parent.nodeName === 'para') {
147
158
  const style = parent.getAttribute('style');
148
- if (style === 'q1' || style === 'q2' || style === 'q3' || style === 'q4') {
149
- poem = style === 'q1' ? 1 : style === 'q2' ? 2 : style === 'q3' ? 3 : 4;
159
+ if (style === 'q1' ||
160
+ style === 'q2' ||
161
+ style === 'q3' ||
162
+ style === 'q4') {
163
+ poem =
164
+ style === 'q1'
165
+ ? 1
166
+ : style === 'q2'
167
+ ? 2
168
+ : style === 'q3'
169
+ ? 3
170
+ : 4;
150
171
  }
151
172
  else if (style === 'd') {
152
173
  descriptive = true;
@@ -156,7 +177,7 @@ class USXParser {
156
177
  if (poem !== null || descriptive !== null) {
157
178
  if (typeof content === 'string') {
158
179
  let text = {
159
- text: content
180
+ text: content,
160
181
  };
161
182
  if (poem !== null) {
162
183
  text.poem = poem;
@@ -168,7 +189,7 @@ class USXParser {
168
189
  }
169
190
  else {
170
191
  let text = {
171
- ...content
192
+ ...content,
172
193
  };
173
194
  if ('text' in text) {
174
195
  if (poem !== null) {
@@ -191,7 +212,7 @@ class USXParser {
191
212
  const verse = {
192
213
  type: 'verse',
193
214
  number: parseInt(element.getAttribute('number') || '0', 10),
194
- content: []
215
+ content: [],
195
216
  };
196
217
  for (let content of this.iterateVerseContent(chapter, verse, nodes)) {
197
218
  addOrJoin(verse.content, content);
@@ -202,7 +223,7 @@ class USXParser {
202
223
  *parseHebrewSubtitle(para, chapter, nodes) {
203
224
  const subtitle = {
204
225
  type: 'hebrew_subtitle',
205
- content: []
226
+ content: [],
206
227
  };
207
228
  for (let content of this.iterateHebrewSubtitleContent(para, chapter, nodes)) {
208
229
  if (typeof content === 'object' && 'number' in content) {
@@ -241,12 +262,14 @@ class USXParser {
241
262
  else if (node instanceof Element && node.nodeName === 'char') {
242
263
  yield* this.iterateChar(nodes, node);
243
264
  }
244
- else if (node instanceof Element && node.nodeName === 'para' && node.getAttribute('style') === 'b') {
265
+ else if (node instanceof Element &&
266
+ node.nodeName === 'para' &&
267
+ node.getAttribute('style') === 'b') {
245
268
  for (let _ of (0, iterators_1.children)(nodes, node)) {
246
269
  // iterate through all the children to prevent iterating over them multiple times
247
270
  }
248
271
  yield {
249
- lineBreak: true
272
+ lineBreak: true,
250
273
  };
251
274
  }
252
275
  else if (node.nodeType === NodeType.Text) {
@@ -259,7 +282,7 @@ class USXParser {
259
282
  if (style === 'wj') {
260
283
  yield {
261
284
  text,
262
- wordsOfJesus: true
285
+ wordsOfJesus: true,
263
286
  };
264
287
  }
265
288
  else {
@@ -286,12 +309,12 @@ class USXParser {
286
309
  text,
287
310
  reference: {
288
311
  chapter: chapter.number,
289
- verse: verse?.number ?? 0
290
- }
312
+ verse: verse?.number ?? 0,
313
+ },
291
314
  };
292
315
  chapter.footnotes.push(note);
293
316
  yield {
294
- noteId: note.noteId
317
+ noteId: note.noteId,
295
318
  };
296
319
  }
297
320
  else {
@@ -312,7 +335,7 @@ class USXParser {
312
335
  if (style === 'wj') {
313
336
  yield {
314
337
  text,
315
- wordsOfJesus: true
338
+ wordsOfJesus: true,
316
339
  };
317
340
  }
318
341
  else {
@@ -329,15 +352,31 @@ const ignoredParaStyles = new Set([
329
352
  // <para> Identification [exclude all] - Running headings & table of contents
330
353
  'ide', // See https://github.com/schierlm/BibleMultiConverter/issues/67
331
354
  'rem', // Remarks (valid in schema though missed in docs)
332
- 'h', 'h1', 'h2', 'h3', 'h4',
333
- 'toc1', 'toc2', 'toc3',
334
- 'toca1', 'toca2', 'toca3',
355
+ 'h',
356
+ 'h1',
357
+ 'h2',
358
+ 'h3',
359
+ 'h4',
360
+ 'toc1',
361
+ 'toc2',
362
+ 'toc3',
363
+ 'toca1',
364
+ 'toca2',
365
+ 'toca3',
335
366
  /* <para> Introductions [exclude all] - Introductionary (non-biblical) content
336
367
  Which might be helpful in a printed book, but intro material in apps is usually bad UX,
337
368
  and users that really care can research a translations methodology themselves
338
369
  */
339
- 'imt', 'imt1', 'imt2', 'imt3', 'imt4',
340
- 'is', 'is1', 'is2', 'is3', 'is4',
370
+ 'imt',
371
+ 'imt1',
372
+ 'imt2',
373
+ 'imt3',
374
+ 'imt4',
375
+ 'is',
376
+ 'is1',
377
+ 'is2',
378
+ 'is3',
379
+ 'is4',
341
380
  'ip',
342
381
  'ipi',
343
382
  'im',
@@ -345,19 +384,39 @@ const ignoredParaStyles = new Set([
345
384
  'ipq',
346
385
  'imq',
347
386
  'ipr',
348
- 'iq', 'iq1', 'iq2', 'iq3', 'iq4',
387
+ 'iq',
388
+ 'iq1',
389
+ 'iq2',
390
+ 'iq3',
391
+ 'iq4',
349
392
  'ib',
350
- 'ili', 'ili1', 'ili2', 'ili3', 'ili4',
393
+ 'ili',
394
+ 'ili1',
395
+ 'ili2',
396
+ 'ili3',
397
+ 'ili4',
351
398
  'iot',
352
- 'io', 'io1', 'io2', 'io3', 'io4',
399
+ 'io',
400
+ 'io1',
401
+ 'io2',
402
+ 'io3',
403
+ 'io4',
353
404
  'iex',
354
405
  'imte',
355
406
  'ie',
356
407
  /* <para> Headings [exclude some] - Exclude book & chapter headings but keep section headings
357
408
  Not excluded: ms# | mr | s# | sr | d | sp | sd#
358
409
  */
359
- 'mt', 'mt1', 'mt2', 'mt3', 'mt4',
360
- 'mte', 'mte1', 'mte2', 'mte3', 'mte4',
410
+ 'mt',
411
+ 'mt1',
412
+ 'mt2',
413
+ 'mt3',
414
+ 'mt4',
415
+ 'mte',
416
+ 'mte1',
417
+ 'mte2',
418
+ 'mte3',
419
+ 'mte4',
361
420
  'cl',
362
421
  'cd', // Non-biblical chapter summary, more than heading
363
422
  'r', // Parallels to be provided by external data
@@ -368,7 +427,7 @@ function* iterateCharContent(char) {
368
427
  if (style === 'wj') {
369
428
  yield {
370
429
  text,
371
- wordsOfJesus: true
430
+ wordsOfJesus: true,
372
431
  };
373
432
  }
374
433
  else {
@@ -409,7 +468,9 @@ function addOrJoin(array, value) {
409
468
  if (typeof last === 'string' && typeof value === 'string') {
410
469
  array[array.length - 1] = last + value;
411
470
  }
412
- else if (isVerseText(last) && isVerseText(value) && hasSameFormatting(last, value)) {
471
+ else if (isVerseText(last) &&
472
+ isVerseText(value) &&
473
+ hasSameFormatting(last, value)) {
413
474
  last.text += value.text;
414
475
  }
415
476
  else {