UNPKG

5.2 kBJavaScriptView Raw
1var Path = require("path");
2var FS = require("fs");
3
4var TPL_DIR = Path.resolve(Path.join(__dirname, "../tpl"));
5
6/**
7 * A template is a text with directives.
8 * A directive is enclosed in double-curlies.
9 * Here is an example :
10 *
11 * `Welcome mister {{name}}! I'm happy to see you again.`
12 *
13 * @module template
14 */
15
16
17function makeDefaultReplacer(map) {
18 var lowerCaseMap = {}, key, val;
19 for (key in map) {
20 val = map[key];
21 lowerCaseMap[key.toLowerCase()] = val;
22 }
23 return function(name) {
24 var result = lowerCaseMap[name.toLowerCase()];
25 if (result === undefined || result === null) {
26 return "{{" + name + "}}";
27 }
28 return result;
29 };
30}
31
32/**
33 *
34 * @example
35 * var Tpl = require("./template");
36 *
37 * var text = "Hi {{Joe}} ! Who's {{Natacha}} ?";
38 * console.log(text);
39 * var replacer = function(name) {
40 * console.log("name: \"" + name + "\"");
41 * return "<data>" + name + "</data>";
42 * };
43 *
44 * var ctx = Tpl.text(text, replacer);
45 * console.log(ctx.out);
46 *
47 * ctx = Tpl.text(
48 * text,
49 * {
50 * joe: "buddy",
51 * natacha: "that girl"
52 * }
53 * );
54 * console.log(ctx.out);
55 *
56 *
57 * @param text Template text with `${NAME}` directives.
58 * @param replacer
59 * Can be of two types:
60 * * __object__: A map between the directives' names and the value of replacement.
61 * * __function__: A function with the directive's name as unique
62 * argument. It must return the replacement string. `this` is used
63 * as a context along all replacements. You can add any add attributs
64 * in it, but please avoid reserved one: `text`, `count`, `out`,
65 * `cursor`. The last gives the cursor's position when the directive
66 * has been reached.
67 * @return The context object with at least theses attributes :
68 * * __text__: Initial text.
69 * * __count__: Number of replacements made.
70 * * __out__: Text after replacements.
71 */
72exports.text = function(text, replacer) {
73 var ctx = {text: text, cursor: 0, count: 0, out: text};
74 if (typeof replacer === 'object') {
75 replacer = makeDefaultReplacer(replacer);
76 }
77 else if (typeof replacer !== 'function') {
78 delete ctx.cursor;
79 return ctx;
80 }
81 var lastPos = 0;
82 var cursor = 0;
83 var mode = 0;
84 var out = '';
85 var c;
86 var name;
87 var result;
88 var flush = function() {
89 out += text.substr(lastPos, cursor - lastPos);
90 lastPos = cursor + 1;
91 };
92 for (cursor = 0 ; cursor < text.length ; cursor++) {
93 c = text.charAt(cursor);
94 if (mode == 0) {
95 if (c == '\\') {
96 flush();
97 mode = 9;
98 }
99 else if (c == '{') {
100 flush();
101 mode = 1;
102 }
103 }
104 else if (mode == 1) {
105 if (c != '{') {
106 flush();
107 out += "{";
108 lastPos--;
109 mode = 0;
110 } else {
111 mode = 2;
112 }
113 }
114 else if (mode == 2) {
115 if (c == '}') {
116 mode = 3;
117 }
118 }
119 else if (mode == 3) {
120 if (c == '}') {
121 mode = 0;
122 ctx.cursor = lastPos;
123 name = text.substr(lastPos + 1, cursor - lastPos - 2).trim();
124 result = replacer.call(ctx, name);
125 ctx.count++;
126 out += result;
127 lastPos = cursor + 1;
128 }
129 }
130 else if (mode == 9) {
131 if (c != '{') {
132 out += '\\';
133 }
134 lastPos = cursor;
135 mode = 0;
136 }
137 }
138 out += text.substr(lastPos);
139 ctx.out = out;
140 delete ctx.cursor;
141 return ctx;
142};
143
144/**
145 * @return The content of `filename` after `replacer` has been applied.
146 * The returned object has the text content in its `oout` attribute.
147 */
148exports.file = function(filename, replacer) {
149 var file = Path.join(TPL_DIR, filename);
150 if (FS.existsSync(file)) {
151 var text = FS.readFileSync(file).toString();
152 return exports.text(text, replacer);
153 } else {
154 throw {fatal: "Mising template file: " + file};
155 }
156};
157
158/**
159 * Copy a whole directory into another place after replacer has been applied.
160 */
161exports.files = function(srcDir, dstDir, replacer) {
162 var statSrc = FS.statSync(Path.join(TPL_DIR, srcDir));
163 if (!statSrc.isDirectory()) return false;
164 if (!FS.existsSync(dstDir)) {
165 FS.mkdir(dstDir);
166 } else {
167 var statDst = FS.statSync(dstDir);
168 if (!statDst.isDirectory()) return false;
169 }
170 var files = FS.readdirSync(Path.join(TPL_DIR, srcDir));
171 files.forEach(
172 function(filename) {
173 var srcFile = Path.join(srcDir, filename);
174 var dstFile = Path.join(dstDir, filename);
175 var stat = FS.statSync(Path.join(TPL_DIR, srcFile));
176 if (stat.isFile()) {
177 var content = exports.file(srcFile, replacer).out;
178 FS.writeFileSync( dstFile, content );
179 } else {
180 exports.files(srcFile, dstFile, replacer);
181 }
182 }
183 );
184 return true;
185};