require 'js' # 処理中画面の表示/非表示 # @param f [Boolean] def in_progress(&block) @in_progress ||= [] @in_progress.push true JS.document.getElementById('in-progress').style.display = 'block' unless @in_progress.empty? block.call @in_progress.shift JS.document.getElementById('in-progress').style.display = 'none' if @in_progress.empty? end # バージョンファイルを読み終わってから main を呼ぶ def start JS.document.getElementById('in-progress').style.display = 'none' JS.global.fetch('/json/versions.json') {|res| unless res.ok JS.global.alert("error #{res.status}") raise end json = res.json.await @available_versions = json.to_a main } end # 開始 def main @param_cache = {} uri = URI.parse(JS.document.location.href.to_s) q = URI.decode_www_form(uri.query.to_s).to_h @type = q['type'] || uri.path.sub('index.html', '').split('/').compact.reject(&:empty?).last || 'mysqld' list = JS.document.getElementById('menu').querySelectorAll('li a').to_a list.each do |a| a.addEventListener('click') do @type = URI.parse(a.href.to_s).path.split('/')[1] redisplay end end vers = q['vers'].to_s.split(',') diff = q['diff'] == 'true' e_diff = diff_element e_diff.checked = diff e_diff.addEventListener('change'){change_diff} plugin = q['plugin'] == 'true' e_plugin = plugin_element e_plugin.checked = plugin e_plugin.addEventListener('change'){redisplay} create_version_list vers.each{|ver| add_input(ver.strip)} add_input change_diff redisplay end # 差分表示切り替え def change_diff only_diff = diff_element.checked == true JS.document.styleSheets[0].cssRules.to_a.each do |css| if css[:selectorText] == 'tr.same-value' css.style.display = only_diff ? 'none' : nil end end set_location end # バージョンリストの datalist 要素作成 def create_version_list version_list = create_element('datalist', id: 'version_list') @available_versions.each do |v| o = create_element('option', value: v, text: v) version_list.appendChild(o) end JS.document.getElementById('versions').appendChild(version_list) end # HTML要素作成 # @param type [String] 要素名 # @param opts [Hash] 要素の属性 def create_element(type, **opts) e = JS.document.createElement(type) opts.each do |k, v| e.setAttribute(k.to_s, v) end e end # バージョン入力用の input 要素を作成 # @param ver [String] バージョン def add_input(ver=nil) v = create_element('input', name: 'version', class: 'version', type: 'search', list: 'version_list') v.value = ver if ver JS.document.getElementById('versions').appendChild(v) v.addEventListener('keypress') do |e| redisplay if e.key == 'Enter' end v.addEventListener('change') do redisplay end end # バージョン入力用要素一覧を返す # @return [Array] def version_elements JS.document.getElementsByName('version').to_a end # 「Difference Only」要素 # @return [Array] def diff_element JS.document.getElementById('only_difference') end # 「Include plugins」要素 # @return [Array] def plugin_element JS.document.getElementById('include_plugins') end # 再表示 def redisplay in_progress do JS.global[:sleepTime] = 0 next if @in_redisplay @in_redisplay = true set_location JS.document.getElementById('param_table')&.remove @versions = [] version_elements.each_with_index do |v, i| ver = v.value = v.value.to_s.strip if ver.empty? v.remove if i < version_elements.size - 1 next end unless @available_versions.include?(ver) ver = @available_versions.select{_1.start_with?(ver+'.')}.first end unless ver v[:className] = 'invalid-version' next end v[:className] = '' read_params(ver) @versions.push ver end add_input if version_elements.empty? || !version_elements.last.value.to_s.empty? display_table ensure if $tableonly JS.global[:sleepTime] = 2000 else JS.global[:sleepTime] = 300 end @in_redisplay = false end end # バージョン ver のパラメータ JSON ファイルを読み込む # @param ver [String] バージョン def read_params(ver) plugin = include_plugins? return if @param_cache[[@type, ver, plugin]] file = plugin ? 'all.json' : 'base.json' JS.global.fetch("/json/#{ver}/#{@type}/#{file}") {|res| unless res.ok == true JS.global.alert("error #{res.status}") raise "error #{res.status}" end json = res.json.await data = JS.global[:Object].keys(json).to_a.map {|key| [key, json[key].to_s] }.to_h @param_cache[[@type, ver, plugin]] = data } end # HTML 要素を元に URL を設定する def set_location uri = URI.parse(JS.global.location.href.to_s) uri.path = "/#{@type}/" vers = version_elements.to_a.map{it.value.to_s}.reject(&:empty?).join(',') diff = diff_element.checked == true plugin = plugin_element.checked == true q = URI.decode_www_form(uri.query.to_s).to_h q.reject!{|k,| ['type', 'vers', 'diff', 'plugin'].include? k} q['vers'] = vers unless vers.empty? q['diff'] = 'true' if diff q['plugin'] = 'true' if plugin query = URI.encode_www_form(q).gsub('%2C', ',') uri.query = query.empty? ? nil : query JS.global.window.history.replaceState('', '', uri.to_s) list = JS.document.getElementById('menu').querySelectorAll('li a').to_a list.each do |a| href = a.href.to_s a.href = href.to_s.sub(/\?.*|\z/, '?'+query) unless query.empty? if URI.parse(href).path.start_with? "/#{@type}/" a.parentElement[:className] = 'current' JS.document.title = "#{a[:textContent]} / MySQL Parameters" else a.parentElement[:className] = nil end end end # パラメータテーブルを表示する def display_table versions = @versions plugin = include_plugins? table = JS.document.getElementById('param_table') table&.remove table = create_element('table', id: 'param_table') tr = create_element('tr') tr.appendChild(create_element('th').tap{it[:textContent] = 'Parameter'}) versions.each do |ver| next unless @param_cache[[@type, ver, plugin]] th = create_element('th') th[:textContent] = ver tr.appendChild(th) end table.appendChild(tr) cache_keys = versions.map{|v| [@type, v, plugin]} params = @param_cache.values_at(*cache_keys).compact.map(&:keys).flatten.sort.uniq JS.document.getElementById('params').appendChild(table) i = 0 params.each do |name| i += 1 same_value = @param_cache.values_at(*cache_keys).compact.map{_1[name]}.uniq.size == 1 tr = create_element('tr') tr.setAttribute('class', 'same-value') if same_value td = create_element('td', class: 'parameter-name') href = manual_link(@type, name) if href a = create_element('a', href:, rel: 'noreferrer noopenner', target: '_blank').tap{it[:textContent] = name} td.appendChild(a) else td[:textContent] = name end tr.appendChild(td) first = true prev = prev_td = nil values = [] versions.each do |ver| next unless @param_cache[[@type, ver, plugin]] value = @param_cache[[@type, ver, plugin]][name] values.push value td = create_element('td', class: 'parameter-value') build_value(prev_td, td, prev, value, first) tr.appendChild(td) prev_td = td prev = value first = false end table.appendChild(tr) end end def build_value(prev_td, td, prev, value, first) if value if !first && prev != value td[:className] = 'parameter-value difference' if prev && value.chomp =~ /\n/ prev_lines = prev.lines hprev_lines = prev_td.innerHTML.to_s.lines xprev_lines = prev.gsub(' ', '').lines value_lines = value.lines xvalue_lines = value.gsub(' ', '').lines require_relative 'diff-lcs/diff-lcs' Diff::LCS.diff(xprev_lines, xvalue_lines).flatten(1).each do |d| if d.action == '+' value_lines[d.position] = "#{escape value_lines[d.position]}" elsif d.action == '-' hprev_lines[d.position] = "#{escape prev_lines[d.position]}" end end prev_td.innerHTML = hprev_lines.join td.innerHTML = value_lines.join else td[:textContent] = value end else td[:textContent] = value end else td[:className] = 'parameter-value unexist' end end def include_plugins? plugin_element.checked == true end ESCAPE = { '<' => '<', '>' => '>', '&' => '&t;', } def escape(s) s.gsub(/[<>&]/){ESCAPE[_1]} end def manual_link(type, name) ver = nil @versions.sort.reverse.each do |v| if @param_cache[[type, v, false]][name] ver = v break end end return nil unless ver ver = ver.split('.')[0,2].join('.') case type when 'mysqld' name = name.gsub('-', '_') "https://dev.mysql.com/doc/refman/9.7/en/server-option-variable-reference.html#:~:text=#{name}" # "https://dev.mysql.com/doc/refman/#{ver}/en/server-options.html#option_mysqld_#{name}" when 'mysql' "https://dev.mysql.com/doc/refman/#{ver}/en/mysql-command-options.html#:~:text=%2D%2D#{name}" when 'variable', 'status' "https://dev.mysql.com/doc/refman/9.7/en/server-option-variable-reference.html#:~:text=#{name}" # "https://dev.mysql.com/doc/refman/#{ver}/en/dynamic-system-variables.html#:~:text=#{name}" # "https://dev.mysql.com/doc/refman/#{ver}/en/server-status-variables.html#statvar_#{name}" # case name # when /^innodb/ # "https://dev.mysql.com/doc/refman/#{ver}/en/innodb-parameters.html#sysvar_#{name}" # else # "https://dev.mysql.com/doc/refman/#{ver}/en/server-system-variables.html#sysvar_#{name}" # end when 'privilege' "https://dev.mysql.com/doc/refman/#{ver}/en/privileges-provided.html#:~:text=#{name}" when 'function' name = name.strip.gsub(/[^a-zA-Z0-9_-]/){'%'+it.ord.to_s(16)} "https://dev.mysql.com/doc/refman/#{ver}/en/built-in-function-reference.html#:~:text=#{name.strip}" when 'ischema' "https://dev.mysql.com/doc/refman/#{ver}/en/information-schema-table-reference.html#:~:text=#{name.split('.')[0]}" when 'pschema' "https://dev.mysql.com/doc/refman/#{ver}/en/performance-schema-table-reference.html#:~:text=#{name.split('.')[0]}" else nil end end