lib/bake/recipe.rb
# frozen_string_literal: true # Released under the MIT License. # Copyright, 2020-2024, by Samuel Williams. require_relative "type" require_relative "arguments" require_relative "documentation" module Bake # Structured access to an instance method in a bakefile. class Recipe # Initialize the recipe. # # @parameter instance [Base] The instance this recipe is attached to. # @parameter name [String] The method name. # @parameter method [Method | Nil] The method if already known. def initialize(instance, name, method = nil) @instance = instance @name = name @command = nil @comments = nil @signature = nil @documentation = nil @method = method @arity = nil end # The {Base} instance that this recipe is attached to. attr :instance # The name of this recipe. attr :name # Sort by location in source file. def <=> other (self.source_location || []) <=> (other.source_location || []) end # The method implementation. def method @method ||= @instance.method(@name) end # The source location of this recipe. def source_location self.method.source_location end # The recipe's formal parameters, if any. # @returns [Array | Nil] def parameters parameters = method.parameters unless parameters.empty? return parameters end end # Whether this recipe has optional arguments. # @returns [Boolean] def options? if parameters = self.parameters parameters.any? do |type, name| type == :keyrest || type == :keyreq || type == :key end end end def required_options if parameters = self.parameters parameters.map do |(type, name)| if type == :keyreq name end end.compact end end # The command name for this recipe. def command @command ||= compute_command end def to_s self.command end # The method's arity, the required number of positional arguments. def arity if @arity.nil? @arity = method.parameters.count{|type, name| type == :req} end return @arity end # Process command line arguments into the ordered and optional arguments. # @parameter arguments [Array(String)] The command line arguments # @returns ordered [Array] # @returns options [Hash] def prepare(arguments, last_result = nil) Arguments.extract(self, arguments, input: last_result) end # Call the recipe with the specified arguments and options. # If the recipe does not accept options, they will be ignored. def call(*arguments, **options, &block) if options.any? and self.options? @instance.send(@name, *arguments, **options, &block) else # Ignore options... @instance.send(@name, *arguments, &block) end end # Any comments associated with the source code which defined the method. # @returns [Array(String)] The comment lines. def comments @comments ||= read_comments end # The documentation object which provides structured access to the {comments}. # @returns [Documentation] def documentation @documentation ||= Documentation.new(self.comments) end # The documented type signature of the recipe. # @returns [Array] An array of {Type} instances. def signature @signature ||= read_signature end # @deprecated Use {signature} instead. alias types signature private def parse(name, value, arguments, types) if count = arguments.index(";") value = arguments.shift(count) arguments.shift end if type = types[name] value = type.parse(value) end return value end def compute_command path = @instance.path if path.empty? @name.to_s elsif path.last.to_sym == @name path.join(":") else (path + [@name]).join(":") end end COMMENT = /\A\s*\#\s?(.*?)\Z/ def read_comments unless source_location = self.method&.source_location # Bail early if we don't have a source location (there are some inconsequential cases on JRuby): return [] end file, line_number = source_location lines = File.readlines(file) line_index = line_number - 1 description = [] line_index -= 1 # Extract comment preceeding method: while line = lines[line_index] # \Z matches a trailing newline: if match = line.match(COMMENT) description.unshift(match[1]) else break end line_index -= 1 end return description end def read_signature types = {} self.documentation.parameters do |parameter| types[parameter[:name].to_sym] = Type.parse(parameter[:type]) end return types end end end