#!/usr/bin/env ruby # # Copyright (c) 2017-2020 Apple Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # THE POSSIBILITY OF SUCH DAMAGE. require "fileutils" require 'erb' require 'optparse' require 'yaml' options = { :additionalSettings => nil, :basePreferences => nil, :debugPreferences => nil, :experimentalPreferences => nil, :internalPreferences => nil, :outputDirectory => nil, :templates => [] } optparse = OptionParser.new do |opts| opts.banner = "Usage: #{File.basename($0)} --input file" opts.separator "" opts.on("--base input", "file to generate settings from") { |basePreferences| options[:basePreferences] = basePreferences } opts.on("--debug input", "file to generate debug settings from") { |debugPreferences| options[:debugPreferences] = debugPreferences } opts.on("--experimental input", "file to generate experimental settings from") { |experimentalPreferences| options[:experimentalPreferences] = experimentalPreferences } opts.on("--internal input", "file to generate internal settings from") { |internalPreferences| options[:internalPreferences] = internalPreferences } opts.on("--additionalSettings input", "file to generate settings from") { |additionalSettings| options[:additionalSettings] = additionalSettings } opts.on("--outputDir output", "directory to generate file in") { |output| options[:outputDirectory] = output } opts.on("--template input", "template to use for generation (may be specified multiple times)") { |template| options[:templates] << template } end optparse.parse! if !options[:additionalSettings] || !options[:basePreferences] || !options[:debugPreferences] || !options[:experimentalPreferences] || !options[:internalPreferences] puts optparse exit -1 end if !options[:outputDirectory] options[:outputDirectory] = Dir.getwd end FileUtils.mkdir_p(options[:outputDirectory]) parsedSettings = begin YAML.load_file(options[:additionalSettings]) rescue ArgumentError => e puts "Could not parse input file: #{e.message}" exit(-1) end def load(path) parsed = begin YAML.load_file(path) rescue ArgumentError => e puts "ERROR: Could not parse input file: #{e.message}" exit(-1) end previousName = nil parsed.keys.each do |name| if previousName != nil and previousName > name puts "ERROR: Input file #{path} is not sorted. First out of order name found is '#{name}'." exit(-1) end previousName = name end parsed end parsedPreferences = {} parsedPreferences.merge!(load(options[:basePreferences])) parsedPreferences.merge!(load(options[:debugPreferences])) parsedPreferences.merge!(load(options[:experimentalPreferences])) parsedPreferences.merge!(load(options[:internalPreferences])) class Setting attr_accessor :name attr_accessor :options attr_accessor :type attr_accessor :defaultValues attr_accessor :excludeFromInternalSettings attr_accessor :condition attr_accessor :onChange attr_accessor :getter attr_accessor :inspectorOverride attr_accessor :customImplementation def initialize(name, options) @name = normalizeNameForWebCore(name, options) @options = options @type = options["refinedType"] || options["type"] @defaultValues = options["defaultValue"]["WebCore"] @excludeFromInternalSettings = options["webcoreExcludeFromInternalSettings"] || false @condition = options["condition"] @onChange = options["webcoreOnChange"] @getter = options["webcoreGetter"] @inspectorOverride = options["inspectorOverride"] @customImplementation = options["webcoreImplementation"] == "custom" end def normalizeNameForWebCore(name, options) if options["webcoreName"] options["webcoreName"] elsif name.start_with?("VP") name[0..1].downcase + name[2..name.length] elsif name.start_with?("CSSOM", "HTTPS") name elsif name.start_with?("CSS", "XSS", "FTP", "DOM", "DNS", "PDF", "ICE", "HDR") name[0..2].downcase + name[3..name.length] elsif name.start_with?("HTTP", "HTML") name[0..3].downcase + name[4..name.length] else name[0].downcase + name[1..name.length] end end def valueType? @type != "String" && @type != "URL" end def idlType # FIXME: Add support for more types including enum types. if @type == "uint32_t" "unsigned long" elsif @type == "double" "double" elsif @type == "String" "DOMString" elsif @type == "bool" "boolean" else return nil end end def parameterType if valueType? @type else "const #{@type}&" end end def hasComplexSetter? @onChange != nil end def hasComplexGetter? hasInspectorOverride? end def setterFunctionName if @name.start_with?("html") "set" + @name[0..3].upcase + @name[4..@name.length] elsif @name.start_with?("css", "xss", "ftp", "dom", "dns", "ice", "hdr") "set" + @name[0..2].upcase + @name[3..@name.length] elsif @name.start_with?("vp") "set" + @name[0..1].upcase + @name[2..@name.length] else "set" + @name[0].upcase + @name[1..@name.length] end end def getterFunctionName @getter || @name end def hasInspectorOverride? @inspectorOverride == true end end class Conditional attr_accessor :condition attr_accessor :settings attr_accessor :settingsNeedingImplementation attr_accessor :boolSettings attr_accessor :boolSettingsNeedingImplementation attr_accessor :nonBoolSettings attr_accessor :nonBoolSettingsNeedingImplementation attr_accessor :settingsWithComplexGetters attr_accessor :settingsWithComplexGettersNeedingImplementation attr_accessor :settingsWithComplexSetters attr_accessor :settingsWithComplexSettersNeedingImplementation def initialize(condition, settings) @condition = condition @settings = settings @settingsNeedingImplementation = @settings.reject { |setting| setting.customImplementation } @boolSettings = @settings.select { |setting| setting.type == "bool" } @boolSettingsNeedingImplementation = @boolSettings.reject { |setting| setting.customImplementation } @nonBoolSettings = @settings.reject { |setting| setting.type == "bool" } @nonBoolSettingsNeedingImplementation = @nonBoolSettings.reject { |setting| setting.customImplementation } @settingsWithComplexGetters = @settings.select { |setting| setting.hasComplexGetter? } @settingsWithComplexGettersNeedingImplementation = @settingsWithComplexGetters.reject { |setting| setting.customImplementation } @settingsWithComplexSetters = @settings.select { |setting| setting.hasComplexSetter? } @settingsWithComplexSettersNeedingImplementation = @settingsWithComplexSetters.reject { |setting| setting.customImplementation } end end class SettingSet attr_accessor :settings attr_accessor :inspectorOverrideSettings attr_accessor :conditions def initialize(settings) @settings = settings @settings.sort! { |x, y| x.name <=> y.name } @inspectorOverrideSettings = @settings.select { |setting| setting.hasInspectorOverride? } @conditions = [] conditionsMap = {} @settings.select { |setting| setting.condition }.each do |setting| if !conditionsMap[setting.condition] conditionsMap[setting.condition] = [] end conditionsMap[setting.condition] << setting end conditionsMap.each do |key, value| @conditions << Conditional.new(key, value) end @conditions.sort! { |x, y| x.condition <=> y.condition } # We also add the unconditional settings as the first element in the conditions array. @conditions.unshift(Conditional.new(nil, @settings.reject { |setting| setting.condition })) end end class Settings attr_accessor :allSettingsSet def initialize(parsedSettingsFromWebCore, parsedSettingsFromWebPreferences) settings = [] parsedSettingsFromWebPreferences.each do |name, options| # An empty "webcoreBinding" entry indicates this preference uses the default, which is bound to Settings. if !options["webcoreBinding"] settings << Setting.new(name, options) end end parsedSettingsFromWebCore.each do |name, options| settings << Setting.new(name, options) end @allSettingsSet = SettingSet.new(settings) end def renderTemplate(template, outputDirectory) file = File.join(outputDirectory, File.basename(template, ".erb")) if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+ output = ERB.new(File.read(template), trim_mode:"-").result(binding) else output = ERB.new(File.read(template), 0, "-").result(binding) end File.open(file, "w+") do |f| f.write(output) end end end settings = Settings.new(parsedSettings, parsedPreferences) options[:templates].each do |template| settings.renderTemplate(template, options[:outputDirectory]) end