UNPKG

10 kBtext/coffeescriptView Raw
1clean = require 'gulp-clean'
2frep = require "gulp-frep"
3gulp = require "gulp"
4gutil = require "gulp-util"
5rename = require 'gulp-rename'
6livereload = require "gulp-livereload"
7git = require 'gulp-git'
8expect = require 'gulp-expect-file'
9path = require "path"
10tap = require 'gulp-tap'
11q = require 'q'
12fs = require 'fs'
13jsYaml = require 'js-yaml'
14
15module.exports = class Gassetic
16 constructor: (@env, @port, @log = true) ->
17 @loadConfig()
18 @includeModules()
19 @validateConfig()
20
21 ###
22 self explanatory
23 ###
24 loadConfig: () ->
25 @config = jsYaml.safeLoad fs.readFileSync 'gassetic.yml', 'utf8'
26
27 ###
28 self explanatory
29 ###
30 validateConfig: () ->
31 if !@getMimetypes()?
32 throw 'missing mimetypes in config'
33 if !@getDefaultTypes()?
34 throw 'missing default task in config'
35 for key of @getMimetypes()
36 if !@getMimetypes()[key][@env]?
37 throw 'missing environment ' + @env + ' in ' + key + ' mimetype'
38 if !@getMimetypes()[key][@env].tasks?
39 throw 'missing task list for ' + @env + ' environment in ' + key + ' mimetype (it can be empty array but must be defined)'
40 for task in @getMimetypes()[key][@env].tasks
41 if !task.name
42 throw 'invalid task "' + task.toString() + '" for ' + key + ' in ' + @env + ' environment, the structure must be like is {name: coffee, args: { bare: true }}'
43 if !@config.requires or @getModuleMethod(@modules, task.name) == undefined
44 throw 'undefined task ' + task.name
45 if !@getMimetypes()[key].files?
46 throw 'missing file list for ' + key + ' mimetype'
47
48 src = @getSourceFilesForType key
49 what = Object.prototype.toString
50 if what.call(src) != '[object Object]'
51 throw 'wrong file list for ' + key + ' mimetype'
52 for file of src
53 if what.call(file) != '[object String]'
54 throw 'invalid file "' + file + '" for ' + key + ' in ' + @env + ' environment'
55
56 if !@getMimetypes()[key][@env].outputFolder?
57 throw 'missing outputFolder path in ' + key + ' ' + @env
58
59 includeModules: () ->
60 @modules = {}
61 module.paths.unshift path.join @cwd(), 'node_modules'
62 module.paths.unshift @cwd()
63 for key, value of @config.requires
64 @modules[key] = require value
65
66 ###
67 @return {Object} mimetypes
68 ###
69 getMimetypes: () ->
70 @config.mimetypes
71
72 ###
73 @return {Array} default tasks
74 ###
75 getDefaultTypes: () ->
76 @config.default
77
78 ###
79 ###
80 getSourceFilesForType: (type) ->
81 @getMimetypes()[type].files
82
83 clean: () ->
84 result = q.defer()
85 files = []
86 for type of @getMimetypes()
87 @getDestinationPathsForType type
88 .map (f) ->
89 files.push f
90 gulp.src(files, read: false).pipe(clean(force: true)).on 'end', ->
91 result.resolve true
92 result.promise
93
94 getDestinationPathsForType: (type) ->
95 paths = []
96 for key, value of @getMimetypes()[type].files
97 paths.push path.join @getMimetypes()[type][@env].outputFolder, key
98 paths
99
100 ###
101 Builds all
102 ###
103 build: (type = null) ->
104 @replaces = {}
105 @watchFiles = []
106 @gitAdd = []
107 finalPromise = q.defer()
108 promises = []
109 if type == null
110 for type in @getDefaultTypes()
111 promises.push @buildType type
112 else
113 promises.push @buildType type
114 done = q.all promises
115 done.then =>
116 @replaceInFiles @replaces
117 .then ->
118 finalPromise.resolve true
119 finalPromise.promise
120
121 cwd: () ->
122 process.cwd()
123
124 ###
125 Builds one type defined in config
126 ###
127 buildType: (type) ->
128 buildOne = (type) =>
129 @replaces[type] = {}
130 all = []
131 tasks = @getMimetypes()[type][@env].tasks
132 gutil.log 'Processing:', gutil.colors.magenta(type), 'with', gutil.colors.gray(
133 (tasks.map (t) -> t.name + '(' + (if t.args then JSON.stringify(t.args) else '') + ')').join(', ')
134 ) if @log
135 for destFilename of @getSourceFilesForType type
136 all.push @buildFiles type, destFilename
137 q.all all
138
139 result = q.defer()
140 deps = @findDependentTypes type
141 if deps.length > 0
142 all = []
143 while deps.length > 0
144 next = deps.shift()
145 all.push @buildType next
146 q.all all
147 .then () =>
148 buildOne.call @, type
149 .then () ->
150 result.resolve true
151 else
152 buildOne.call @, type
153 .then () ->
154 result.resolve true
155 return result.promise
156
157 ###*
158 ###
159 buildFiles: (type, destinationFilenameConfigKey) ->
160 @replaces[type][destinationFilenameConfigKey] = []
161 result = q.defer()
162 tasks = @getMimetypes()[type][@env].tasks
163 gutil.log ' -', gutil.colors.cyan(destinationFilenameConfigKey) if @log
164 sourceFiles = @getMimetypes()[type].files[destinationFilenameConfigKey]
165 destination = path.join @getMimetypes()[type][@env].outputFolder, destinationFilenameConfigKey
166 pipe = gulp.src sourceFiles
167 filtered = sourceFiles.filter (path) ->
168 path.indexOf('*') == -1 # remove all with stars
169 pipe = pipe.pipe expect {errorOnFailure: true, reportUnexpected: false}, filtered
170 if @isDev() and (@getMimetypes()[type][@env].autoRenaming == undefined or @getMimetypes()[type][@env].autoRenaming == true)
171 i = 0
172 pipe = pipe.pipe rename (path) ->
173 path.basename += '_' + i++
174 path
175 tasks.map (t) =>
176 if !@getModuleMethod(@modules, t.name)?
177 gutil.log gutil.colors.red 'calling ' + t.name + ' task but it has not been defined, add it into the requires array'
178 if t.args?
179 if typeof t.args == 'string' or typeof t.args == 'number' or (typeof t.args == 'object' and t.args.length == undefined)
180 pipe = pipe.pipe @getModuleMethod(@modules, t.name) [@replaceArgs(t.args, destinationFilenameConfigKey)]...
181 else
182 pipe = pipe.pipe @getModuleMethod(@modules, t.name) @replaceArgs(t.args, destinationFilenameConfigKey)...
183 else if t.callback?
184 pipe = pipe.pipe @getModuleMethod(@modules, t.name) [@modules[t.callback]]...
185 else
186 pipe = pipe.pipe @getModuleMethod(@modules, t.name).call @
187 pipe = pipe.pipe gulp.dest destination
188 .pipe tap (f) =>
189 if @getMimetypes()[type][@env].webPath
190 webPath = f.path.substring (path.join(@cwd(), @getMimetypes()[type][@env].outputFolder)).length + 1
191 webPath = path.join @getMimetypes()[type][@env].webPath, webPath
192 @replaces[type][destinationFilenameConfigKey].push webPath
193 @watchFiles.push f.path
194 if @getMimetypes()[type][@env].autoGitAdd
195 @gitAdd.push f.path
196 .on 'end', ->
197 result.resolve true
198 return result.promise
199
200 getModuleMethod: (module, taskName) ->
201 levels = taskName.split '.'
202 while levels.length > 0
203 module = module[levels.shift()]
204 module
205
206 replaceArgs: (args, filename) ->
207 string = JSON.stringify args
208 string = string.replace '%filename%', filename
209 JSON.parse string
210
211 replaceInFiles: (replacements, callback) ->
212 regexs = []
213 for type of replacements
214 for one of replacements[type]
215 scripts = '\n'
216 for filename in replacements[type][one]
217 scripts += @buildScriptString(type, filename) + '\n'
218 regexs.push
219 pattern: new RegExp("<!-- " + @env + ':' + one + " -->([\\s\\S]*?)<!-- endbuild -->", "ig")
220 replacement: "<!-- " + @env + ":" + one + " -->" + scripts + "<!-- endbuild -->"
221 regexs.push
222 pattern: new RegExp("<!-- " + '\\*' + ':' + one + " -->([\\s\\S]*?)<!-- endbuild -->", "ig")
223 replacement: "<!-- " + '*' + ":" + one + " -->" + scripts + "<!-- endbuild -->"
224
225 allfiles = []
226 progress = []
227 i = 0
228 # find the templates first
229 gulp.src @config.replacementPaths, read: false
230 .pipe tap (file) =>
231 allfiles.push file.path
232 if @config.autoGitAdd
233 @gitAdd.push file.path
234 .on 'end', =>
235 # do the replace
236 for file in allfiles
237 result = q.defer()
238 progress.push result.promise
239 ((file, deferred) =>
240 pipe = gulp.src file
241 .pipe frep regexs
242 .pipe gulp.dest path.dirname file
243 .on 'end', =>
244 deferred.resolve true
245 ) file, result
246 if @gitAdd.length > 0
247 gulp.src @gitAdd
248 .pipe git.add()
249 return q.all progress
250
251 buildScriptString: (type, fileWebPath) ->
252 fileWebPath = fileWebPath.replace /\\/g, '/' # windows workaround
253 if @getMimetypes()[type][@env].htmlTag?
254 return @getMimetypes()[type][@env].htmlTag.replace /%path%/g, fileWebPath
255 else
256 ext = path.extname fileWebPath
257 switch ext
258 when ".css"
259 str = "<link rel=\"stylesheet\" href=\"" + fileWebPath + "\" />"
260 when ".js"
261 str = "<script src=\"" + fileWebPath + "\"></script>"
262 else
263 str = '<!-- extension not supported -->'
264 str
265
266 ###
267 Finds dependent types for type that needs to be run first
268 @param {string} type
269 @param {boolean} recursive
270 @return {Array} dependency types
271 ###
272 findDependentTypes: (type, recursive) ->
273 deps = []
274 if @config.mimetypes[type].deps?
275 for d in @config.mimetypes[type].deps
276 deps.push d
277 if recursive
278 deps = deps.concat @findDependentTypes d
279 deps
280
281 watch: () ->
282 port = @port || @config.livereload?.port
283 lrParams = @config.livereload?.options
284 if lrParams
285 if lrParams.cert && lrParams.key
286 lrParams.key = fs.readFileSync lrParams.key
287 lrParams.cert = fs.readFileSync lrParams.cert
288 server = livereload port, lrParams
289 else
290 server = livereload port
291
292 toWatch = []
293 for type in @getDefaultTypes()
294 toWatch.push type
295 for d in @findDependentTypes type, true
296 if toWatch.indexOf(d) == -1
297 toWatch.push d
298
299 for type in toWatch
300 if @getMimetypes()[type].watch?
301 @watchSources @getMimetypes()[type].watch, type
302 else
303 for destinationFile of @getMimetypes()[type].files
304 sources = @getMimetypes()[type].files[destinationFile]
305 @watchSources sources, type, destinationFile
306
307 gulp.watch @watchFiles
308 .on 'change', (e) =>
309 gutil.log gutil.colors.yellow new Date() if @log
310 gutil.log gutil.colors.blue e.path if @log
311 server.changed e
312
313 watchSources: (sources, type, destinationFile = '*') ->
314 gutil.log 'Watching', gutil.colors.cyan(sources.length), gutil.colors.magenta(type), 'paths for', gutil.colors.green(destinationFile), '...' if @log
315 gulp.watch sources
316 .on 'change', (e) =>
317 if destinationFile != '*'
318 destFiles = [destinationFile]
319 else
320 destFiles = []
321 for f of @getMimetypes()[type].files
322 destFiles.push f
323 for f in destFiles
324 @buildFiles type, f
325
326 isDev: () ->
327 @env == 'dev'
328
\No newline at end of file