# frozen_string_literal: true# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com># # Permission is hereby granted, free of charge, to any person obtaining a copy# of this software and associated documentation files (the "Software"), to deal# in the Software without restriction, including without limitation the rights# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell# copies of the Software, and to permit persons to whom the Software is# furnished to do so, subject to the following conditions:# # The above copyright notice and this permission notice shall be included in# all copies or substantial portions of the Software.# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN# THE SOFTWARE.require_relative'base'moduleBake# The default file name for the top level bakefile.BAKEFILE="bake.rb"# Represents a context of task execution, containing all relevant state.classContext# Search upwards from the specified path for a {BAKEFILE}.# If path points to a file, assume it's a `bake.rb` file. Otherwise, recursively search up the directory tree starting from `path` to find the specified bakefile.# @returns [String | Nil] The path to the bakefile if it could be found.defself.bakefile_path(path,bakefile: BAKEFILE)ifFile.file?(path)returnpathendcurrent=pathwhilecurrentbakefile_path=File.join(current,BAKEFILE)ifFile.exist?(bakefile_path)returnbakefile_pathendparent=File.dirname(current)ifcurrent==parentbreakelsecurrent=parentendendreturnnilend# Load a context from the specified path.# @path [String] A file-system path.defself.load(path=Dir.pwd)ifbakefile_path=self.bakefile_path(path)scope=Scope.load(bakefile_path)working_directory=File.dirname(bakefile_path)loaders=Loaders.default(working_directory)elsescope=nilworking_directory=pathloaders=Loaders.default(working_directory)endreturnself.new(loaders,scope,working_directory)end# Initialize the context with the specified loaders.# @parameter loaders [Loaders]definitialize(loaders,scope=nil,root=nil)@loaders=loaders@stack=[]@instances=Hash.newdo|hash,key|hash[key]=instance_for(key)end@scope=scope@root=rootif@scopebase=Base.derivebase.prepend(@scope)@instances[[]]=base.new(self)end@recipes=Hash.newdo|hash,key|hash[key]=recipe_for(key)endend# The loaders which will be used to resolve recipes in this context.attr:loaders# The scope for the root {BAKEFILE}.attr:scope# The root path of this context.# @returns [String | Nil]attr:root# Invoke recipes on the context using command line arguments.# @parameter commands [Array(String)]defcall(*commands)last_result=nilwhilecommand=commands.shiftifrecipe=@recipes[command]arguments,options=recipe.prepare(commands)last_result=recipe.call(*arguments,**options)elseraiseArgumentError,"Could not find recipe for #{command}!"endendreturnlast_resultend# Lookup a recipe for the given command name.# @parameter command [String] The command name, e.g. `bundler:release`.deflookup(command)@recipes[command]enddefto_sif@root"#{self.class}#{File.basename(@root)}"elseself.class.nameendenddefinspect"\#<#{self.class}#{@root}>"endprivatedefrecipe_for(command)path=command.split(":")ifinstance=@instances[path]andinstance.respond_to?(path.last)returninstance.recipe_for(path.last)else*path,name=*pathifinstance=@instances[path]andinstance.respond_to?(name)returninstance.recipe_for(name)endendreturnnilenddefinstance_for(path)ifbase=base_for(path)returnbase.new(self)endend# @parameter path [Array(String)] the path for the scope.defbase_for(path)base=nil@loaders.eachdo|loader|ifscope=loader.scope_for(path)base||=Base.derive(path)base.prepend(scope)endendreturnbaseendendend