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'# 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.debugging@had_errors=falseself.class.shared_instance(options["verbose"])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 --debug 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)@_shared_instance||=::Middleman::Application.server.instdoset:environment,:buildset:logging,verboseendend# 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||=beginmock=::Rack::MockSession.new(shared_server.to_rack_app)sess=::Rack::Test::Session.new(mock)response=sess.get("__middleman__")sessendend# 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:source# 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))super(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=[]Find.find(@destination)do|path|nextifpath.match(/\/\./)&&!path.match(/\.htaccess/)unlesspath==destination@cleaning_queue<<Pathname.new(path)endendifFile.exist?(@destination)end# 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 spritesputs"== Prerendering CSS"if@app.logging?@app.sitemap.resources.selectdo|resource|resource.ext==".css"end.eachdo|resource|Middleman::Cli::Build.shared_rack.get(URI.escape(resource.destination_path))endputs"== Checking for Compass sprites"if@app.logging?# Double-check for compass sprites@app.files.find_new_files(Pathname.new(@app.source_dir)+@app.images_dir)# 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 filesputs"== Building files"if@app.logging?resources=@app.sitemap.resources.sortdo|a,b|a_idx=sort_order.index(a.ext)||100b_idx=sort_order.index(b.ext)||100a_idx<=>b_idxend# 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)@cleaning_queue.delete(Pathname.new(output_path).realpath)ifcleaning?endendend# Alias "b" to "build"Base.map({"b"=>"build"})end# Quiet down create fileclass::Thor::Actions::CreateFiledefon_conflict_behavior(&block)say_status:create,:greenblock.callunlesspretend?endend