UNPKG

8.73 kBJavaScriptView Raw
1var fs = require('fs')
2var path = require('path')
3var mime = require('mime-types')
4var terraform = require('terraform')
5var fse = require('fs-extra')
6var envy = require('envy-json')
7
8
9exports.buildFallbackPriorityList = function(filePath){
10 var list = []
11 var popAndPush = function(arr){
12 if (arr.length === 1) return list
13 arr.pop()
14 var path200 = arr.join("/") + "/200.html"
15 var path404 = arr.join("/") + "/404.html"
16 var list200 = terraform.helpers.buildPriorityList(path200)
17 var list404 = terraform.helpers.buildPriorityList(path404)
18 list = list.concat(list200).concat(list404)
19 return popAndPush(arr)
20 }
21 return popAndPush(filePath.split("/"))
22}
23
24
25/**
26 *
27 * Normalize Url
28 *
29 * - removes querystring
30 * - removes extra slashes
31 * - changes `/` to `/index.html`
32 */
33
34exports.normalizeUrl = function(url){
35
36 // take off query string
37 var base = unescape(url.split('?')[0])
38
39 /**
40 * Normalize Path
41 *
42 * Note: This converts unix paths to windows path on windows
43 * (not sure if this is a good thing)
44 */
45 var file_path = path.normalize(base)
46
47 // index.html support
48 if (path.sep == file_path[file_path.length - 1]) file_path += 'index.html'
49
50 return file_path
51}
52
53
54/**
55 *
56 * Mime Type
57 *
58 * returns type of the file
59 *
60 * TODO: reference ext from terraform
61 */
62
63exports.mimeType = function(source){
64 var ext = path.extname(source)
65
66 if(['.jade', '.md', '.ejs'].indexOf(ext) !== -1){
67 return mime.lookup('html')
68 }else if(['.less', '.styl', '.scss', '.sass'].indexOf(ext) !== -1){
69 return mime.lookup('css')
70 } else if (['.js', '.coffee'].indexOf(ext) !== -1) {
71 return mime.lookup('js')
72 } else {
73 return mime.lookup(source)
74 }
75
76}
77
78
79/**
80 *
81 * Walk directory for files
82 *
83 * recursive function that returns the directory tree
84 * http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
85 *
86 */
87
88var walk = function(dir, done) {
89 var results = []
90
91 fs.readdir(dir, function(err, list) {
92 if (err){
93 return done(err)
94 }
95 var pending = list.length
96
97 if (!pending) return done(null, results);
98 list.forEach(function(file) {
99 file = path.resolve(dir, file)
100 fs.stat(file, function(err, stat) {
101 if (stat && stat.isDirectory()) {
102 walk(file, function(err, res) {
103 results = results.concat(res)
104 if (!--pending) done(null, results)
105 })
106 } else {
107 results.push(file)
108 if (!--pending) done(null, results)
109 }
110 })
111 })
112 })
113
114}
115
116
117/**
118 *
119 * Fetch all the file paths for a directory.
120 * returns and array of all the relative paths.
121 *
122 */
123
124exports.ls = function(dir, callback) {
125 walk(dir, function(err, results){
126 var files = []
127 results.map(function(file){ files.push(path.relative(dir, file)) })
128 callback(null, files)
129 })
130}
131
132
133/**
134 * Setup
135 *
136 * This is the style and configuration of a Harp Application.
137 * returns object with contents of Harp.json and application style
138 *
139 * {
140 * "projectPath" : "/path/to/app",
141 * "publicPath" : "/path/to/app/public",
142 * "config" : { ... }
143 * }
144 */
145
146exports.setup = function(projectPath, env){
147 if(!env) env = "development"
148
149 try{
150 var configPath = path.join(projectPath, "harp.json")
151 var contents = fs.readFileSync(configPath).toString()
152 var publicPath = path.join(projectPath, "public")
153 }catch(e){
154 try{
155 var configPath = path.join(projectPath, "_harp.json")
156 var contents = fs.readFileSync(configPath).toString()
157 var publicPath = projectPath
158 }catch(e){
159 var contents = "{}"
160 var publicPath = projectPath
161 }
162 }
163
164 // not sure what this does anymore.
165 if(!contents || contents.replace(/^\s\s*/, '').replace(/\s\s*$/, '') == ''){
166 contents = '{}'
167 }
168
169 // attempt to parse the file
170 try{
171 var cfg = JSON.parse(contents)
172 }catch(e){
173 e.source = "JSON"
174 e.dest = "CONFIG"
175 e.message = e.message
176 e.filename = configPath
177 e.stack = contents
178 e.lineno = -1
179 throw new terraform.helpers.TerraformError(e)
180 }
181
182 if(!cfg.hasOwnProperty('globals')) cfg['globals'] = {}
183
184 cfg.globals.environment = process.env.NODE_ENV || env
185
186 // replace values that look like environment variables
187 // e.g. '$foo' -> process.env.foo
188 cfg = envy(cfg)
189
190 return {
191 cwd : process.cwd(),
192 projectPath : projectPath,
193 publicPath : publicPath,
194 config : cfg
195 }
196
197}
198
199
200/**
201 *
202 * Template for outputing Less errors.
203 *
204 */
205
206exports.cssError = function(error){
207 var body = '' +
208
209 'body{' +
210 'margin:0;' +
211 '}' +
212
213 'body:before {' +
214 'display: block;'+
215 'white-space: pre;' +
216 'content: "'+ error.error.source +' -> ' + error.error.dest + ' (' + error.error.message + ') ' + error.error.filename + '";'+
217 'color: #444;'+
218 'background-color: #fefe96;' +
219 'padding: 40px 40px;'+
220 'margin: 0;'+
221 'font-family: monospace;'+
222 'font-size: 14px;'+
223 '}'
224
225 return body
226}
227
228
229/**
230 *
231 * Will Collide
232 *
233 * Returns true if first path is in the line of fire of the second path.
234 * ie: if we delete the second path will the first path be affected?
235 */
236
237var willCollide = exports.willCollide = function(projectPath, outputPath){
238 var projectPath = path.resolve(projectPath)
239 var outputPath = path.resolve(outputPath)
240 var relativePath = path.relative(projectPath, outputPath)
241 var arr = relativePath.split(path.sep)
242 var result = true;
243
244 arr.forEach(function(i){
245 if(i !== "..") result = false
246 })
247
248 /**
249 * for @kennethormandy ;)
250 */
251 if ([path.sep, "C:\\"].indexOf(outputPath) !== -1) result = true
252
253 /**
254 * For #400
255 */
256 if (projectPath === outputPath) result = true
257
258 return result
259}
260
261
262/**
263 *
264 * Will Allow
265 *
266 * Returns `true` if we feel projectPath is safe from the output path.
267 * For this to be the case. The outputPath must live only one directory
268 * back from the projectPath and the projectPath must live in a directory
269 * starting with an underscore.
270 */
271
272exports.willAllow = function(projectPath, outputPath){
273 var projectPath = path.resolve(projectPath)
274 var outputPath = path.resolve(outputPath)
275 var relativePath = path.relative(projectPath, outputPath)
276 var arr = relativePath.split(path.sep)
277
278 if(willCollide(projectPath, outputPath)){
279 if(relativePath === ".."){
280 if(projectPath.split(path.sep)[projectPath.split(path.sep).length - 1][0] == "_"){
281 return true
282 }else{
283 return false
284 }
285 }else{
286 return false
287 }
288 }else{
289 return true
290 }
291}
292
293
294/**
295 * Prime
296 * (Disk I/O)
297 *
298 * Cleans out a directory but ignores one (optionally).
299 *
300 * primePath: Absolute Path
301 * options: Object
302 * ignore: Absolute Path || Relative (to delete)
303 *
304 * This is a powerful Function so take it seriously.
305 *
306 */
307
308exports.prime = function(primePath, options, callback){
309
310 if(!callback){
311 callback = options
312 options = {}
313 }
314
315 /**
316 * Options (only one)
317 */
318 var ignorePath = options.ignore
319 ? path.resolve(primePath, options.ignore)
320 : null
321
322 // Absolute paths are predictable.
323 var primePath = path.resolve(primePath)
324
325 fse.mkdirp(primePath, function(){
326 fse.readdir(primePath, function(error, contents){
327
328 /**
329 * Delete each item in the directory in parallel. Thanks Ry!
330 */
331
332 if(contents.length == 0) return callback()
333
334 var total = contents.length
335 var count = 0
336
337
338 contents.forEach(function(i){
339 var filePath = path.resolve(primePath, i)
340 var gitRegExp = new RegExp(/^.git/)
341
342 /**
343 * We leave `.git`, `.gitignore`, and project path.
344 */
345 if(filePath === ignorePath || i.match(gitRegExp)){
346 count++
347 if(count == total) callback()
348 }else{
349 fse.remove(filePath, function(err){
350 count++
351 if(count == total) callback()
352 })
353 }
354 })
355
356 })
357 })
358
359}
360
361
362/**
363 * Stacktrace
364 *
365 * Formats a stactrace
366 *
367 *
368 * This is a powerful Function so take it seriously.
369 *
370 */
371
372exports.stacktrace = function(str, options){
373 var lineno = options.lineno || -1
374 var context = options.context || 8
375 var context = context = context / 2
376 var lines = ('\n' + str).split('\n')
377 var start = Math.max(lineno - context, 1)
378 var end = Math.min(lines.length, lineno + context)
379
380 if(lineno === -1) end = lines.length
381
382 var pad = end.toString().length
383
384 var context = lines.slice(start, end).map(function(line, i){
385 var curr = i + start
386 return (curr == lineno ? ' > ' : ' ')
387 + Array(pad - curr.toString().length + 1).join(' ')
388 + curr
389 + '| '
390 + line
391 }).join('\n')
392
393 return context
394}