require "nokogiri" require "pathname" require_relative "string" # The DSLGenerator class generates the DSL methods and files # for use in JMeter performance testing scripts. It uses a JMeter TestPlan (idl.xml) # to generate the methods and files. # @see https://github.com/jlurena/jmeter_perf/wiki/3.-Generating-DSL-Methods class DSLGenerator # Initializes the DSLGenerator. # @param dsl_dir [String] Path to the directory where DSL files will be generated. # @param idl_xml_path [String] Path to the XML file used for generating methods. def initialize(dsl_dir:, idl_xml_path:, document_dsl: true) @document_dsl = document_dsl @dsl_dir = dsl_dir @idl_xml_path = idl_xml_path end # Main method to generate DSL files and methods. # It parses the XML, generates methods, documents, # updates RBS files, and creates individual DSL files. # @return [void] def generate results = parse_xml methods = generate_methods(results) write_methods_to_dsl_md(methods) if @document_dsl update_rbs(methods) if @document_dsl create_dsl_files(results) end private # Parses the XML file specified by the `idl_xml_path` parameter. # @return [Array] Array of XML nodes representing valid elements. def parse_xml idl_xml = File.open(@idl_xml_path) doc = Nokogiri::XML(idl_xml.read.gsub(/\n\s+/, "")) results = [] doc.traverse do |node| if valid_node?(node) results << node end end results end # Checks if a given Nokogiri XML node is valid based on specific criteria. # # A node is considered valid if: # - It is not an instance of Nokogiri::XML::Document. # - It has an attribute named "testclass". # - Its name is not "elementProp". # # @param node [Nokogiri::XML::Node] The XML node to be validated. # @return [Boolean] Returns true if the node is valid, false otherwise. def valid_node?(node) node.class != Nokogiri::XML::Document && node.attributes["testclass"] && node.name != "elementProp" end # Generates methods based on parsed XML results. # @param results [Array] Parsed XML nodes. # @return [Array>] Array of class and method name pairs. def generate_methods(results) results.map do |element| klass = JmeterPerf::Helpers::String.classify(element.attributes["testname"].to_s) method = JmeterPerf::Helpers::String.underscore(klass) [klass, method] end.sort_by(&:first) end # Creates individual DSL files based on parsed XML results. # @param results [Array] Parsed XML nodes. # @return [void] def create_dsl_files(results) FileUtils.rm_rf(@dsl_dir) FileUtils.mkdir_p(@dsl_dir) results.each do |element| klass = JmeterPerf::Helpers::String.classify(element.attributes["testname"].to_s) puts "\tfor Element #{klass}" File.write("#{@dsl_dir}/#{JmeterPerf::Helpers::String.underscore(klass)}.rb", <<~EOC) module JmeterPerf class DSL # DSL method synonymous with the JMeter Element #{klass} # @param [Hash] params Parameters for the #{klass} element (default: `{}`). # @yield block to attach to the #{klass} element # @return [JmeterPerf::#{klass}], a subclass of JmeterPerf::DSL that can be chained with other DSL methods. # @see https://github.com/jlurena/jmeter_perf/wiki/1.-DSL-Documentation##{klass.downcase} def #{JmeterPerf::Helpers::String.underscore(klass)}(params = {}, &) node = #{klass}.new(params) attach_node(node, &) end class #{klass} attr_accessor :doc include JmeterPerf::Helpers::XmlDocumentUpdater def initialize(params = {}) testname = params.is_a?(Array) ? "#{klass}" : (params[:name] || "#{klass}") @doc = Nokogiri::XML(JmeterPerf::Helpers::String.strip_heredoc( <<~EOS #{element.to_xml.gsub(/testname=".+?"/, 'testname="#{testname}"').gsub(/^/, " ")} EOS )) update params update_at_xpath params if params.is_a?(Hash) && params[:update_at_xpath] end end end end EOC end end # Updates the RBS file with method signatures for the DSL methods. # @param methods [Array>] Array of class and method name pairs. # @return [void] def update_rbs(methods) file_path = "sig/jmeter_perf.rbs" replace_regex = /\s*## AUTOGENERATED - DSL methods RBS(.*?)## AUTOGENERATED - DSL methods RBS/m new_content = <<~NEW_CONTENT #{methods.map { |klass, method| " def #{method}: (Hash[Symbol, untyped], &block) -> void" }.join("\n")} NEW_CONTENT current_content = File.read(file_path) new_file_content = current_content.gsub(replace_regex) do "\n ## AUTOGENERATED - DSL methods RBS\n\n#{new_content}\n ## AUTOGENERATED - DSL methods RBS" end File.write(file_path, new_file_content) end # Writes the list of generated methods to a markdown file. # @param methods [Array>] Array of class and method name pairs. # @return [void] def write_methods_to_dsl_md(methods) output = [] output << "# JmeterPerf::DSL methods" methods.each do |klass, method| output << "- #{klass}\n `#{method}`" end File.write("DSL.md", output.join("\n")) end end