UNPKG

4.11 kBJavaScriptView Raw
1const fs = require('fs')
2 , path = require('path')
3 , after = require('after')
4 , cpr = require('cpr')
5
6function adjustForLang (orig, lang, callback) {
7 if (!lang) {
8 process.nextTick(function () { cb(orig); })
9 return
10 }
11
12 var extName = path.extname(orig)
13 , langAware = orig.slice(0, -extName.length) + '.' + lang + extName
14
15 fs.open(langAware, 'r', function (err, fd) {
16 if (!err && fd)
17 fs.close(fd)
18
19 callback(null, err ? orig : langAware)
20 });
21}
22
23// find a destination for this file, if it already exists then add a number to
24// the end of the filename (before extension) and keep incrementing that number
25// until we find a free filename
26
27function findDestination (file, lang, callback) {
28 if (lang)
29 file = file.replace('.' + lang, '')
30
31 file = path.basename(file)
32
33 var f = path.join(process.cwd(), file)
34
35 fs.exists(f, function (exists) {
36 if (!exists)
37 return callback(null, f)
38
39 var ext = path.extname(file)
40 , pfx = file.substring(0, file.length - ext.length)
41
42 ;(function next (i) {
43 f = path.join(process.cwd(), pfx + i + ext)
44
45 fs.exists(f, function (exists) {
46 if (!exists)
47 return callback(null, f)
48
49 next(i + 1)
50 })
51 }(1))
52 })
53}
54
55
56// copy the boilerplate files to CWD with names that aren't going to
57// overwrite existing files
58
59function prepare (callback) {
60 if (!this._boilerplate.length)
61 return process.nextTick(callback)
62
63 var done = after(this._boilerplate.length, callback)
64 , out = this.boilerplateOut = {}
65 , self = this;
66
67 this._boilerplate.forEach(function (src, index) {
68 adjustForLang(src, self.lang, function(err, src) {
69 // This will never happen, actually, but I wouldn't
70 // want you to fear it might :-)
71 if (err)
72 return callback(err)
73
74 self._boilerplate[index] = src
75 var callback = done
76 findDestination(src, self.lang, function (err, dst) {
77 if (err)
78 return callback(err)
79
80 out[src] = out[path.basename(src)] = path.basename(dst)
81
82 copy(src, dst, callback)
83 })
84 });
85 })
86}
87
88
89function copy (src, dst, callback) {
90 function copyFile () {
91 fs.createReadStream(src)
92 .on('error', function (err) {
93 callback && callback(err)
94 })
95 .pipe(fs.createWriteStream(dst))
96 .on('error', function (err) {
97 callback && callback(err)
98 })
99 .on('close', function () {
100 callback && callback()
101 })
102 }
103
104 fs.stat(src, function (err, stat) {
105 if (err)
106 return callback(err)
107
108 if (stat.isFile())
109 return copyFile()
110
111 if (stat.isDirectory())
112 return cpr(src, dst, callback)
113
114 return callback(new Error('Boilerplate source must be a regular file or directory'))
115 })
116}
117
118
119function fix (exercise) {
120 exercise._boilerplate = []
121
122 exercise.addPrepare(prepare)
123
124 exercise.addBoilerplate = function (file) {
125 if (Array.isArray(file)) {
126 return file.forEach(function (f) {
127 exercise.addBoilerplate(f)
128 })
129 }
130
131 if (typeof file != 'string')
132 throw new TypeError('addBoilerplate must be provided with a path to a file or an array of paths')
133
134 exercise._boilerplate.push(file)
135 }
136
137
138 // augment getExerciseText() such that the string {boilerplate:filename} will be replaced
139 // in the problem.md with the name of the copy of that file written to CWD
140
141 var getExerciseText = exercise.getExerciseText
142 exercise.getExerciseText = function (callback) {
143 var boilerplateOut = this.boilerplateOut
144
145 getExerciseText.call(this, function (err, type, contents) {
146 if (err)
147 return callback(err)
148
149 // proper path resolution
150 contents = contents.replace(
151 /\{boilerplate:([^}]+)\}/gi
152 , function (match, boilerplateFile) {
153 return boilerplateOut[boilerplateFile] || '(ERROR: Unknown boilerplate file)'
154 }
155 )
156
157 callback(null, type, contents)
158 })
159 }
160}
161
162
163function boilerplate (exercise) {
164 if (typeof exercise.addBoilerplate != 'function')
165 fix(exercise)
166 return exercise
167}
168
169
170module.exports = boilerplate