1 | clean = require 'gulp-clean'
|
2 | frep = require "gulp-frep"
|
3 | gulp = require "gulp"
|
4 | gutil = require "gulp-util"
|
5 | rename = require 'gulp-rename'
|
6 | livereload = require "gulp-livereload"
|
7 | git = require 'gulp-git'
|
8 | expect = require 'gulp-expect-file'
|
9 | path = require "path"
|
10 | tap = require 'gulp-tap'
|
11 | q = require 'q'
|
12 | fs = require 'fs'
|
13 | jsYaml = require 'js-yaml'
|
14 |
|
15 | module.exports = class Gassetic
|
16 | constructor: (@env, @port, @log = true) ->
|
17 | @loadConfig()
|
18 | @includeModules()
|
19 | @validateConfig()
|
20 |
|
21 | |
22 |
|
23 |
|
24 | loadConfig: () ->
|
25 | @config = jsYaml.safeLoad fs.readFileSync 'gassetic.yml', 'utf8'
|
26 |
|
27 | |
28 |
|
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 |
|
68 |
|
69 | getMimetypes: () ->
|
70 | @config.mimetypes
|
71 |
|
72 | |
73 |
|
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 |
|
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 |
|
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
|
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 |
|
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 |
|
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 |