require"middleman-core"# CLI ModulemoduleMiddleman::Cli# The CLI Build classclassBuild<ThorincludeThor::Actionsattr_reader:debuggingcheck_unknown_options!namespace:builddesc"build [options]","Builds the static site for deployment"method_option:clean,:type=>:boolean,:aliases=>"-c",:default=>false,:desc=>'Removes orphaned files or directories from build'method_option:glob,:type=>:string,:aliases=>"-g",:default=>nil,:desc=>'Build a subset of the project'method_option:verbose,:type=>:boolean,:default=>false,:desc=>'Print debug messages'method_option:instrument,:type=>:string,:default=>false,:desc=>'Print instrument messages'method_option:profile,:type=>:boolean,:default=>false,:desc=>'Generate profiling report for the build'# Core build Thor command# @return [void]defbuildif!ENV["MM_ROOT"]raiseThor::Error,"Error: Could not find a Middleman project config, perhaps you are in the wrong folder?"end# Use Rack::Test for inspecting a running server for outputrequire"rack"require"rack/test"require'find'@debugging=Middleman::Cli::Base.respond_to?(:debugging)&&Middleman::Cli::Base.debugging@had_errors=falseself.class.shared_instance(options["verbose"],options["instrument"])self.class.shared_rackopts={}opts[:glob]=options["glob"]ifoptions.has_key?("glob")opts[:clean]=options["clean"]ifoptions.has_key?("clean")actionGlobAction.new(self,opts)if@had_errors&&!@debuggingself.shell.say"There were errors during this build, re-run with --verbose to see the full exception."endexit(1)if@had_errorsself.class.shared_instance.run_hook:after_build,selfend# Static methodsclass<<selfdefexit_on_failure?trueend# Middleman::Application singleton## @return [Middleman::Application]defshared_instance(verbose=false,instrument=false)@_shared_instance||=::Middleman::Application.server.instdoset:environment,:buildlogger(verbose?0:1,instrument)endend# Middleman::Application class singleton## @return [Middleman::Application]defshared_server@_shared_server||=shared_instance.classend# Rack::Test::Session singleton## @return [Rack::Test::Session]defshared_rack@_shared_rack||=::Rack::Test::Session.new(shared_server.to_rack_app)end# Set the root path to the Middleman::Application's rootdefsource_rootshared_instance.rootendendno_tasks{# Render a resource to a file.## @param [Middleman::Sitemap::Resource] resource# @return [String] The full path of the file that was writtendefrender_to_file(resource)build_dir=self.class.shared_instance.build_diroutput_file=File.join(build_dir,resource.destination_path)beginresponse=self.class.shared_rack.get(URI.escape(resource.destination_path))ifresponse.status==200create_file(output_file,response.body)elsehandle_error(output_file,response.body)endrescue=>ehandle_error(output_file,"#{e}\n#{e.backtrace.join("\n")}",e)endoutput_fileenddefhandle_error(file_name,response,e=Thor::Error.new(response))@had_errors=truesay_status:error,file_name,:redifself.debuggingraiseeexit(1)elsifoptions["verbose"]self.shell.error(response)endend}end# A Thor Action, modular code, which does the majority of the work.classGlobAction<::Thor::Actions::EmptyDirectoryattr_reader:sourceattr_reader:logger# Setup the action## @param [Middleman::Cli::Build] base# @param [Hash] configdefinitialize(base,config={})@app=base.class.shared_instancesource=@app.source@destination=@app.build_dir@source=File.expand_path(base.find_in_source_paths(source.to_s))@logger=Middleman::Cli::Build.shared_instance.loggersuper(base,@destination,config)end# Execute the action# @return [void]definvoke!queue_current_pathsifcleaning?execute!clean!ifcleaning?endprotected# Remove files which were not built in this cycle# @return [void]defclean!files=@cleaning_queue.select{|q|q.file?}directories=@cleaning_queue.select{|q|q.directory?}files.eachdo|f|base.remove_filef,:force=>trueenddirectories=directories.sort_by{|d|d.to_s.length}.reverse!directories.eachdo|d|base.remove_filed,:force=>trueifdirectory_empty?dendend# Whether we should clean the build# @return [Boolean]defcleaning?@config.has_key?(:clean)&&@config[:clean]end# Whether the given directory is empty# @param [String] directory# @return [Boolean]defdirectory_empty?(directory)directory.children.empty?end# Get a list of all the paths in the destination folder and save them# for comparison against the files we build in this cycle# @return [void]defqueue_current_paths@cleaning_queue=[]returnunlessFile.exist?(@destination)paths=::Middleman::Util.all_files_under(@destination)@cleaning_queue+=paths.selectdo|path|!path.to_s.match(/\/\./)||path.to_s.match(/\.htaccess/)endend# Actually build the app# @return [void]defexecute!# Sort order, images, fonts, js/css and finally everything else.sort_order=%w(.png .jpeg .jpg .gif .bmp .svg .svgz .ico .woff .otf .ttf .eot .js .css)# Pre-request CSS to give Compass a chance to build spriteslogger.debug"== Prerendering CSS"@app.sitemap.resources.selectdo|resource|resource.ext==".css"end.eachdo|resource|Middleman::Cli::Build.shared_rack.get(URI.escape(resource.destination_path))endlogger.debug"== Checking for Compass sprites"# Double-check for compass sprites@app.files.find_new_files((Pathname(@app.source_dir)+@app.images_dir).relative_path_from(@app.root_path))@app.sitemap.ensure_resource_list_updated!# Sort paths to be built by the above order. This is primarily so Compass can# find files in the build folder when it needs to generate sprites for the# css fileslogger.debug"== Building files"resources=@app.sitemap.resources.sort_bydo|r|sort_order.index(r.ext)||100end# Loop over all the paths and build them.resources.eachdo|resource|nextif@config[:glob]&&!File.fnmatch(@config[:glob],resource.destination_path)output_path=base.render_to_file(resource)ifcleaning?pn=Pathname(output_path)@cleaning_queue.delete(pn.realpath)ifpn.exist?endend::Middleman::Profiling.report("build")endend# Alias "b" to "build"Base.map({"b"=>"build"})end# Quiet down create fileclass::Thor::Actions::CreateFiledefon_conflict_behavior(&block)ifidentical?say_status:identical,:blueelsesay_status:update,:yellowblock.callunlesspretend?endendend