UNPKG

coffee-toaster

Version:

Minimalist manager and build system for CoffeeScript, an alternative for AMD's or CJS's OOP patterns, but with similar results. Made for those who dare to use class definitions in CoffeeScript while being able to easily inherit from external files. Powered with imports directives that use wildcards facilities, exposed scopes and excluded files filter options. The system can even use folders-as-namespaces to help you avoid naming collisions in architecture.

396 lines (294 loc) 10.6 kB
#<< toaster/utils/* #<< toaster/core/script class Builder # requirements fs = require 'fs' path = require 'path' cs = require "coffee-script" uglify = require("uglify-js").uglify uglify_parser = require("uglify-js").parser Script = toaster.core.Script {FnUtil, FsUtil, ArrayUtil, StringUtil} = toaster.utils _include_tmpl: "document.write('<scri'+'pt src=\"%SRC%\"></scr'+'ipt>')" constructor:(@toaster, @cli, @config)-> @vendors = @config.vendors @bare = @config.bare @packaging = @config.packaging @expose = @config.expose @minify = @config.minify @exclude = [].concat( @config.exclude ) @httpfolder = @config.httpfolder @release = @config.release @debug = @config.debug @init() @watch() if @cli.argv.w init:()-> # initializes buffer array to keep all tracked files @files = @config.files for folder in @config.src_folders # search for all *.coffee files inside src folder result = FsUtil.find folder.path, /.coffee/ # folder path and alias fpath = folder.path falias = folder.alias # collects every files into Script instances for file in result include = true for item in @exclude include &= !(new RegExp( item ).test file) if include s = new Script @, fpath, file, falias, @cli @files.push s build:( header_code = "", footer_code = "" )=> # namespaces namespaces = @build_namespaces() # prepare vendors vendors = @merge_vendors() # prepare release contents contents = [] contents.push vendors if vendors isnt "" contents.push namespaces if @packaging contents.push header_code if header_code isnt "" contents.push @compile() contents.push footer_code if header_code isnt "" contents = contents.join '\n' # uglifying if @minify ast = uglify_parser.parse contents ast = uglify.ast_mangle ast ast = uglify.ast_squeeze ast contents = uglify.gen_code ast # write release file fs.writeFileSync @release, contents # notify user through cli now = ("#{new Date}".match /[0-9]{2}\:[0-9]{2}\:[0-9]{2}/)[0] log "[#{now}] #{'Compiled'.bold} #{@release}".green # compiling for debug if @cli.argv.d && @debug? files = @compile_for_debug() # saving boot loader for f, i in files include = "#{@httpfolder}/toaster/#{f}" tmpl = @_include_tmpl.replace "%SRC%", include files[i] = tmpl # prepare boot loader contents contents = [] contents.push vendors if vendors isnt "" contents.push namespaces if @packaging contents.push header_code if header_code isnt "" contents.push (files.join "\n") contents.push footer_code if header_code isnt "" contents = contents.join '\n' # write boot-loader file fs.writeFileSync @debug, contents # notify user through cli log "[#{now}] #{'Compiled'.bold} #{@debug}".green # Creates a NS holder for all folders build_namespaces:()-> tree = {} for folder in @config.src_folders t = (tree[folder.alias] = {}) if folder.alias? @build_ns_tree t || tree, folder.path buffer = "" for name, scope of tree buffer += "var #{name} = " buffer += "#{@expose}.#{name} = " if @expose? buffer += (JSON.stringify scope, null, '').replace /\"/g, "'" buffer += ";\n" return buffer # Walk some folderpath and lists all its subfolders build_ns_tree:( tree, folderpath )-> folders = FsUtil.ls_folders folderpath for folder in folders @build_ns_tree (tree[folder.match /[^\/]+$/m] = {}), folder watch:()-> # loops through all source folders for src in @config.src_folders # and watch them entirely FsUtil.watch_folder src.path, /.coffee$/, FnUtil.proxy (src, info)=> # skip all watching notifications return if info.action == "watching" # skipe all folder creation return if info.type == "folder" and info.action == "created" # folder path and alias ipath = info.path fpath = src.path falias = src.alias || "" # check if it's from some excluded folder include = true include &= !(new RegExp( item ).test ipath) for item in @exclude return unless include # Titleize the type for use in the log messages bellow type = StringUtil.titleize info.type # relative filepath relative_path = info.path.replace "#{fpath}/", "#{falias}/" if relative_path.substr( 0, 1 ) == "/" relative_path = relative_path.substr 1 # date for CLI notifications now = ("#{new Date}".match /[0-9]{2}\:[0-9]{2}\:[0-9]{2}/)[0] # switch over created, deleted, updated and watching switch info.action # when a new file is created when "created" # initiate file and adds it to the array if info.type == "file" # toaster/core/script s = new Script @, fpath, ipath, falias, @cli @files.push s # cli msg msg = "#{('New ' + info.type + ' created').bold}" log "[#{now}] #{msg} #{info.path}".green # when a file is deleted when "deleted" # removes files from array file = ArrayUtil.find @files, relative_path, "filepath" return if file is null @files.splice file.index, 1 # cli msg msg = "#{(type + ' deleted, stop watching').bold}" log "[#{now}] #{msg} #{info.path}".red # when a file is updated when "updated" # updates file information file = ArrayUtil.find @files, relative_path, "filepath" file.item.getinfo() # cli msg msg = "#{(type + ' changed').bold}" log "[#{now}] #{msg} #{info.path}".cyan # when a file starts being watched # when "watching" # msg = "#{('Watching ' + info.type + ':').bold.cyan}" # log "#{msg} #{info.path.cyan}" if @toaster.before_build is null or @toaster.before_build() # rebuilds modules @build() after_watch?() , src compile:()-> # validating syntax for file in @files try cs.compile file.raw # if there's some error catch err # catches and shows it, and abort the compilation msg = err.message.replace '"', '\\"' msg = "#{msg.white} at file: " + "#{file.filepath}".bold.red error msg return null # otherwise move ahead, and expands all dependencies wild-cards file.expand_dependencies() for file in @files # reordering files @reorder() # merging everything output = (file.raw for file in @files).join "\n" # compiling output = cs.compile output, {bare: @bare} compile_for_debug:()-> release_path = @debug.split("/").slice(0, -1).join "/" release_path += "/toaster" # cleaning previous/existent structure FsUtil.rmdir_rf release_path if fs.existsSync release_path # creating new structre from scratch FsUtil.mkdir_p release_path # initializing empty array of filepaths files = [] # loop through all ordered files for file, index in @files # computing releative filepath (replacing .coffee by .js) relative_path = file.filepath.replace ".coffee", ".js" # computing absolute filepath absolute_path = "#{release_path}/#{relative_path}" # extracts its folder path folder_path = absolute_path.split('/').slice(0,-1).join "/" # create container folder if it doesnt exist yet FsUtil.mkdir_p folder_path if !fs.existsSync folder_path # writing file try fs.writeFileSync absolute_path, cs.compile file.raw, {bare:@bare} catch err ## dont show nothing because the error was alreary shown ## in the compile rotine above # # msg = err.message.replace '"', '\\"' # msg = "#{msg.white} at file: " + "#{file.filepath}".bold.red # error msg continue # adds to the files buffer files.push relative_path # returns all filepaths return files missing = {} reorder: (cycling = false) -> # log "Module.reorder" # if cycling is true or @missing is null, initializes empty array # for holding missing dependencies # # cycling means the redorder method is being called recursively, # no other methods call it with cycling = true @missing = {} if cycling is false # looping through all files for file, i in @files # if theres no dependencies, go to next file continue if !file.dependencies.length && !file.baseclasses.length # otherwise loop thourgh all file dependencies for filepath, index in file.dependencies # search for dependency dependency = ArrayUtil.find @files, filepath, "filepath" dependency_index = dependency.index if dependency? # continue if the dependency was already initialized continue if dependency_index < i && dependency? # if it's found if dependency? # if there's some circular dependency loop if ArrayUtil.has dependency.item.dependencies, file.filepath # remove it from the dependencies file.dependencies.splice index, 1 # then prints a warning msg and continue warn "Circular dependency found between ".yellow + filepath.grey.bold + " and ".yellow + file.filepath.grey.bold continue # otherwise if no circular dependency is found, reorder # the specific dependency and run reorder recursively # until everything is beautiful else @files.splice index, 0, dependency.item @files.splice dependency.index + 1, 1 @reorder true break # otherwise if the dependency is not found else if @missing[filepath] != true # then add it to the @missing hash (so it will be ignored # until reordering finishes) @missing[filepath] = true # move it to the end of the dependencies array (avoiding # it from being touched again) file.dependencies.push filepath file.dependencies.splice index, 1 # ..and finally prints a warning msg warn "#{'Dependency'.yellow} #{filepath.bold.grey} " + "#{'not found for file'.yellow} " + file.filepath.grey.bold # validate if all base classes was properly imported file_index = ArrayUtil.find @files, file.filepath, "filepath" file_index = file_index.index for bc in file.baseclasses found = ArrayUtil.find @files, bc, "classname" not_found = (found == null) || (found.index > file_index) if not_found && !@missing[bc] @missing[bc] = true warn "Base class ".yellow + "#{bc} ".bold.grey + "not found for class ".yellow + "#{file.classname} ".bold.grey + "in file ".yellow + file.filepath.bold.grey merge_vendors:()=> buffer = [] for vendor in @vendors if fs.existsSync vendor buffer.push fs.readFileSync vendor, 'utf-8' else warn "Vendor not found at ".white + vendor.yellow.bold return buffer.join "\n"