522ee3c506cfd4ae581f03b7dd20cb672fe173b8
[WebKit-https.git] / Source / WTF / Scripts / GeneratePreferences.rb
1 #!/usr/bin/env ruby
2 #
3 # Copyright (c) 2017, 2020 Apple Inc. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
7 # are met:
8 # 1. Redistributions of source code must retain the above copyright
9 #    notice, this list of conditions and the following disclaimer.
10 # 2. Redistributions in binary form must reproduce the above copyright
11 #    notice, this list of conditions and the following disclaimer in the
12 #    documentation and/or other materials provided with the distribution.
13 #
14 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 # THE POSSIBILITY OF SUCH DAMAGE.
25
26 require "fileutils"
27 require 'erb'
28 require 'optparse'
29 require 'yaml'
30
31 options = {
32   :frontend => nil,
33   :basePreferences => nil,
34   :debugPreferences => nil,
35   :experimentalPreferences => nil,
36   :internalPreferences => nil,
37   :outputDirectory => nil,
38   :templates => []
39 }
40 optparse = OptionParser.new do |opts|
41   opts.banner = "Usage: #{File.basename($0)} --frontend <frontend> --base <base> --debug <debug> --experimental <experimental> --internal <internal> --template file"
42
43   opts.separator ""
44
45   opts.on("--frontend input", "frontend to generate preferences for (WebKit, WebKitLegacy)") { |frontend| options[:frontend] = frontend }
46   opts.on("--base input", "file to generate preferences from") { |basePreferences| options[:basePreferences] = basePreferences }
47   opts.on("--debug input", "file to generate debug preferences from") { |debugPreferences| options[:debugPreferences] = debugPreferences }
48   opts.on("--experimental input", "file to generate experimental preferences from") { |experimentalPreferences| options[:experimentalPreferences] = experimentalPreferences }
49   opts.on("--internal input", "file to generate internal preferences from") { |internalPreferences| options[:internalPreferences] = internalPreferences }
50   opts.on("--template input", "template to use for generation (may be specified multiple times)") { |template| options[:templates] << template }
51   opts.on("--outputDir output", "directory to generate file in") { |outputDir| options[:outputDirectory] = outputDir }
52 end
53
54 optparse.parse!
55
56 if !options[:frontend] || !options[:basePreferences] || !options[:debugPreferences] || !options[:experimentalPreferences] || !options[:internalPreferences]
57   puts optparse
58   exit -1
59 end
60
61 if !options[:outputDirectory]
62   options[:outputDirectory] = Dir.getwd
63 end
64
65 FileUtils.mkdir_p(options[:outputDirectory])
66
67 def load(path)
68   parsed = begin
69     YAML.load_file(path)
70   rescue ArgumentError => e
71     puts "ERROR: Could not parse input file: #{e.message}"
72     exit(-1)
73   end
74   if parsed
75     previousName = nil
76     parsed.keys.each do |name|
77       if previousName != nil and previousName > name
78         puts "ERROR: Input file #{path} is not sorted. First out of order name found is '#{name}'."
79         exit(-1)
80       end
81       previousName = name
82     end
83   end
84   parsed
85 end
86
87 parsedBasePreferences = load(options[:basePreferences])
88 parsedDebugPreferences = load(options[:debugPreferences])
89 parsedExperimentalPreferences = load(options[:experimentalPreferences])
90 parsedInternalPreferences = load(options[:internalPreferences])
91
92
93 class Preference
94   attr_accessor :name
95   attr_accessor :opts
96   attr_accessor :type
97   attr_accessor :humanReadableName
98   attr_accessor :humanReadableDescription
99   attr_accessor :webcoreBinding
100   attr_accessor :condition
101   attr_accessor :hidden
102   attr_accessor :defaultValues
103   attr_accessor :exposed
104
105   def initialize(name, opts, frontend)
106     @name = name
107     @opts = opts
108     @type = opts["type"]
109     @humanReadableName = '"' + (opts["humanReadableName"] || "") + '"'
110     @humanReadableDescription = '"' + (opts["humanReadableDescription"] || "") + '"'
111     @getter = opts["getter"]
112     @webcoreBinding = opts["webcoreBinding"]
113     @webcoreName = opts["webcoreName"]
114     @condition = opts["condition"]
115     @hidden = opts["hidden"] || false
116     @defaultValues = opts["defaultValue"][frontend]
117     @exposed = !opts["exposed"] || opts["exposed"].include?(frontend)
118   end
119
120   def nameLower
121     if @getter
122       @getter
123     elsif @name.start_with?("VP")
124       @name[0..1].downcase + @name[2..@name.length]
125     elsif @name.start_with?("CSS", "DOM", "DNS", "FTP", "ICE", "IPC", "PDF", "XSS")
126       @name[0..2].downcase + @name[3..@name.length]
127     elsif @name.start_with?("HTTP")
128       @name[0..3].downcase + @name[4..@name.length]
129     else
130       @name[0].downcase + @name[1..@name.length]
131     end
132   end
133
134   def webcoreNameUpper
135     if @webcoreName
136       @webcoreName[0].upcase + @webcoreName[1..@webcoreName.length]
137     else
138       @name
139     end
140   end
141
142   def typeUpper
143     if @type == "uint32_t"
144       "UInt32"
145     else
146       @type.capitalize
147     end
148   end
149
150
151   # WebKitLegacy specific helpers.
152
153   def preferenceKey
154     if @opts["webKitLegacyPreferenceKey"]
155       @opts["webKitLegacyPreferenceKey"]
156     else
157       "WebKit#{@name}"
158     end
159   end
160   
161   def preferenceAccessor
162     case @type
163     when "bool"
164       "_boolValueForKey"
165     when "uint32_t"
166       "_integerValueForKey"
167     when "double"
168      "_floatValueForKey"
169     when "String"
170       "_stringValueForKey"
171     else
172       raise "Unknown type: #{@type}"
173     end
174   end
175 end
176
177 class Preferences
178   attr_accessor :preferences
179
180   def initialize(parsedBasePreferences, parsedDebugPreferences, parsedExperimentalPreferences, parsedInternalPreferences, frontend)
181     @frontend = frontend
182
183     @preferences = []
184     @preferencesNotDebug = initializeParsedPreferences(parsedBasePreferences)
185     @preferencesDebug = initializeParsedPreferences(parsedDebugPreferences)
186     @experimentalFeatures = initializeParsedPreferences(parsedExperimentalPreferences)
187     @internalFeatures = initializeParsedPreferences(parsedInternalPreferences)
188
189     @preferences.sort! { |x, y| x.name <=> y.name }
190     @preferencesNotDebug.sort! { |x, y| x.name <=> y.name }
191     @preferencesDebug.sort! { |x, y| x.name <=> y.name }
192     @experimentalFeatures.sort! { |x, y| x.name <=> y.name }.sort! { |x, y| x.humanReadableName <=> y.humanReadableName }
193     @internalFeatures.sort! { |x, y| x.name <=> y.name }.sort! { |x, y| x.humanReadableName <=> y.humanReadableName }
194
195     @exposedPreferences = @preferences.select { |p| p.exposed }
196     @exposedPreferencesNotDebug = @preferencesNotDebug.select { |p| p.exposed }
197     @exposedPreferencesDebug = @preferencesDebug.select { |p| p.exposed }
198     @exposedExperimentalFeatures = @experimentalFeatures.select { |p| p.exposed }
199     @exposedInternalFeatures = @internalFeatures.select { |p| p.exposed }
200
201     @preferencesBoundToSetting = @preferences.select { |p| !p.webcoreBinding }
202     @preferencesBoundToDeprecatedGlobalSettings = @preferences.select { |p| p.webcoreBinding == "DeprecatedGlobalSettings" }
203     @preferencesBoundToRuntimeEnabledFeatures = @preferences.select { |p| p.webcoreBinding == "RuntimeEnabledFeatures" }
204
205     @warning = "THIS FILE WAS AUTOMATICALLY GENERATED, DO NOT EDIT."
206   end
207
208   def initializeParsedPreferences(parsedPreferences)
209     result = []
210     if parsedPreferences
211       parsedPreferences.each do |name, options|
212         if !options["webcoreBinding"] && options["defaultValue"].size != 3
213           raise "ERROR: Preferences bound to WebCore::Settings must have default values for all frontends: #{name}"
214         end
215         if options["defaultValue"].include?(@frontend)
216           preference = Preference.new(name, options, @frontend)
217           @preferences << preference
218           result << preference
219         end
220       end
221     end
222     result
223   end
224
225   def renderTemplate(templateFile, outputDirectory)
226     resultFile = File.join(outputDirectory, File.basename(templateFile, ".erb"))
227     tempResultFile = resultFile + ".tmp"
228
229     output = ERB.new(File.read(templateFile), 0, "-").result(binding)
230     File.open(tempResultFile, "w+") do |f|
231       f.write(output)
232     end
233     if (!File.exist?(resultFile) || IO::read(resultFile) != IO::read(tempResultFile))
234       FileUtils.move(tempResultFile, resultFile)
235     else
236       FileUtils.remove_file(tempResultFile)
237       FileUtils.uptodate?(resultFile, [templateFile]) or FileUtils.touch(resultFile)
238     end
239   end
240 end
241
242 preferences = Preferences.new(parsedBasePreferences, parsedDebugPreferences, parsedExperimentalPreferences, parsedInternalPreferences, options[:frontend])
243
244 options[:templates].each do |template|
245   preferences.renderTemplate(template, options[:outputDirectory])
246 end