blob: 5ff4b58fd72a20378312226c4a694bd59c91f1cf [file] [log] [blame]
#!/usr/bin/env ruby
#
# Copyright (c) 2017-2023 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 = {
:outputDirectory => nil,
:templates => [],
:settingsFiles => []
}
optparse = OptionParser.new do |opts|
opts.banner = "Usage: #{File.basename($0)} [--outputDir <output>] --template <input> [--template <file>...] <settings> [<settings>...]"
opts.separator ""
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!
options[:settingsFiles] = ARGV.shift(ARGV.size)
if options[:settingsFiles].empty?
puts optparse
exit 1
end
if !options[:outputDirectory]
options[:outputDirectory] = Dir.getwd
end
FileUtils.mkdir_p(options[:outputDirectory])
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
class Setting
attr_accessor :name
attr_accessor :options
attr_accessor :type
attr_accessor :status
attr_accessor :category
attr_accessor :defaultValues
attr_accessor :disableInLockdownMode
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"]
@status = options["status"]
@defaultValues = options["defaultValue"]["WebCore"]
@excludeFromInternalSettings = options["webcoreExcludeFromInternalSettings"] || false
@disableInLockdownMode = options["disableInLockdownMode"] || 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?("URL","CSS", "XSS", "FTP", "DOM", "DNS", "PDF", "ICE", "HDR", "RTC")
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?("url", "css", "xss", "ftp", "dom", "dns", "ice", "hdr", "pdf", "rtc")
"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
def stableFeature?
# FIXME: Not all "embedder" settings should be considered stable, only the
# settings that are on by default. Assuming embedder gets split into two
# categories (off-by-default and on-by-default), only the latter should be
# considered stable.
!@status or %w{ embedder internal stable mature }.include? @status
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
attr_accessor :unstableSettings
attr_accessor :unstableGlobalSettings
def initialize(settingsFiles)
settingsByName = {}
globalSettingsByName = {}
settingsFiles.each do |file|
parsedSettings = load(file).each do |name, options|
# An empty "webcoreBinding" entry indicates this preference uses the default, which is bound to Settings.
if !options["webcoreBinding"]
settingsByName[name] = Setting.new(name, options)
elsif options["webcoreBinding"] == "DeprecatedGlobalSettings"
globalSettingsByName[name] = Setting.new(name, options)
end
end
end
@allSettingsSet = SettingSet.new(settingsByName.values)
@unstableFeatures = settingsByName.values.reject(&:stableFeature?)
@unstableGlobalFeatures = globalSettingsByName.values.reject(&:stableFeature?)
end
def renderTemplate(template, outputDirectory)
file = File.join(outputDirectory, File.basename(template, ".erb"))
if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+
erb = ERB.new(File.read(template), trim_mode:"-")
else
erb = ERB.new(File.read(template), 0, "-")
end
erb.filename = template
output = erb.result(binding)
File.open(file, "w+") do |f|
f.write(output)
end
end
end
settings = Settings.new(options[:settingsFiles])
options[:templates].each do |template|
settings.renderTemplate(template, options[:outputDirectory])
end