UNPKG

5.55 kBJavaScriptView Raw
1'use strict';
2
3/**
4this module provides functions for reading and writing local files,
5as well as functions for preparing data that is about to be written
6*/
7
8// Node.js built-ins
9
10const path = require('path');
11
12// foreign modules
13
14const pify = require('pify');
15const globp = pify(require('glob'));
16const loadJson = require('load-json-file');
17const memoize = require('lodash.memoize');
18const readJsonFiles = require('@blinkmobile/json-as-files').readData;
19const rimrafp = pify(require('rimraf'));
20const writeJson = require('write-json-file');
21const writeJsonFiles = require('@blinkmobile/json-as-files').writeData;
22
23// local modules
24
25const asyncp = require('./utils/asyncp');
26
27// this module
28
29function defaultResource (base) {
30 return {
31 config: {
32 all: {},
33 default: {
34 footer: { $file: `${base}.footer.html` },
35 header: { $file: `${base}.header.html` },
36 styleSheet: { $file: `${base}.styleSheet.css` }
37 }
38 }
39 };
40}
41
42function defaultAnswerSpace (base) {
43 const obj = defaultResource(base);
44 Object.assign(obj.config.all, {
45 basicModeDisabledMessage: { $file: `${base}.basicModeDisabledMessage.html` }
46 });
47 Object.assign(obj.config.default, {
48 htmlHead: { $file: `${base}.htmlHead.html` },
49 introMessage: { $file: `${base}.introMessage.html` }
50 });
51 return obj;
52}
53
54function defaultInteraction (base) {
55 const obj = defaultResource(base);
56 Object.assign(obj.config.default, {
57 inputPrompt: { $file: `${base}.inputPrompt.html` },
58 madl: { $file: `${base}.madl.php` },
59 message: { $file: `${base}.message.txt` },
60 xsl: { $file: `${base}.xsl.xml` }
61 });
62 return obj;
63}
64
65function deleteInteraction (options) {
66 return rimrafp(path.join(options.cwd, 'interactions', options.name));
67}
68
69function fixResource (data) {
70 delete data.created_time;
71 delete data.links;
72 delete data.modified_time;
73 return data;
74}
75
76function fixAnswerSpace (data) {
77 fixResource(data);
78 delete data.sitemap;
79 return data;
80}
81
82function fixInteraction (data) {
83 fixResource(data);
84 if (typeof data.order !== 'number') {
85 delete data.order;
86 }
87 return data;
88}
89
90/** mutates defaults, does not mutate existing */
91function mergePlaceholders (existing, defaults) {
92 [
93 'all', 'default'
94 ].forEach((section) => {
95 const config = existing.config || {};
96 config[section] = config[section] || {};
97 Object.keys(config[section]).forEach((key) => {
98 const value = config[section][key];
99 if (value && typeof value === 'object' && value.$file) {
100 defaults.config[section][key] = value;
101 }
102 });
103 });
104 return defaults;
105}
106
107function listInteractions (options) {
108 const cwd = options.cwd;
109 return globp('*/*.json', {
110 cwd: path.join(cwd, 'interactions')
111 })
112 .then((files) => {
113 return files
114 // keep just the results that match NAME/NAME.json pattern
115 .filter((x) => /^([^\\\s]+)\/\1\.json$/.test(x))
116 // then just give us the names
117 .map((x) => x.split('/')[0]);
118 })
119 .then((names) => asyncp.mapSeries(names, asyncp.asyncify((name) => {
120 return readInteraction({ cwd, name })
121 .then((data) => ({ id: data.id, name: data.name }));
122 })));
123}
124
125function newInteraction (options) {
126 const name = options.name;
127 const data = {
128 config: {
129 all: {},
130 default: {
131 display: 'hide',
132 displayName: name
133 }
134 },
135 name
136 };
137 switch (options.type) {
138 case 'madl':
139 data.config.all.type = 'madl code';
140 data.config.default.madl = '?><?php\n';
141 break;
142
143 case 'message':
144 data.config.all.type = options.type;
145 data.config.default.message = '\n';
146 break;
147 }
148 return data;
149}
150
151function readAnswerSpace (options) {
152 return readJsonFiles({ filePath: path.join(options.cwd, 'answerSpace.json') })
153 .then((data) => fixAnswerSpace(data));
154}
155
156const readCachedAnswerSpace = memoize(readAnswerSpace);
157
158function readInteraction (options) {
159 const filePath = path.join(options.cwd, 'interactions', options.name, `${options.name}.json`);
160 return readJsonFiles({ filePath })
161 .then((data) => fixInteraction(data));
162}
163
164function writeAnswerSpace (options) {
165 const filePath = path.join(options.cwd, 'answerSpace.json');
166 const defaultPlaceholders = defaultAnswerSpace('answerSpace');
167
168 return writeMergedPlaceholders(filePath, defaultPlaceholders)
169 .then(() => {
170 const data = Object.assign({}, options.data);
171 fixAnswerSpace(data);
172 // persist the real data, honoring the $file placeholders
173 return writeJsonFiles({ data, filePath });
174 });
175}
176
177function writeInteraction (options) {
178 const cwd = options.cwd;
179 const name = options.name;
180 const filePath = path.join(cwd, 'interactions', name, `${name}.json`);
181 const defaultPlaceholders = defaultInteraction(name);
182
183 return writeMergedPlaceholders(filePath, defaultPlaceholders)
184 .then(() => {
185 const data = Object.assign({}, options.data);
186 fixInteraction(data);
187 // persist the real data, honoring the $file placeholders
188 return writeJsonFiles({ data, filePath });
189 });
190}
191
192function writeMergedPlaceholders (filePath, defaults) {
193 return loadJson(filePath)
194 .catch(() => ({})) // default to empty Object
195 .then((existing) => mergePlaceholders(existing, defaults))
196 .then((merged) => writeJson(filePath, merged));
197}
198
199module.exports = {
200 deleteInteraction,
201 fixAnswerSpace,
202 fixInteraction,
203 listInteractions,
204 newInteraction,
205 readAnswerSpace,
206 readCachedAnswerSpace,
207 readInteraction,
208 writeAnswerSpace,
209 writeInteraction
210};