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