@eit6609/storyteller 1.0.7 → 2.0.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/.eslintrc CHANGED
@@ -79,7 +79,7 @@ rules:
79
79
  # Custom rules
80
80
  no-warning-comments: 1
81
81
  max-depth: [1, 3]
82
- max-statements: [1, 16]
82
+ max-statements: [1, 20]
83
83
  camelcase: [2, {properties: "never"}]
84
84
  brace-style: [2, "1tbs", {allowSingleLine: true}]
85
85
  block-scoped-var: 2
package/README.md CHANGED
@@ -116,12 +116,13 @@ The methods `reset()` and `move()` are modifiers, and will be used with the `got
116
116
 
117
117
  The pages of the ebook are generated by a set of XHTML templates, one for every location of the game.
118
118
 
119
- After many experiments with the most popular templating engines for Node.js, I have chosen
120
- [Pug](https://pugjs.org/api/getting-started.html) because it gives you enough freedom to call JavaScript code inside
121
- the template. This is vital, but many engines (the very popular [Handlebars](https://handlebarsjs.com) among others)
122
- make the call of methods on a class instance a nightmare.
119
+ After many experiments with the most popular templating engines for Node.js, I have chosen:
123
120
 
124
- Of course [EJS](https://ejs.co) gives you *complete* freedom, but I prefer higher level engines like Pug.
121
+ * [EJS](https://ejs.co)
122
+ * [Pug](https://pugjs.org/api/getting-started.html)
123
+
124
+ There are many engines (the very popular [Handlebars](https://handlebarsjs.com) among others) but most
125
+ of them make the call of methods on a class instance a nightmare.
125
126
 
126
127
  I have added **experimental** support for markdown templating by means of my
127
128
  [Markdown Templates](https://github.com/eit6609/markdown-templates) engine.
@@ -129,7 +130,7 @@ I have added **experimental** support for markdown templating by means of my
129
130
  Let's see an example of template:
130
131
 
131
132
  ```pug
132
- doctype strict
133
+ doctype 1.1
133
134
  html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en')
134
135
  head
135
136
  link(href="style-epub.css", rel="stylesheet", type="text/css")
@@ -144,7 +145,7 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en')
144
145
  li Peg 2: #{state.pegs[1]}
145
146
  li Peg 3: #{state.pegs[2]}
146
147
  if state.isFinished()
147
- h1 YOU’VE WON!
148
+ h1 YOU WIN!
148
149
  br
149
150
  p.first Want to #[a(href=goto((state) => state.reset())) play again]?
150
151
  else
@@ -235,14 +236,17 @@ These are the supported options:
235
236
  as input for the [ePUB creator](https://github.com/eit6609/epub-creator), so you can put in this directory any extra
236
237
  file (images, stylesheets) that you need in the ePUB.
237
238
  * `metadata`, object, required, the options for the ePUB creator, with these properties:
238
- * `title`, string, optional, default `untitled`: the title of the ePUB
239
+ * `title`, string, optional, default `Untitled`: the title of the ePUB
239
240
  * `author`, string, optional, default no author: the author of the ePUB
240
241
  * `language`, string, optional, default `en`: the language of the ePUB
242
+ * `description`, string, optional, default no description: the description of the ePUB
243
+ * `isbn`, string, optional, default no ISBN: the ISBN of the ePUB
244
+ * `tags`, array of string, optional, default no subjects: the subjects of the ePUB
241
245
  * `cover`, string, optional, default no cover: a path relative to `outputDir` of an image that will become the cover
242
246
  of the ePUB
243
247
  * `filename`, string, required: the path of the generated ePUB
244
- * `markdown`, boolean, optional, default `false`: if `false` the template engine is Pug, otherwise it is Markdown
245
- Templates
248
+ * `templateEngine`, string, required, possible values: `ejs`, `pug`, `mt`: the template engine used to
249
+ generate the pages.
246
250
  * `debug`, boolean, optional, default `false`: if `true` the `debug()` function called in the templates will return the
247
251
  page key, otherwise the empty string.
248
252
  * `contentBefore`, array, optional, default `[]`. Extra, static pages to insert into the generated ePUB before the
@@ -287,15 +291,15 @@ The parameters are:
287
291
 
288
292
  * `templateName`: the path of a template file, without the extension, relative to the `templatesDir`. It defaults to
289
293
  the current template's name.
290
- * `action`: a function that receives a state as its only parameter and modifies it. It should return a falsy
294
+ * `action`: a function that receives a clone of the state as its only parameter and modifies it. It should return a falsy
291
295
  value unless it wants to replace the received state with a new one: in this case it should return the new state. This is
292
296
  handy for complex games because it enables you to move through independent stages of the game. More about this later,
293
297
  in the *Tips & Tricks* section.
294
298
 
295
299
  With this function you actually trigger the generation of the pages, because, if the requested page does not exist,
296
- the generator creates an empty page and enqueues it for the build, that is the execution of the template with the state
297
- of the page. That execution could find and execute some `goto()` that could trigger the creation of new pages, and so
298
- on.
300
+ the generator creates an empty page and enqueues it for the build, that is the execution of the template with a clone
301
+ of the state of the page. That execution could find and execute some `goto()` that could trigger the creation of new
302
+ pages, and so on.
299
303
 
300
304
  ## Examples
301
305
 
@@ -313,14 +317,19 @@ But if you are lazy you can just download the generated ePUBs.
313
317
 
314
318
  A classic puzzle!
315
319
 
316
- There are two scripts:
320
+ There are three scripts:
317
321
 
318
- * `main-xhtml.js`, that uses the Pug (.pug) templates
322
+ * `main-pug.js`, that uses the Pug (.pug) templates
323
+ * `main-ejs.js`, that uses the EJS (.html) templates
319
324
  * `main-markdown.js`, that uses the experimental Markdown Templates (.md) templates
320
325
 
321
326
  The generated ePUBs should be the same.
322
327
 
323
- You can download the generated ePUB [here](examples/goat-cabbage-wolf/code/goat-cabbage-wolf-xhtml.epub).
328
+ You can download the generated ePUBs here:
329
+
330
+ * [pug](examples/goat-cabbage-wolf/code/goat-cabbage-wolf-pug.epub)
331
+ * [ejs](examples/goat-cabbage-wolf/code/goat-cabbage-wolf-ejs.epub)
332
+ * [markdown](examples/goat-cabbage-wolf/code/goat-cabbage-wolf-markdown.epub)
324
333
 
325
334
  ### Desert Traversal
326
335
 
@@ -365,7 +374,7 @@ class Safe {
365
374
  }
366
375
 
367
376
  choose (digit) {
368
- this.ok = String(digit) === this.combination.charAt(this.index);
377
+ this.ok = this.ok && String(digit) === this.combination.charAt(this.index);
369
378
  this.index++;
370
379
  }
371
380
 
@@ -382,7 +391,7 @@ class Safe {
382
391
  with this template:
383
392
 
384
393
  ```pug
385
- doctype strict
394
+ doctype 1.1
386
395
  html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en')
387
396
  head
388
397
  link(href="style-epub.css", rel="stylesheet", type="text/css")
@@ -391,9 +400,9 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en')
391
400
  h3 Open the Safe
392
401
  hr
393
402
  if state.isRight()
394
- h1 YOU’VE WON!
403
+ h1 YOU WIN!
395
404
  else if state.isWrong()
396
- h1 YOU’VE LOST!
405
+ h1 YOU LOSE!
397
406
  else
398
407
  p.first Choose digit ##{state.index + 1}:
399
408
  ul
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eit6609/storyteller",
3
- "version": "1.0.7",
3
+ "version": "2.0.0",
4
4
  "description": "An interactive ebooks generator",
5
5
  "keywords": [
6
6
  "epub",
@@ -38,11 +38,12 @@
38
38
  "jasmine-spec-reporter": "^3.2.0"
39
39
  },
40
40
  "dependencies": {
41
- "@eit6609/epub-creator": "^1.0.0",
41
+ "@eit6609/epub-creator": "^1.1.0",
42
42
  "@eit6609/markdown-templates": "^1.0.0",
43
43
  "@hapi/joi": "^16.1.7",
44
+ "ejs": "^3.1.8",
44
45
  "lodash": "^4.17.10",
45
- "marked": "^0.8.2",
46
- "pug": "^2.0.4"
46
+ "marked": "^4.0.10",
47
+ "pug": "^3.0.1"
47
48
  }
48
49
  }
package/src/generator.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const
4
4
  { inspect } = require('util'),
5
- { filter, forEach, get, isUndefined, pick, sortBy } = require('lodash'),
5
+ { filter, forEach, get, isUndefined, omit, sortBy } = require('lodash'),
6
6
  Joi = require('@hapi/joi'),
7
7
  { EPUBCreator } = require('@eit6609/epub-creator'),
8
8
  Page = require('./page.js'),
@@ -20,10 +20,14 @@ const optionsSchema = Joi.object({
20
20
  title: Joi.string(),
21
21
  author: Joi.string(),
22
22
  language: Joi.string(),
23
+ isbn: Joi.string(),
24
+ description: Joi.string(),
25
+ tags: Joi.array().items(Joi.string()),
23
26
  cover: Joi.string(),
24
- filename: Joi.string().required()
27
+ filename: Joi.string().required(),
25
28
  }).required(),
26
- markdown: Joi.boolean(),
29
+ advancedMetadata: Joi.array(),
30
+ templateEngine: Joi.string().valid('pug', 'ejs', 'mt').required(),
27
31
  debug: Joi.boolean(),
28
32
  contentBefore: Joi.array().items(extraContentSchema),
29
33
  contentAfter: Joi.array().items(extraContentSchema),
@@ -41,7 +45,8 @@ class Generator {
41
45
  this.templatesDir = options.templatesDir;
42
46
  this.outputDir = options.outputDir;
43
47
  this.metadata = options.metadata;
44
- this.markdown = options.markdown === true;
48
+ this.advancedMetadata = options.advancedMetadata || [];
49
+ this.templateEngine = options.templateEngine;
45
50
  this.debug = options.debug === true;
46
51
  this.contentBefore = options.contentBefore || [];
47
52
  this.contentAfter = options.contentAfter || [];
@@ -96,6 +101,9 @@ class Generator {
96
101
  createEpub () {
97
102
  const spine = [];
98
103
  const toc = [];
104
+ if (this.metadata.cover) {
105
+ toc.push([{ label: 'Cover', href: 'cover-page.html' }]);
106
+ }
99
107
  for (const { tocLabel, fileName } of this.contentBefore) {
100
108
  spine.push(fileName);
101
109
  if (tocLabel) {
@@ -118,14 +126,15 @@ class Generator {
118
126
  spine,
119
127
  toc,
120
128
  cover,
121
- simpleMetadata: pick(this.metadata, ['author', 'title', 'language'])
129
+ simpleMetadata: omit(this.metadata, ['cover', 'filename']),
130
+ metadata: this.advancedMetadata,
122
131
  });
123
132
  return epubCreator.create(this.metadata.filename);
124
133
  }
125
134
 
126
135
  printReport () {
127
136
  function makeFilterByTemplateName (templateName) {
128
- return (pageKey) => pageKey.substring(0, pageKey.lastIndexOf('/')) === templateName;
137
+ return (pageKey) => pageKey.substring(0, pageKey.indexOf('{') - 1) === templateName;
129
138
  }
130
139
 
131
140
  const templateNames = sortBy(Array.from(this.templates.keys()));
package/src/page.js CHANGED
@@ -15,7 +15,7 @@ const {
15
15
  toPairsIn
16
16
  } = require('lodash'),
17
17
  fs = require('fs'),
18
- marked = require('marked');
18
+ { marked } = require('marked');
19
19
 
20
20
  function wrapBody (body) {
21
21
  return `<?xml version="1.0" encoding="utf-8"?>
@@ -98,8 +98,8 @@ class Page {
98
98
  followLink (templateName, action) {
99
99
  templateName = templateName || this.template.name;
100
100
  let { state } = this;
101
+ state = cloneDeep(state);
101
102
  if (action) {
102
- state = cloneDeep(state);
103
103
  const newState = action.call(null, state);
104
104
  // this way the action can create a new state and initialize a new stage of the game:
105
105
  if (newState) {
@@ -110,7 +110,7 @@ class Page {
110
110
  }
111
111
 
112
112
  build () {
113
- if (this.builder.markdown) {
113
+ if (this.builder.templateEngine === 'mt') {
114
114
  const text = this.template.build(this);
115
115
  const markedOptions = {
116
116
  smartypants: true,
package/src/template.js CHANGED
@@ -1,28 +1,73 @@
1
1
  'use strict';
2
2
 
3
3
  const
4
+ fs = require('fs'),
4
5
  pug = require('pug'),
5
- mt = require('@eit6609/markdown-templates');
6
+ mt = require('@eit6609/markdown-templates'),
7
+ ejs = require('ejs');
8
+
9
+ class Pug {
10
+ getFileExtension () {
11
+ return 'pug';
12
+ }
13
+
14
+ loadTemplate (fileName, { mockPug }) {
15
+ return (mockPug || pug).compileFile(fileName, { pretty: true });
16
+ }
17
+ }
18
+
19
+ class EJS {
20
+ getFileExtension () {
21
+ return 'ejs';
22
+ }
23
+
24
+ loadTemplate (fileName, { mockEJS }) {
25
+ if (mockEJS) {
26
+ return mockEJS.compile(mockEJS.getFileContent());
27
+ }
28
+ const src = fs.readFileSync(fileName, 'utf-8');
29
+ return ejs.compile(src);
30
+ }
31
+ }
32
+
33
+ class MarkdownTemplates {
34
+ getFileExtension () {
35
+ return 'md';
36
+ }
37
+
38
+ loadTemplate (fileName, { mockMT }) {
39
+ return (mockMT || mt).compileFile(fileName);
40
+ }
41
+ }
6
42
 
7
43
  class Template {
8
44
 
9
- constructor (name, builder, mockMT, mockPug) {
45
+ static createTemplateEngineAdapter (name) {
46
+ switch (name) {
47
+ case 'pug':
48
+ return new Pug();
49
+ case 'ejs':
50
+ return new EJS();
51
+ case 'mt':
52
+ return new MarkdownTemplates();
53
+ default:
54
+ throw new Error(`Unknown template engine: "${name}"`);
55
+ }
56
+ }
57
+
58
+ constructor (name, builder, { mockMT, mockPug, mockEJS } = {}) {
10
59
  this.name = name;
11
60
  this.templatesDir = builder.templatesDir;
12
- this.markdown = builder.markdown;
13
- this.load(mockMT, mockPug);
61
+ this.adapter = Template.createTemplateEngineAdapter(builder.templateEngine || 'ejs');
62
+ this.load({ mockMT, mockPug, mockEJS });
14
63
  }
15
64
 
16
- load (mockMT, mockPug) {
17
- if (this.markdown) {
18
- this.template = (mockMT || mt).compileFile(this.getFileName());
19
- } else {
20
- this.template = (mockPug || pug).compileFile(this.getFileName(), { pretty: true });
21
- }
65
+ load ({ mockMT, mockPug, mockEJS }) {
66
+ this.template = this.adapter.loadTemplate(this.getFileName(), { mockMT, mockPug, mockEJS });
22
67
  }
23
68
 
24
69
  getFileName () {
25
- return `${this.templatesDir}/${this.name}.${this.markdown ? 'md' : 'pug'}`;
70
+ return `${this.templatesDir}/${this.name}.${this.adapter.getFileExtension()}`;
26
71
  }
27
72
 
28
73
  build (page) {
@@ -31,5 +76,4 @@ class Template {
31
76
 
32
77
  }
33
78
 
34
-
35
79
  module.exports = Template;