UNPKG

27.8 kBtext/coffeescriptView Raw
1# Requires
2fs = require 'fs'
3path = require 'path'
4util = require 'bal-util'
5coffee = require 'coffee-script'
6less = require 'less-bal'
7pulverizr = require 'pulverizr-bal'
8csslint = require('csslint').CSSLint
9jshint = require('jshint').JSHINT
10uglify = require 'uglify-js'
11jsp = uglify.parser
12pro = uglify.uglify
13cwd = process.cwd()
14watchTree = false
15caterpillar = require 'caterpillar'
16
17# =====================================
18# Prototypes
19
20# Checks if an array contains a value
21Array::has or= (value2) ->
22 for value1 in @
23 if value1 is value2
24 return true
25 return false
26
27
28# =====================================
29# Buildr
30
31# Define
32class Buildr
33
34 # Configuration
35 config: {
36 # Options
37 name: null # (name to be outputted in log messages) String or null
38 log: true # (log status updates to console?) true or false
39 watch: false # (automatically rebuild on file change?) true or false
40
41 # Handlers
42 buildHandler: false # (fired when build completed) function or false
43 rebuildHandler: false # (fired when rebuild completed) function or false
44 successHandler: false # (fired when (re)build completed successfully) function or false
45
46 # Paths
47 srcPath: false # String
48 outPath: false # String or false
49
50 # Checking
51 checkScripts: false # Array or true or false
52 checkStyles: false # Array or true or false
53 jshintOptions: false # Object or false
54 csslintOptions: false # Object or false
55
56 # Compression (without outPath only the generated bundle files are compressed)
57 compressScripts: false # Array or true or false
58 compressStyles: false # Array or true or false
59 compressImages: false # Array or true or false
60
61 # Order
62 scriptsOrder: false # Array or false
63 stylesOrder: false # Array or false
64
65 # Bundling (requires Order)
66 bundleScriptPath: false # String or false
67 bundleStylePath: false # String or false
68 deleteBundledFiles: false # (requires outPath) true or false
69
70 # Loaders (requires Order)
71 srcLoaderHeader: false # String or false
72 srcLoaderPath: false # String or false
73 }
74
75 # Files to clean
76 filesToClean: []
77
78 # Error
79 errors: []
80
81 # Watching
82 watching: false
83
84 # Processing
85 processing: false
86
87 # Logger
88 logger: false
89
90 # Constructor
91 constructor: (options={}) ->
92 # Prepare
93 @filesToClean = []
94 @errors = []
95
96 # Apply configuration
97 tmp = {}
98 tmp[key] = value for own key,value of @config
99 tmp[key] = value for own key,value of options
100 @config = tmp
101
102 # Handlers
103 @config.buildHandler or= (err) =>
104 if err
105 @log 'error', err
106 throw err
107 @log 'info', 'Building completed'
108 @config.successHandler.call(@) if @config.successHandler
109 @config.rebuildHandler or= (err) =>
110 if err
111 @log 'error', err
112 throw err
113 @log 'info', 'ReBuilding completed'
114 @config.successHandler.call(@) if @config.successHandler
115
116 # Logger
117 if @config.log is true then @config.log = 6
118 @logger or= @config.logger or new caterpillar.Logger
119 transports:
120 level: @config.log or 6
121 formatter:
122 module: module
123
124 # Completed
125 true
126
127
128 # =====================================
129 # Actions
130
131 # Log
132 log: (args...) =>
133 if @config.name
134 type = args.shift()
135 args.unshift "[#{@config.name}]"
136 args.unshift type
137 @logger.log.apply(@logger,args)
138
139 # Watch
140 watch: (next) ->
141 # Check
142 if @watching
143 return
144 else
145 @watching = true
146
147 # Requires
148 watchTree = require 'watch-tree' unless watchTree
149
150 # Prepare
151 buildr = @
152 log = @log
153 next or= @config.rebuildHandler or @config.buildHandler
154
155 # Log
156 log 'debug', 'Setting up watching...'
157
158 # Watch the src directory
159 watcher = watchTree.watchTree @config.srcPath
160 watcher.on 'fileDeleted', (path) ->
161 buildr.process ->
162 log 'info', 'Rebuilt due to file delete at '+(new Date()).toLocaleString()
163 next.apply(next,arguments)
164 watcher.on 'fileCreated', (path,stat) ->
165 buildr.process ->
166 log 'info', 'Rebuilt due to file create at '+(new Date()).toLocaleString()
167 next.apply(next,arguments)
168 watcher.on 'fileModified', (path,stat) ->
169 buildr.process ->
170 log 'info', 'Rebuilt due to file change at '+(new Date()).toLocaleString()
171 next.apply(next,arguments)
172
173 # Log
174 log 'debug', 'Watching setup'
175
176 # Next
177 next false
178
179 # Process
180 process: (next) ->
181 # Check
182 if @processing
183 log 'info', 'Processing postponed'
184 return
185 @processing = true
186
187 # Prepare
188 log = @log
189 next or= @config.buildHandler
190
191 # Log
192 log 'info', 'Processing started'
193
194 # Watch
195 @watch() if @config.watch
196
197 # Check configuration
198 @checkConfiguration (err) =>
199 return next err if err
200
201 # Check files
202 @checkFiles (err) =>
203 return next err if err
204
205 # Copy srcPath to outPath
206 @cpSrcToOut (err) =>
207 return next err if err
208
209 # Generate files
210 @generateFiles (err) =>
211 return next err if err
212
213 # Clean outPath
214 @cleanOutPath (err) =>
215 return next err if err
216
217 # Compress outPath
218 @compressFiles (err) =>
219 @processing = false
220 log 'info', 'Processing finished'
221 next err
222
223
224 # ---------------------------------
225 # Check Configuration
226
227 # Check Configuration
228 # next(err)
229 checkConfiguration: (next) ->
230 # Check
231 return next new Error('srcPath is required') unless @config.srcPath
232
233 # Prepare
234 log = @log
235 tasks = new util.Group (err) =>
236 log 'debug', 'Checked configuration'
237 next err
238 tasks.total = 6
239 log 'debug', 'Checking configuration'
240
241 # Ensure
242 @config.outPath or= @config.srcPath
243
244 # Expand srcPath
245 util.expandPath @config.srcPath, cwd, {}, (err,srcPath) =>
246 return tasks.exit err if err
247 @config.srcPath = srcPath
248 tasks.complete err
249
250 # Expand outPath
251 util.expandPath @config.outPath, cwd, {}, (err,outPath) =>
252 return tasks.exit err if err
253 @config.outPath = outPath
254 tasks.complete err
255
256 # Expand bundleScriptPath
257 if @config.bundleScriptPath
258 util.expandPath @config.bundleScriptPath, cwd, {}, (err,bundleScriptPath) =>
259 return tasks.exit err if err
260 @config.bundleScriptPath = bundleScriptPath
261 tasks.complete err
262 else
263 tasks.complete false
264
265 # Expand bundleStylePath
266 if @config.bundleStylePath
267 util.expandPath @config.bundleStylePath, cwd, {}, (err,bundleStylePath) =>
268 return tasks.exit err if err
269 @config.bundleStylePath = bundleStylePath
270 tasks.complete err
271 else
272 tasks.complete false
273
274 # Expand srcLoaderPath
275 if @config.srcLoaderPath
276 util.expandPath @config.srcLoaderPath, cwd, {}, (err,srcLoaderPath) =>
277 return tasks.exit err if err
278 @config.srcLoaderPath = srcLoaderPath
279 tasks.complete err
280 else
281 tasks.complete false
282
283 # Adjust Atomic Options
284 if @config.srcPath is @config.outPath
285 @config.deleteBundledFiles = false
286 if @config.compressScripts
287 @config.compressScripts =
288 if @config.bundleScriptPath
289 [@config.bundleScriptPath]
290 else
291 false
292 if @config.compressStyles
293 @config.compressStyles =
294 if @config.bundleStylePath
295 [@config.bundleStylePath]
296 else
297 false
298 if @config.compressImages
299 @config.compressImages = false
300
301 # Auto find files?
302 # Not yet implemented
303 if @config.bundleScripts is true
304 @config.bundleScripts = false
305 if @config.bundleStyles is true
306 @config.bundleStyles = false
307
308 # Finish
309 tasks.complete false
310
311
312 # ---------------------------------
313 # Check Files
314
315 # Check files
316 # next(err)
317 checkFiles: (next,config) ->
318 # Prepare
319 log = @log
320 config or= @config
321 return next false unless config.checkScripts or config.checkStyles
322 log 'debug', 'Check files'
323
324 # Handle
325 @forFilesInDirectory(
326 # Directory
327 config.srcPath
328
329 # Callback
330 (fileFullPath,fileRelativePath,next) =>
331 # Render
332 @checkFile fileFullPath, next
333
334 # Next
335 (err) ->
336 return next err if err
337 log 'debug', 'Checked files'
338 next err
339 )
340
341 # Completed
342 true
343
344
345 # ---------------------------------
346 # Copy srcPath to outPath
347
348 # Copy srcPath to outPath
349 # next(err)
350 cpSrcToOut: (next,config) ->
351 # Prepare
352 log = @log
353 config or= @config
354 return next false if config.outPath is config.srcPath
355 log 'debug', "Copying #{config.srcPath} to #{config.outPath}"
356
357 # Remove outPath
358 util.rmdir config.outPath, (err) ->
359 return next err if err
360
361 # Copy srcPath to outPath
362 util.cpdir config.srcPath, config.outPath, (err) ->
363 # Next
364 log 'debug', "Copied #{config.srcPath} to #{config.outPath}"
365 next err
366
367 # Completed
368 true
369
370
371 # ---------------------------------
372 # Generate Files
373
374 # Generate files
375 # next(err)
376 generateFiles: (next) ->
377 # Prepare
378 log = @log
379 tasks = new util.Group (err) ->
380 log 'debug', 'Generated files'
381 next err
382 tasks.total += 3
383 log 'debug', 'Generating files'
384
385 # Generate src loader file
386 @generateSrcLoaderFile tasks.completer()
387
388 # Generate bundled script file
389 @generateBundledScriptFile tasks.completer()
390
391 # Generate bundle style file
392 @generateBundledStyleFile tasks.completer()
393
394 # Completed
395 true
396
397 # Generate src loader file
398 # next(err)
399 generateSrcLoaderFile: (next,config) ->
400 # Check
401 config or= @config
402 return next false unless config.srcLoaderPath
403
404 # Log
405 log 'debug', "Generating #{config.srcLoaderPath}"
406
407 # Prepare
408 templates = {}
409 srcLoaderData = ''
410 srcLoaderPath = config.srcLoaderPath
411 loadedInTemplates = null
412
413 # Loaded in Templates
414 templateTasks = new util.Group (err) =>
415 # Check
416 next err if err
417
418 # Stringify scripts
419 srcLoaderData += "scripts = [\n"
420 for script in config.scriptsOrder
421 srcLoaderData += "\t'#{script}'\n"
422 srcLoaderData += "\]\n\n"
423
424 # Stringify styles
425 srcLoaderData += "styles = [\n"
426 for style in config.stylesOrder
427 srcLoaderData += "\t'#{style}'\n"
428 srcLoaderData += "\]\n\n"
429
430 # Append Templates
431 srcLoaderData += templates.srcLoader+"\n\n"+templates.srcLoaderHeader
432
433 # Write in coffee first for debugging
434 fs.writeFile srcLoaderPath, srcLoaderData, (err) ->
435 # Check
436 return next err if err
437
438 # Compile Script
439 srcLoaderData = coffee.compile(srcLoaderData)
440
441 # Now write in javascript
442 fs.writeFile srcLoaderPath, srcLoaderData, (err) ->
443 # Check
444 return next err if err
445
446 # Log
447 log 'debug', "Generated #{config.srcLoaderPath}"
448
449 # Good
450 next false
451
452 # Total Template Tasks
453 templateTasks.total = if config.srcLoaderHeader then 1 else 2
454
455 # Load srcLoader Template
456 fs.readFile __dirname+'/templates/srcLoader.coffee', (err,data) ->
457 return templateTasks.exit err if err
458 templates.srcLoader = data.toString()
459 templateTasks.complete err
460
461 # Load srcLoaderHeader Template
462 if config.srcLoaderHeader
463 templates.srcLoaderHeader = config.srcLoaderHeader
464 else
465 fs.readFile __dirname+'/templates/srcLoaderHeader.coffee', (err,data) ->
466 return templateTasks.exit err if err
467 templates.srcLoaderHeader = data.toString()
468 templateTasks.complete err
469
470 # Completed
471 true
472
473 # Generate out style file
474 # next(err)
475 generateBundledStyleFile: (next,config) ->
476 # Check
477 log = @log
478 config or= @config
479 return next false unless config.bundleStylePath
480
481 # Log
482 log 'debug', "Generating #{config.bundleStylePath}"
483
484 # Prepare
485 source = ''
486
487 # Cycle
488 @useOrScan(
489 # Files
490 config.stylesOrder
491
492 # Directory
493 @config.outPath
494
495 # Callback
496 (fileFullPath,fileRelativePath,next) =>
497 # Ensure .less file exists
498 extension = path.extname(fileRelativePath)
499 switch extension
500 # CSS
501 when '.css'
502 # Determine less path
503 _fileRelativePath = fileRelativePath
504 _fileFullPath = fileFullPath
505 fileRelativePath = _fileRelativePath.substring(0,_fileRelativePath.length-extension.length)+'.less'
506 fileFullPath = _fileFullPath.substring(0,_fileFullPath.length-extension.length)+'.less'
507
508 # Amend clean files
509 if config.deleteBundledFiles
510 @filesToClean.push _fileFullPath
511 @filesToClean.push fileFullPath
512
513 # Get relative bundled path
514 bundledRelativePath = @getRelativePath(
515 config.bundleStylePath
516 fileFullPath
517 )
518
519 # Check if less path exists
520 path.exists fileFullPath, (exists) ->
521 # It does
522 if exists
523 # Append source
524 source += """@import "#{bundledRelativePath}";\n"""
525 next false
526 # It doesn't
527 else
528 # Create it
529 util.cp _fileFullPath, fileFullPath, (err) ->
530 return next err if err
531 # Append source
532 source += """@import "#{bundledRelativePath}";\n"""
533 next false
534
535 # Less
536 when '.less'
537 # Amend clean files
538 if config.deleteBundledFiles
539 @filesToClean.push fileFullPath
540
541 # Get relative bundled path
542 bundledRelativePath = @getRelativePath(
543 config.bundleStylePath
544 fileFullPath
545 )
546
547 # Append source
548 source += """@import "#{bundledRelativePath}";\n"""
549 next false
550
551 # Something else
552 else
553 next false
554
555 # Next
556 (err) =>
557 return next err if err
558
559 # Log
560 log 'debug', "Compiling #{config.bundleStylePath}"
561
562 # Compile file
563 @compileStyleData(
564 # File Path
565 config.bundleStylePath
566
567 # Source
568 source
569
570 # Next
571 (err,result) ->
572 return next err if err
573
574 # Write
575 fs.writeFile config.bundleStylePath, result, (err) ->
576 # Log
577 log 'debug', "Generated #{config.bundleStylePath}"
578
579 # Forward
580 next err, result
581 )
582 )
583
584 # Completed
585 true
586
587 # Generate out script file
588 # next(err)
589 generateBundledScriptFile: (next,config) ->
590 # Check
591 log = @log
592 config or= @config
593 return next false unless config.bundleScriptPath
594
595 # Log
596 log 'debug', "Generating #{config.bundleScriptPath}"
597
598 # Prepare
599 results = {}
600
601 # Cycle
602 @useOrScan(
603 # Files
604 config.scriptsOrder
605
606 # Directory
607 config.outPath
608
609 # Callback
610 (fileFullPath,fileRelativePath,next) =>
611 # Ensure valid extension
612 extension = path.extname(fileRelativePath)
613 switch extension
614 # Script
615 when '.js','.coffee'
616 # Render
617 @compileScriptFile(
618 # File path
619 fileFullPath
620
621 # Next
622 (err,result) =>
623 return next err if err
624 results[fileRelativePath] = result
625 if config.deleteBundledFiles
626 @filesToClean.push fileFullPath
627 next err
628
629 # Write file
630 false
631 )
632
633 # Else
634 else
635 next false
636
637 # Next
638 (err) ->
639 return next err if err
640
641 # Prepare
642 result = ''
643
644 # Cycle Array
645 if config.scriptsOrder.has?
646 for fileRelativePath in config.scriptsOrder
647 return next new Error("The file #{fileRelativePath} failed to compile") unless results[fileRelativePath]?
648 result += results[fileRelativePath]
649
650 # Write file
651 fs.writeFile config.bundleScriptPath, result, (err) ->
652 # Log
653 log 'debug', "Generated #{config.bundleScriptPath}"
654
655 # Forward
656 next err
657 )
658
659 # Completed
660 true
661
662
663 # ---------------------------------
664 # Clean outPath
665
666 # Clean outPath
667 # next(err)
668 cleanOutPath: (next) ->
669 # Check
670 return next false unless (@filesToClean||[]).length
671
672 # Prepare
673 log = @log
674 tasks = new util.Group (err) ->
675 log 'debug', 'Cleaned outPath'
676 next err
677 tasks.total += @filesToClean.length
678 log 'debug', 'Cleaning outPath'
679
680 # Delete files to clean
681 for fileFullPath in @filesToClean
682 log 'debug', "Cleaning #{fileFullPath}"
683 fs.unlink fileFullPath, tasks.completer()
684
685 # Completed
686 true
687
688
689 # ---------------------------------
690 # Compress Files
691
692 # Compress files
693 # next(err)
694 compressFiles: (next,config) ->
695 # Prepare
696 config or= @config
697 return next false unless config.compressScripts or config.compressStyles or config.compressImages
698
699 # Prepare
700 log = @log
701 tasks = new util.Group (err) ->
702 log 'debug', 'Compressed files'
703 next err
704 tasks.total += 1
705 log 'debug', 'Compressing files'
706
707 # Bundled Files
708 if config.compressScripts is true
709 if config.bundleScriptPath
710 ++tasks.total
711 @compressFile config.bundleScriptPath, tasks.completer()
712 if config.bundleStylePath
713 ++tasks.total
714 @compressFile config.bundleStylePath, tasks.completer()
715
716 # Handle
717 @useOrScan(
718 # Array
719 config.compressScripts
720
721 # Directory
722 config.outPath
723
724 # Callback
725 (fileFullPath,fileRelativePath,next) =>
726 # Render
727 @compressFile fileFullPath, next
728
729 # Next
730 tasks.completer()
731 )
732
733 # Completed
734 true
735
736
737 # =====================================
738 # Helpers
739
740 # Determine relative path
741 getRelativePath: (srcPath,dstPath) ->
742 srcParts = srcPath.split /[\/\\]/g
743 dstParts = dstPath.split /[\/\\]/g
744
745 outParts = []
746 stillCommon = null
747 for srcPart,i in srcParts
748 if stillCommon is null
749 if srcPart isnt dstParts[i]
750 stillCommon = i
751 else
752 outParts.push '..'
753 for dstPart,i in dstParts[stillCommon..]
754 outParts.push dstPart
755
756 outPath = outParts.join '/'
757
758 # For each file in an array
759 # callback(fileFullPath,fileRelativePath,next)
760 # next(err)
761 forFilesInArray: (files,parentPath,callback,next) ->
762 # Check
763 return next false unless (files||[]).length
764
765 # Prepare
766 log = @log
767 tasks = new util.Group (err) =>
768 next err
769 tasks.total += files.length
770
771 # Cycle
772 for fileRelativePath in files
773 # Expand filePath
774 ((fileRelativePath)=>
775 util.expandPath fileRelativePath, parentPath, {}, (err,fileFullPath) =>
776 return tasks.exit err if err
777 callback fileFullPath, fileRelativePath, tasks.completer()
778 )(fileRelativePath)
779
780 # Completed
781 true
782
783 # For each file in a directory
784 # callback(fileFullPath,fileRelativePath,next)
785 # next(err)
786 forFilesInDirectory: (parentPath,callback,next) ->
787 # Scan for files
788 util.scandir(
789 # Path
790 parentPath
791
792 # File Action
793 # next(err)
794 callback
795
796 # Dir Action
797 false
798
799 # Next
800 next
801 )
802
803 # Completed
804 true
805
806 # Use or scan
807 # callback(fileFullPath,fileRelativePath,next)
808 # next(err)
809 useOrScan: (files,parentPath,callback,next) ->
810 # Handle
811 if files is true
812 @forFilesInDirectory(
813 # Directory
814 parentPath
815
816 # Callback
817 callback
818
819 # Next
820 next
821 )
822 else if files and files.length
823 @forFilesInArray(
824 # Files
825 files
826
827 # Directory
828 parentPath
829
830 # Callback
831 callback
832
833 # Next
834 next
835 )
836 else
837 next false
838
839 # Completed
840 true
841
842
843 # =====================================
844 # Files
845
846 # Compile the file
847 # next(err)
848 compileFile: (fileFullPath,next) ->
849 # Prepare
850 extension = path.extname fileFullPath
851
852 # Handle
853 switch extension
854 when '.coffee'
855 @compileScriptFile fileFullPath, next
856 when '.less'
857 @compileStyleFile fileFullPath, next
858 else
859 next false
860
861 # Completed
862 true
863
864 # Compress the file
865 # next(err)
866 compressFile: (fileFullPath,next,config) ->
867 # Prepare
868 config or= @config
869 extension = path.extname fileFullPath
870
871 # Handle
872 switch extension
873 # Scripts
874 when '.js'
875 if config.compressScripts is true or config.compressScripts.has? and config.compressScripts.has(fileFullPath)
876 @compressScriptFile fileFullPath, next
877 else
878 next false
879
880 # Styles
881 when '.css'
882 if config.compressStyles is true or config.compressStyles.has? and config.compressStyles.has(fileFullPath)
883 @compressStyleFile fileFullPath, next
884 else
885 next false
886
887 # Images
888 when '.gif','.jpg','.jpeg','.png','.tiff','.bmp'
889 if config.compressImages is true or config.compressImages.has? and config.compressImages.has(fileFullPath)
890 @compressImageFile fileFullPath, next
891 else
892 next false
893
894 # Other
895 else
896 next false
897
898 # Completed
899 true
900
901 # Check the file
902 # next(err)
903 checkFile: (fileFullPath,next,config) ->
904 # Prepare
905 config or= @config
906 extension = path.extname fileFullPath
907
908 # Handle
909 switch extension
910 when '.js'
911 if config.checkScripts is true or config.checkScripts.has? and config.checkScripts.has(fileFullPath)
912 @checkScriptFile fileFullPath, next
913 else
914 next false
915 when '.css'
916 if config.checkStyles is true or config.checkStyles.has? and config.checkStyles.has(fileFullPath)
917 @checkStyleFile fileFullPath, next
918 else
919 next false
920 else
921 next false
922
923 # Completed
924 true
925
926
927 # =====================================
928 # Image Files
929
930 # ---------------------------------
931 # Compress
932
933 # Compress Image File
934 # next(err)
935 compressImageFile: (fileFullPath,next) ->
936 # Prepare
937 log = @log
938
939 # Log
940 log 'debug', "Compressing #{fileFullPath}"
941
942 # Attempt
943 try
944 # Compress
945 pulverizr.compress fileFullPath, quiet: true
946
947 # Log
948 log 'debug', "Compressed #{fileFullPath}"
949
950 # Forward
951 next false
952
953 # Error
954 catch err
955 # Forward
956 next err
957
958 # Complete
959 true
960
961 # =====================================
962 # Style Files
963
964 # ---------------------------------
965 # Compile
966
967 # Compile Style File
968 # next(err,result)
969 compileStyleData: (fileFullPath,src,next) ->
970 # Prepare
971 log = @log
972 result = ''
973 options =
974 paths: [path.dirname(fileFullPath)]
975 optimization: 1
976 filename: fileFullPath
977
978 # Compile
979 new (less.Parser)(options).parse src, (err, tree) ->
980 if err
981 log 'debug', err
982 next new Error('Less compilation failed'), result
983 else
984 try
985 # Compile
986 result = tree.toCSS compress: 0
987
988 # Write
989 next false, result
990 catch err
991 next err, result
992
993 # Completed
994 true
995
996 # Compile Style File
997 # next(err,result)
998 compileStyleFile: (fileFullPath,next,write=true) ->
999 # Prepare
1000 log = @log
1001
1002 # Log
1003 # log 'debug', "Compiling #{fileFullPath}"
1004
1005 # Read
1006 fs.readFile fileFullPath, (err,data) =>
1007 return next err if err
1008
1009 # Compile
1010 @compileStyleData fileFullPath, data.toString(), (err,result) ->
1011 return next err, result if err or !write
1012
1013 # Write
1014 fs.writeFile fileFullPath, result, (err) ->
1015 return next err if err
1016
1017 # Log
1018 # log 'debug', "Compiled #{fileFullPath}"
1019
1020 # Forward
1021 next err, result
1022
1023 # Completed
1024 true
1025
1026 # ---------------------------------
1027 # Compress
1028
1029 # Compress Style File
1030 # next(err,result)
1031 compressStyleData: (fileFullPath,src,next) ->
1032 # Prepare
1033 log = @log
1034 result = ''
1035 options =
1036 paths: [path.dirname(fileFullPath)]
1037 optimization: 1
1038 filename: fileFullPath
1039
1040 # Compress
1041 new (less.Parser)(options).parse src, (err, tree) ->
1042 if err
1043 log 'debug', err
1044 next new Error('Less compilation failed'), result
1045 else
1046 try
1047 # Compress
1048 result = tree.toCSS compress: 1
1049
1050 # Write
1051 next false, result
1052 catch err
1053 next err, result
1054
1055 # Completed
1056 true
1057
1058 # Compress Style File
1059 # next(err,result)
1060 compressStyleFile: (fileFullPath,next,write=true) ->
1061 # Prepare
1062 log = @log
1063
1064 # Log
1065 log 'debug', "Compressing #{fileFullPath}"
1066
1067 # Read
1068 fs.readFile fileFullPath, (err,data) =>
1069 return next err if err
1070
1071 # Compress
1072 @compressStyleData fileFullPath, data.toString(), (err,result) ->
1073 return next err, result if err or !write
1074
1075 # Write
1076 fs.writeFile fileFullPath, result, (err) ->
1077 return next err if err
1078
1079 # Log
1080 log 'debug', "Compressed #{fileFullPath}"
1081
1082 # Forward
1083 next err, result
1084
1085 # Completed
1086 true
1087
1088 # ---------------------------------
1089 # Check
1090
1091 # Check Style Data
1092 # next(err,errord)
1093 checkStyleData: (fileFullPath,src,next,config) ->
1094 # Prepare
1095 log = @log
1096 config or= @config
1097 errord = false
1098
1099 # Peform checks
1100 result = csslint.verify src, config.csslintOptions||{}
1101 formatId = 'text'
1102
1103 # Check for errors
1104 unless result.messages.length
1105 return next false, false
1106
1107 # Log the errors
1108 for message in result.messages
1109 continue unless message and message.type is 'error'
1110
1111 # Errord
1112 errord = true
1113
1114 # Output
1115 if errord
1116 log 'error', csslint.getFormatter(formatId).formatResults(result, fileFullPath, formatId)
1117
1118 # Forward
1119 next false, errord
1120
1121
1122 # Check Style File
1123 # next(err,errord)
1124 checkStyleFile: (fileFullPath,next) ->
1125 # Prepare
1126 log = @log
1127
1128 # Log
1129 log 'debug', "Checking #{fileFullPath}"
1130
1131 # Read
1132 fs.readFile fileFullPath, (err,data) =>
1133 # Error
1134 return next err, false if err
1135
1136 # Check
1137 @checkStyleData fileFullPath, data.toString(), (err,errord) ->
1138 return next err if err
1139
1140 # Log
1141 log 'debug', "Checked #{fileFullPath}"
1142
1143 # Forward
1144 return next err, errord
1145
1146 # Completed
1147 true
1148
1149
1150 # =====================================
1151 # Script Files
1152
1153 # ---------------------------------
1154 # Compile
1155
1156 # Compile Script Data
1157 # next(err,result)
1158 compileScriptData: (extension,src,next) ->
1159 # Prepare
1160 result = false
1161
1162 # Compile
1163 try
1164 switch extension
1165 when '.coffee'
1166 result = coffee.compile src
1167 when '.js'
1168 result = src
1169 else
1170 throw new Error('Unknown script type: '+extension)
1171 catch err
1172 next err
1173
1174 # Forward
1175 next false, result
1176
1177 # Compile Script File
1178 # next(err,result)
1179 compileScriptFile: (fileFullPath,next,write=true) ->
1180 # Prepare
1181 log = @log
1182
1183 # Log
1184 # log 'debug', "Compiling #{fileFullPath}"
1185
1186 # Read
1187 fs.readFile fileFullPath, (err,data) =>
1188 return next err if err
1189
1190 # Compile
1191 @compileScriptData path.extname(fileFullPath), data.toString(), (err,result) ->
1192 return next err, result if err or !write
1193
1194 # Write
1195 fs.writeFile fileFullPath, result, (err) ->
1196 return next err if err
1197
1198 # Log
1199 # log 'debug', "Compiled #{fileFullPath}"
1200
1201 # Forward
1202 next err, result
1203
1204 # Completed
1205 true
1206
1207 # ---------------------------------
1208 # Compress
1209
1210 # Compress Script Data
1211 # next(err,result)
1212 compressScriptData: (src,next) ->
1213 # Compress
1214 ast = jsp.parse(src) # parse code and get the initial AST
1215 ast = pro.ast_mangle(ast) # get a new AST with mangled names
1216 ast = pro.ast_squeeze(ast) # get an AST with compression optimizations
1217 out = pro.gen_code(ast) # compressed code here
1218
1219 # Forward
1220 return next false, out
1221
1222 # Compress Script File
1223 # next(err,result)
1224 compressScriptFile: (fileFullPath,next,write=true) ->
1225 # Prepare
1226 log = @log
1227
1228 # Log
1229 log 'debug', "Compressing #{fileFullPath}"
1230
1231 # Read
1232 fs.readFile fileFullPath, (err,data) =>
1233 return next err if err
1234
1235 # Compile
1236 @compressScriptData data.toString(), (err,result) ->
1237 return next err, result if err or !write
1238
1239 # Write
1240 fs.writeFile fileFullPath, result, (err) ->
1241 return next err if err
1242
1243 # Log
1244 log 'debug', "Compressed #{fileFullPath}"
1245
1246 # Forward
1247 next err, result
1248
1249 # Completed
1250 true
1251
1252 # ---------------------------------
1253 # Check
1254
1255 # Check Script Data
1256 # next(err,errord)
1257 checkScriptData: (fileFullPath,src,next,config) ->
1258 # Prepare
1259 log = @log
1260 config or= @config
1261 errord = false
1262
1263 # Peform checks
1264 jshint src, config.jshintOptions||{}
1265 result = jshint.data()
1266 result.errors or= []
1267
1268 # Check for errors
1269 unless result.errors.length
1270 return next false, false
1271
1272 # Log the file
1273 log 'error', "\n#{fileFullPath}:"
1274
1275 # Log the errors
1276 for error in result.errors
1277 continue unless error and error.raw
1278
1279 # Errord
1280 errord = true
1281
1282 # Log
1283 message = error.raw.replace(/\.$/,'').replace /\{([a-z])\}/, (a,b) ->
1284 error[b] or a
1285 evidence =
1286 if error.evidence
1287 "\n\t" + error.evidence.replace(/^\s+/, '')
1288 else
1289 ''
1290 log 'warn', "\tLine #{error.line}: #{message} #{evidence}\n"
1291
1292 # Forward
1293 next false, errord
1294
1295
1296 # Check Script File
1297 # next(err,errord)
1298 checkScriptFile: (fileFullPath,next) ->
1299 # Prepare
1300 log = @log
1301
1302 # Log
1303 log 'debug', "Checking #{fileFullPath}"
1304
1305 # Read
1306 fs.readFile fileFullPath, (err,data) =>
1307 # Error
1308 return next err, false if err
1309
1310 # Check
1311 @checkScriptData fileFullPath, data.toString(), (err,errord) ->
1312 return next err if err
1313
1314 # Log
1315 log 'debug', "Checked #{fileFullPath}"
1316
1317 # Forward
1318 return next err, errord
1319
1320 # Completed
1321 true
1322
1323
1324# =====================================
1325# Export
1326
1327module.exports =
1328 createInstance: (options) ->
1329 return new Buildr(options)