hdoc-tools 0.7.27 → 0.8.1

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/hdoc-build.js CHANGED
@@ -1,4 +1,6 @@
1
- const { stat } = require('fs');
1
+ const {
2
+ stat
3
+ } = require('fs');
2
4
 
3
5
  (function () {
4
6
  'use strict';
@@ -7,12 +9,22 @@ const { stat } = require('fs');
7
9
  fs = require('fs-extra'),
8
10
  mdfm = require('markdown-it-front-matter'),
9
11
  path = require('path'),
12
+ Database = require('better-sqlite3'),
10
13
  URL = require("url").URL,
11
14
  validate = require(path.join(__dirname, 'hdoc-validate.js')),
12
15
  hdoc = require(path.join(__dirname, 'hdoc-module.js')),
16
+ hdoc_index = require(path.join(__dirname, 'hdoc-db.js')),
13
17
  zipper = require('zip-local');
14
18
 
15
- const h_tags_to_search = ['h1', 'h2', 'h3'];
19
+ const h_tags_to_search = ['h1', 'h2', 'h3'],
20
+ index_cols = [
21
+ 'relative_url',
22
+ 'book_id',
23
+ 'book_audience',
24
+ 'book_tags',
25
+ 'doc_title',
26
+ 'doc_content'
27
+ ];
16
28
 
17
29
  let conversion_attempted = 0,
18
30
  conversion_success = 0,
@@ -20,55 +32,56 @@ const { stat } = require('fs');
20
32
  includes_found = 0,
21
33
  includes_success = 0,
22
34
  includes_failed = 0,
35
+ hdocbook_project,
23
36
  docId = '',
24
37
  md_files = [],
25
- static_html_files = [];
38
+ static_html_files = [],
39
+ index_records = [];
26
40
 
27
- const transform_html = function(file_path) {
28
- if (fs.existsSync(file_path)) {
41
+ const transform_static_html = function (file_path) {
42
+ if (fs.existsSync(file_path.path)) {
29
43
  // Load HTML file
30
- let html_txt = fs.readFileSync(file_path, 'utf8');
31
- html_txt = html_txt.replace(/\r/gm,''); // Remove CR's so we're just dealing with newlines
44
+ let html_txt = fs.readFileSync(file_path.path, 'utf8');
45
+ html_txt = html_txt.replace(/\r/gm, ''); // Remove CR's so we're just dealing with newlines
32
46
 
33
47
  let html_txt_updated = false;
34
-
48
+
35
49
  // Check if we have a frontmatter comment
36
50
  const fm_header = hdoc.getHTMLFrontmatterHeader(html_txt);
37
- if (fm_header.fm_properties.length > 0 ) {
51
+ if (Object.keys(fm_header.fm_properties).length > 0) {
38
52
  // We have some frontmatter headers, check if title is one of them
39
53
  let fm_title_found = false;
40
- for (let i = 0; i < fm_header.fm_properties.length; i++) {
41
- if (fm_header.fm_properties[i].key.toLowerCase() === 'title') {
42
-
43
- // We have a title - bur does the title have a value
44
- if (fm_header.fm_properties[i].value === '') {
45
- // No value - remove title from the properties array
46
- // so we don't end up with 2 title properties, one empty and one with a value
47
- fm_header.fm_properties.splice(i, 1);
48
- } else {
49
- // We have a value for the title property
50
- fm_title_found = true;
51
- }
54
+ if (fm_header.fm_properties && fm_header.fm_properties.title !== undefined) {
55
+ // We have a title - but does the title have a value
56
+ if (fm_header.fm_properties.title === '') {
57
+ // No value - remove title from the properties map
58
+ // so we don't end up with 2 title properties, one empty and one with a value
59
+ delete fm_header.fm_properties.title;
60
+ } else {
61
+ // We have a value for the title property
62
+ fm_title_found = true;
52
63
  }
53
64
  }
54
-
65
+
55
66
  if (!fm_title_found) {
56
67
  // No frontmatter title found in properties
57
68
  // Go get title from h tags in html
58
69
  const html_heading = hdoc.getFirstHTMLHeading(html_txt, h_tags_to_search);
59
-
70
+
60
71
  if (html_heading && html_heading[0] && html_heading[0].children && html_heading[0].children[0] && html_heading[0].children[0].data) {
61
72
  // We've found a heading tag, add that as a title to the existing frontmatter properties
62
73
  let frontmatter_header = `[[FRONTMATTER\ntitle: ${html_heading[0].children[0].data}`;
63
- fm_header.fm_properties.forEach(function(fm_prop){
64
- frontmatter_header += `\n${fm_prop.key}: ${fm_prop.value}`;
65
- });
74
+ for (const key in fm_header.fm_properties) {
75
+ if (fm_header.fm_properties.hasOwnProperty(key)) {
76
+ frontmatter_header += `\n${key}: ${fm_header.fm_properties[key]}`;
77
+ }
78
+ }
66
79
  frontmatter_header += '\n]]';
67
80
  html_txt = html_txt.replace(fm_header.fm_header, frontmatter_header);
68
81
  html_txt_updated = true;
69
82
  } else {
70
83
  // No header tag, no frontmatter title, output a warning
71
- console.log(`No frontmatter title property, or ${h_tags_to_search.join(', ')} tags detected in ${file_path}`);
84
+ console.log(`No frontmatter title property, or ${h_tags_to_search.join(', ')} tags detected in ${file_path.path}`);
72
85
  }
73
86
  }
74
87
  } else {
@@ -81,26 +94,27 @@ const { stat } = require('fs');
81
94
  html_txt_updated = true;
82
95
  } else {
83
96
  // No header tag, no frontmatter title, output a warning
84
- console.log(`No frontmatter title property, or ${h_tags_to_search.join(', ')} tags detected in ${file_path}`);
97
+ console.log(`No frontmatter title property, or ${h_tags_to_search.join(', ')} tags detected in ${file_path.path}`);
85
98
  }
86
99
  }
100
+ index_records.push({relative_path: file_path.relativePath, index_html: hdoc_index.transform_html_for_index(html_txt)});
87
101
  if (html_txt_updated) {
88
102
  // Save HTML into HTML file
89
- fs.writeFile(file_path, html_txt, function writeJSON(err) {
103
+ fs.writeFile(file_path.path, html_txt, function writeJSON(err) {
90
104
  if (err) return console.log('Error writing:', target_file, '\r\n', err);
91
105
  });
92
106
  }
93
107
  }
94
108
  };
95
109
 
96
- const transform_markdown_and_save_html = function (file_path) {
110
+ const transform_markdown_and_save_html = function (file_path) {
97
111
  conversion_attempted++;
98
- if (fs.existsSync(file_path)) {
112
+ if (fs.existsSync(file_path.path)) {
99
113
  // Load markdown file
100
- let md_txt = hdoc.expand_variables(fs.readFileSync(file_path, 'utf8'));
114
+ let md_txt = hdoc.expand_variables(fs.readFileSync(file_path.path, 'utf8'));
101
115
 
102
116
  // Pull in external includes
103
- const includes_processed = hdoc.process_includes(file_path, md_txt);
117
+ const includes_processed = hdoc.process_includes(file_path.path, md_txt);
104
118
  md_txt = includes_processed.body;
105
119
  includes_found += includes_processed.found;
106
120
  includes_success += includes_processed.success;
@@ -123,12 +137,12 @@ const { stat } = require('fs');
123
137
  let frontmatter_content = "";
124
138
  md.use(mdfm, function (fm) {
125
139
  frontmatter_content = fm;
126
- });
140
+ });
127
141
 
128
142
  // Process tips
129
143
  const tips = require(__dirname + '/custom_modules/tips.js');
130
144
  md.use(tips, {
131
- links: true
145
+ links: true
132
146
  });
133
147
 
134
148
  // Render markdown into HTML
@@ -138,7 +152,7 @@ const { stat } = require('fs');
138
152
  let fm_contains_title = false;
139
153
  let fm_content = frontmatter_content.split(/\r?\n/);
140
154
  if (fm_content.length >= 0) {
141
- fm_content.forEach(function(fm_prop){
155
+ fm_content.forEach(function (fm_prop) {
142
156
  const fm_property = fm_prop.split(':');
143
157
  if (fm_property[0] && fm_property[0] === 'title' && fm_property[1] && fm_property[1].length > 0) fm_contains_title = true;
144
158
  });
@@ -162,40 +176,43 @@ const { stat } = require('fs');
162
176
  if (frontmatter_content.length) {
163
177
  html_txt = "<!--[[FRONTMATTER\r\n" + frontmatter_content + "\r\n]]-->\r\n" + html_txt;
164
178
  }
165
-
179
+
166
180
  // Save HTML into HTML file
167
- const target_file = file_path.replace(path.extname(file_path), '.html');
181
+ const target_file = file_path.path.replace(path.extname(file_path.path), '.html');
182
+ const relative_path = file_path.relativePath.replace(path.extname(file_path.path), '.html');
168
183
  fs.writeFile(target_file, html_txt, function writeJSON(err) {
169
184
  if (err) return console.log('Error writing:', target_file, '\r\n', err);
170
185
  });
171
186
 
187
+ index_records.push({relative_path: relative_path, index_html: hdoc_index.transform_html_for_index(html_txt)});
188
+
172
189
  // Delete MD file from _work path
173
190
  try {
174
- fs.unlinkSync(file_path);
191
+ fs.unlinkSync(file_path.path);
175
192
  } catch (e) {
176
- console.log(`Error deleting ${file_path}: ${e}`);
193
+ console.log(`Error deleting ${file_path.path}: ${e}`);
177
194
  }
178
195
  conversion_success++;
179
196
  return true;
180
197
  }
181
198
  conversion_failed++;
182
- console.error('MD file does not exist:', file_path);
199
+ console.error('MD file does not exist:', file_path.path);
183
200
  return false;
184
201
  };
185
202
 
186
203
  // File callbacks for scans
187
204
  const fileCallback = function (element) {
188
205
  if (element.extension === 'md') {
189
- md_files.push(element.path);
206
+ md_files.push(element);
190
207
  } else {
191
- static_html_files.push(element.path);
208
+ static_html_files.push(element);
192
209
  }
193
210
  };
194
211
 
195
212
  const dreeOptions = {
196
213
  descendants: true,
197
214
  depth: 10,
198
- extensions: ['md','html','htm'],
215
+ extensions: ['md', 'html', 'htm'],
199
216
  hash: false,
200
217
  normalize: true,
201
218
  size: false,
@@ -210,7 +227,7 @@ const { stat } = require('fs');
210
227
  // * copy the hdocbook content to the work folder
211
228
  // * Render all markdown into side-by-side HTML file
212
229
  // * Replace SERVER_VARS embedded in documents with the right version information etc.
213
- // * Build an index (sqlite FTS5) by extracting text from all HTML content in the work
230
+ // * Build an index (sqlite FTS5) by extracting text from all HTML content in the work
214
231
  // folder, conceptually we are making a little mini website crawler to index all of the content
215
232
  // within the book.
216
233
  // * Package everything up into a ZIP file, ready for the build controller to package and publish
@@ -220,8 +237,8 @@ const { stat } = require('fs');
220
237
 
221
238
  // Load the hdocbook-project.json file to get the docId
222
239
  // use the docId to get the book config
223
- const hdocbook_project_config_path = path.join(source_path, 'hdocbook-project.json'),
224
- hdocbook_project = require(hdocbook_project_config_path);
240
+ const hdocbook_project_config_path = path.join(source_path, 'hdocbook-project.json');
241
+ hdocbook_project = require(hdocbook_project_config_path);
225
242
 
226
243
  docId = hdocbook_project.docId;
227
244
 
@@ -259,10 +276,11 @@ const { stat } = require('fs');
259
276
  });
260
277
 
261
278
  // Work through Static HTML files and add Frontmatter tags
262
- static_html_files.forEach(function(static_html_file){
263
- transform_html(static_html_file);
279
+ static_html_files.forEach(function (static_html_file) {
280
+ transform_static_html(static_html_file);
264
281
  });
265
282
 
283
+
266
284
  console.log(` MD files found: ${conversion_attempted}`);
267
285
  console.log(`Successfully converted to HTML: ${conversion_success}`);
268
286
  console.log(` Failed to convert: ${conversion_failed}\r\n`);
@@ -272,15 +290,49 @@ const { stat } = require('fs');
272
290
 
273
291
  console.log(` Static HTML Files Found: ${static_html_files.length}\r\n`);
274
292
 
293
+ // Validate content
275
294
  const validation_success = validate.run(work_path, docId, verbose);
276
295
  if (!validation_success) {
277
296
  process.exit(1);
278
297
  }
279
298
 
280
- const zip_path = path.join(work_path, docId + '.zip');
281
- zipper.sync.zip(path.join(work_path, docId)).compress().save(zip_path);
299
+ // Now build the index
300
+ console.log('Performing SQlite index creation...\n');
301
+ let db_name = path.join(work_path, docId, docId + '.db');
302
+ const db = new Database(db_name);
303
+ console.log(`Database created: ${db_name}`);
304
+
305
+ // Now add the table
306
+ const table_name = 'hdoc_index';
307
+ const table_created = hdoc_index.create_virtual_table(db, table_name, index_cols);
308
+
309
+ if (table_created !== null) {
310
+ console.log(`Error creating table: ${table_created}`);
311
+ } else {
312
+ console.log(`Virtual table created: ${table_created}`);
313
+ if (!hdocbook_config.tags) hdocbook_config.tags = [];
314
+ for (let i = 0; i < index_records.length; i++) {
315
+ const index_vals = [
316
+ index_records[i].relative_path.replace('\\', '/').replace(`${hdocbook_config.docId}/`, ''),
317
+ hdocbook_config.docId,
318
+ hdocbook_config.audience.join(','),
319
+ hdocbook_config.tags.join(','),
320
+ index_records[i].index_html.fm_props.title,
321
+ index_records[i].index_html.text
322
+ ];
323
+ hdoc_index.insert_record(db, table_name, index_cols, index_vals, hdocbook_config.docId, index_records[i].index_html.fm_props.title);
324
+ }
325
+ console.log(`\nIndex Build Complete`);
326
+ }
327
+
328
+ try {
329
+ const zip_path = path.join(work_path, docId + '.zip');
330
+ zipper.sync.zip(path.join(work_path, docId)).compress().save(zip_path);
331
+ console.log(`\nZIP Creation Success: ${zip_path}\n`);
332
+ console.log('Build Complete\n');
333
+ } catch (e) {
334
+ console.log('\nError creating ZIP: ' + e);
335
+ }
282
336
 
283
- console.log(`ZIP Creation Success: ${zip_path}\r\n`);
284
- console.log('Build Complete\r\n');
285
337
  };
286
338
  })();
package/hdoc-db.js CHANGED
@@ -1,148 +1,71 @@
1
- const {
2
- nextTick
3
- } = require('process');
4
-
5
1
  (function () {
6
2
  'use strict';
7
3
 
8
- const dree = require('dree'),
9
- fs = require('fs-extra'),
10
- html2text = require('html-to-text'),
11
- sqlite3 = require('sqlite3'),
4
+ const html2text = require('html-to-text'),
12
5
  path = require('path'),
13
6
  hdoc = require(path.join(__dirname, 'hdoc-module.js'));
14
7
 
15
- const table_name = 'hbdocs';
16
-
17
- let db_name = 'hbdocs.db',
18
- hdocbook_meta = null,
19
- md_files = [];
20
-
21
- // File callbacks for scan
22
- const file_callback = function (element) {
23
- if (path.extname(element.path) === '.md' && !element.path.includes('_work')) {
24
- md_files.push(element);
25
- } else if (element.name === 'hdocbook.json') {
26
- // hdocbook meta data, read and store
27
- hdocbook_meta = require(element.path);
8
+ exports.create_virtual_table = function (db, table_name, columns) {
9
+ let create_sql = [];
10
+ create_sql.push(`create VIRTUAL table ${table_name} USING fts5(`);
11
+ for (let i = 0; i < columns.length; i++) {
12
+ if (i !== 0) create_sql.push(`,${columns[i]}`);
13
+ else create_sql.push(columns[i]);
14
+ }
15
+ create_sql.push(');');
16
+ try {
17
+ db.exec(create_sql.join('\n'));
18
+ return null;
19
+ } catch (e) {
20
+ return e;
28
21
  }
29
22
  };
30
23
 
31
- const create_table = function (db) {
32
- db.exec(`
33
- create table ${table_name} (
34
- relative_url text primary key not null,
35
- book_id text not null,
36
- title text not null,
37
- content text not null,
38
- tags text null,
39
- audience text not null
40
- );
41
- `, (err) => {
42
- if (err !== null) {
43
- console.error(`Error creating table [${table_name}]: ${err}`);
44
- return false;
24
+ exports.insert_record = function (db, table, columns, values, book_id, doc_title) {
25
+ let queryProps = [];
26
+ queryProps.push(`INSERT INTO ${table}`);
27
+ let cols = '(';
28
+ let vals = 'VALUES (';
29
+ for (let i = 0; i < columns.length; i++) {
30
+ if (i === 0) {
31
+ cols += `${columns[i]}`;
32
+ vals += '?';
45
33
  } else {
46
- console.log(` Table created successfully: ${table_name}`);
47
- return true;
34
+ cols += `,${columns[i]}`;
35
+ vals += ',?';
48
36
  }
49
- });
50
- return true;
51
- };
52
-
53
- const build_db = function (db, md, book_def, md_files) {
54
- for (let i = 0; i < md_files.length; i++) {
55
-
56
- const content = transform_markdown(md_files[i].path, md);
57
- db.exec(`
58
- insert into ${table_name}
59
- (book_id, title, relative_url, content, tags, audience)
60
- values
61
- (
62
- '${book_def.docId}',
63
- '${book_def.title}',
64
- '${md_files[i].relativePath}',
65
- '${content}',
66
- '${book_def.tags.join(';')}',
67
- '${book_def.audience.join(';')}'
68
- );
69
- `, (err) => {
70
- if (err !== null) {
71
- console.error(` Error inserting record: [${md_files[i].relativePath}] ${err}`);
72
- return false;
73
- } else {
74
- console.log(` Record inserted successfully: ${md_files[i].relativePath}`);
75
- return true;
76
- }
77
- });
37
+ }
38
+ cols += ')';
39
+ vals += ')';
40
+ queryProps.push(cols);
41
+ queryProps.push(vals);
42
+
43
+ try {
44
+ const stmt = db.prepare(queryProps.join('\n'));
45
+ const info = stmt.run(values);
46
+ console.log(`Inserted index record ${info.lastInsertRowid}: ${book_id} - ${doc_title}`);
47
+ } catch (e) {
48
+ console.log(`Index record creation failed - ${book_id}/${doc_title}: ${e}`);
78
49
  }
79
50
  };
80
51
 
81
- const transform_markdown = function (file_path, md) {
82
- // Load markdown file
83
- let md_txt = hdoc.expand_variables(fs.readFileSync(file_path, 'utf8'));
52
+ exports.transform_html_for_index = function (html_txt) {
53
+ let response = {
54
+ text: '',
55
+ fm_props: {}
56
+ };
84
57
 
85
- // Pull in external includes
86
- const includes_processed = hdoc.process_includes(file_path, md_txt);
87
- md_txt = includes_processed.body;
88
-
89
- // Render markdown into HTML
90
- var html_txt = md.render(md_txt.toString());
58
+ // Get frontmatter properties
59
+ const fm_headers = hdoc.getHTMLFrontmatterHeader(html_txt);
60
+ response.fm_props = fm_headers.fm_properties;
91
61
 
92
62
  // Convert HTML into plain text
93
- const text = html2text.convert(html_txt, {
63
+ response.text = html2text.convert(html_txt, {
94
64
  wordwrap: null
95
- });
96
- return text;
97
- };
65
+ });
98
66
 
99
- const dree_options = {
100
- descendants: true,
101
- depth: 10,
102
- extensions: ['md', 'json'],
103
- hash: false,
104
- normalize: true,
105
- size: false,
106
- sizeInBytes: false,
107
- stat: false,
108
- symbolicLinks: false
67
+ return response;
109
68
  };
110
69
 
111
- exports.run = function (source_path, md) {
112
- /*
113
- STEVE - The purpose of this command is to allow content developers to do local builds of
114
- the SQlite database and index file, for validation before commit and build
115
- */
116
- dree.scan(source_path, dree_options, file_callback);
117
-
118
- if (hdocbook_meta === null) {
119
- console.error('hdocbook.json was not found, or has no content');
120
- process.exit(1);
121
- }
122
- if (md_files.length === 0) {
123
- console.error('No markdown files detected in', source_path);
124
- process.exit(1);
125
- }
126
70
 
127
- db_name = path.join(source_path, db_name);
128
- if (fs.existsSync(db_name)) {
129
- try {
130
- fs.removeSync(db_name);
131
- } catch (e) {
132
- console.error(`Failed to delete existing db file: ${db_name}`);
133
- }
134
- }
135
-
136
- let db = new sqlite3.Database(db_name, (err) => {
137
- if (err) {
138
- console.error('Error creating database:', err);
139
- process.exit(1);
140
- }
141
- console.log(`DB file created successfully: ${db_name}`);
142
- if (create_table(db)) {
143
- if (!hdocbook_meta.tags) hdocbook_meta.tags = [];
144
- build_db(db, md, hdocbook_meta, md_files);
145
- }
146
- });
147
- };
148
71
  })();
package/hdoc-module.js CHANGED
@@ -163,7 +163,7 @@
163
163
  exports.getHTMLFrontmatterHeader = function(html_body) {
164
164
  let response = {
165
165
  fm_header: '',
166
- fm_properties: []
166
+ fm_properties: {}
167
167
  };
168
168
  const $ = cheerio.load(html_body, { decodeEntities: false });
169
169
  if ($._root && $._root.children && $._root.children instanceof Array && $._root.children.length > 0) {
@@ -174,7 +174,7 @@
174
174
  for (let i = 0; i < fm_properties.length; i++) {
175
175
  const property_details = fm_properties[i].split(':', 2);
176
176
  if (property_details.length > 1) {
177
- response.fm_properties.push({key: property_details[0].trim(), value: property_details[1].trim()});
177
+ response.fm_properties[property_details[0].trim().toLowerCase()] = property_details[1].trim();
178
178
  }
179
179
  }
180
180
 
package/hdoc.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  (function () {
4
4
  'use strict';
@@ -77,9 +77,6 @@
77
77
  } else if (command == 'help') {
78
78
  const help = require(path.join(__dirname, 'hdoc-help.js'));
79
79
  help.run();
80
- } else if (command == 'buildindex') {
81
- const build_db = require(path.join(__dirname, 'hdoc-db.js'));
82
- build_db.run(source_path);
83
80
  } else {
84
81
  console.log('Unknown command:', command, '\r\n');
85
82
  console.log('Run hdoc help for information regarding this tool.\r\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hdoc-tools",
3
- "version": "0.7.27",
3
+ "version": "0.8.1",
4
4
  "description": "Hornbill HDocBook Development Support Tool",
5
5
  "main": "hdoc.js",
6
6
  "bin": {
@@ -28,6 +28,7 @@
28
28
  "author": "Hornbill Technologies Ltd",
29
29
  "license": "ISC",
30
30
  "dependencies": {
31
+ "better-sqlite3": "^8.0.1",
31
32
  "body-parser": "^1.20.1",
32
33
  "cheerio": "^1.0.0-rc.12",
33
34
  "cookie-parser": "^1.4.6",
@@ -42,7 +43,6 @@
42
43
  "markdown-it-front-matter": "^0.2.3",
43
44
  "multer": "^1.4.5-lts.1",
44
45
  "prompt": "^1.3.0",
45
- "sqlite3": "^5.1.4",
46
46
  "stream": "0.0.2",
47
47
  "sync-request": "^6.1.0",
48
48
  "words-count": "^2.0.2",