From faa89b95c90f8dfebd54657d98244b3d27f2ad99 Mon Sep 17 00:00:00 2001 From: monkeyWzr Date: Thu, 12 Sep 2024 02:07:44 +0900 Subject: [PATCH] add an option to merge default configs --- lib/commonmarker.rb | 12 ++++---- lib/commonmarker/config.rb | 60 +++++++++++++++++++++----------------- lib/commonmarker/node.rb | 14 +++++---- test/config_test.rb | 37 +++++++++++++++++++++++ test/spec_test.rb | 3 +- 5 files changed, 86 insertions(+), 40 deletions(-) create mode 100644 test/config_test.rb diff --git a/lib/commonmarker.rb b/lib/commonmarker.rb index 536c8b98..fbc0cbe2 100755 --- a/lib/commonmarker.rb +++ b/lib/commonmarker.rb @@ -14,14 +14,15 @@ class << self # # text - A {String} of text # options - A {Hash} of render, parse, and extension options to transform the text. + # merge_default_options - A {Boolean} representing whether or not merge the default for unspecified keys in render, parse, and extension options # # Returns the `parser` node. - def parse(text, options: Commonmarker::Config::OPTIONS) + def parse(text, options: Commonmarker::Config::OPTIONS, merge_default_options: false) raise TypeError, "text must be a String; got a #{text.class}!" unless text.is_a?(String) raise TypeError, "text must be UTF-8 encoded; got #{text.encoding}!" unless text.encoding.name == "UTF-8" raise TypeError, "options must be a Hash; got a #{options.class}!" unless options.is_a?(Hash) - opts = Config.process_options(options) + opts = Config.process_options(options, merge_default_options:) commonmark_parse(text, options: opts) end @@ -31,15 +32,16 @@ def parse(text, options: Commonmarker::Config::OPTIONS) # text - A {String} of text # options - A {Hash} of render, parse, and extension options to transform the text. # plugins - A {Hash} of additional plugins. + # merge_default_options - A {Boolean} representing whether merge the default for unspecified keys in render, parse, and extension options # # Returns a {String} of converted HTML. - def to_html(text, options: Commonmarker::Config::OPTIONS, plugins: Commonmarker::Config::PLUGINS) + def to_html(text, options: Commonmarker::Config::OPTIONS, plugins: Commonmarker::Config::PLUGINS, merge_default_options: false) raise TypeError, "text must be a String; got a #{text.class}!" unless text.is_a?(String) raise TypeError, "text must be UTF-8 encoded; got #{text.encoding}!" unless text.encoding.name == "UTF-8" raise TypeError, "options must be a Hash; got a #{options.class}!" unless options.is_a?(Hash) - opts = Config.process_options(options) - plugins = Config.process_plugins(plugins) + opts = Config.process_options(options, merge_default_options:) + plugins = Config.process_plugins(plugins, merge_default_options:) commonmark_to_html(text, options: opts, plugins: plugins) end diff --git a/lib/commonmarker/config.rb b/lib/commonmarker/config.rb index 0cc7841b..ac63ef81 100644 --- a/lib/commonmarker/config.rb +++ b/lib/commonmarker/config.rb @@ -45,8 +45,8 @@ module Config underline: false, spoiler: false, greentext: false, - }, - format: [:html].freeze, + }.freeze, + format: [:html].freeze, # this option is currently not used }.freeze PLUGINS = { @@ -59,37 +59,47 @@ module Config class << self include Commonmarker::Utils - def merged_with_defaults(options) - Commonmarker::Config::OPTIONS.merge(process_options(options)) - end - - def process_options(options) - { + def process_options(options, merge_default_options:) + ret = { parse: process_parse_options(options[:parse]), render: process_render_options(options[:render]), extension: process_extension_options(options[:extension]), } + + if merge_default_options + ret.keys.each do |type| + ret[type] = Commonmarker::Config::OPTIONS[type].merge(ret[type]) + end + end + + ret end - def process_plugins(plugins) - { + def process_plugins(plugins, merge_default_options:) + ret = { syntax_highlighter: process_syntax_highlighter_plugin(plugins&.fetch(:syntax_highlighter, nil)), } + + if merge_default_options + ret.keys.each do |type| + ret[type] = Commonmarker::Config::PLUGINS[type].merge(ret[type]) if ret[type] + end + end + + ret end end [:parse, :render, :extension].each do |type| define_singleton_method :"process_#{type}_options" do |option| - Commonmarker::Config::OPTIONS[type].each_with_object({}) do |(key, value), hash| - if option.nil? # option not provided, go for the default - hash[key] = value - next - end + return Commonmarker::Config::OPTIONS[type].dup if option.nil? + raise TypeError, "#{type} options must be a Hash; got a #{option.class}!" unless option.is_a?(Hash) - # option explicitly not included, remove it - next if option[key].nil? + option.keys.each_with_object({}) do |key, hash| + next unless Commonmarker::Config::OPTIONS[type].key?(key) - hash[key] = fetch_kv(option, key, value, type) + default_value = Commonmarker::Config::OPTIONS[type][key] + hash[key] = fetch_kv(option, key, default_value, type) end end end @@ -97,17 +107,13 @@ def process_plugins(plugins) [:syntax_highlighter].each do |type| define_singleton_method :"process_#{type}_plugin" do |plugin| return if plugin.nil? # plugin explicitly nil, remove it + raise TypeError, "#{plugin} config must be a Hash; got a #{plugin.class}!" unless plugin.is_a?(Hash) - Commonmarker::Config::PLUGINS[type].each_with_object({}) do |(key, value), hash| - if plugin.nil? # option not provided, go for the default - hash[key] = value - next - end - - # option explicitly not included, remove it - next if plugin[key].nil? + plugin.keys.each_with_object({}) do |key, hash| + next unless Commonmarker::Config::PLUGINS[type].key?(key) - hash[key] = fetch_kv(plugin, key, value, type) + default_value = Commonmarker::Config::PLUGINS[type][key] + hash[key] = fetch_kv(plugin, key, default_value, type) end end end diff --git a/lib/commonmarker/node.rb b/lib/commonmarker/node.rb index 50bd71e5..1a08b6fe 100644 --- a/lib/commonmarker/node.rb +++ b/lib/commonmarker/node.rb @@ -36,13 +36,14 @@ def each # # options - A {Hash} of render, parse, and extension options to transform the text. # plugins - A {Hash} of additional plugins. + # merge_default_options - A {Boolean} representing whether merge the default for unspecified keys in render, parse, and extension options # # Returns a {String} of HTML. - def to_html(options: Commonmarker::Config::OPTIONS, plugins: Commonmarker::Config::PLUGINS) + def to_html(options: Commonmarker::Config::OPTIONS, plugins: Commonmarker::Config::PLUGINS, merge_default_options: false) raise TypeError, "options must be a Hash; got a #{options.class}!" unless options.is_a?(Hash) - opts = Config.process_options(options) - plugins = Config.process_plugins(plugins) + opts = Config.process_options(options, merge_default_options:) + plugins = Config.process_plugins(plugins, merge_default_options:) node_to_html(options: opts, plugins: plugins).force_encoding("utf-8") end @@ -51,13 +52,14 @@ def to_html(options: Commonmarker::Config::OPTIONS, plugins: Commonmarker::Confi # # options - A {Symbol} or {Array of Symbol}s indicating the render options # plugins - A {Hash} of additional plugins. + # merge_default_options - A {Boolean} representing whether merge the default for unspecified keys in render, parse, and extension options # # Returns a {String}. - def to_commonmark(options: Commonmarker::Config::OPTIONS, plugins: Commonmarker::Config::PLUGINS) + def to_commonmark(options: Commonmarker::Config::OPTIONS, plugins: Commonmarker::Config::PLUGINS, merge_default_options: false) raise TypeError, "options must be a Hash; got a #{options.class}!" unless options.is_a?(Hash) - opts = Config.process_options(options) - plugins = Config.process_plugins(plugins) + opts = Config.process_options(options, merge_default_options:) + plugins = Config.process_plugins(plugins, merge_default_options:) node_to_commonmark(options: opts, plugins: plugins).force_encoding("utf-8") end diff --git a/test/config_test.rb b/test/config_test.rb new file mode 100644 index 00000000..e7fec9e0 --- /dev/null +++ b/test/config_test.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "test_helper" + +class ConfigTest < Minitest::Test + def test_process_options + user_config = { + parse: { + smart: true, + }, + render: { + unsafe: false, + }, + } + processed_config = Commonmarker::Config.process_options(user_config, merge_default_options: true) + + expected_config = [:parse, :render, :extension].each_with_object({}) do |type, hash| + hash[type] = Commonmarker::Config::OPTIONS[type].merge(user_config[type] || {}) + end + + assert_equal(expected_config, processed_config) + end + + def test_process_plugins + user_config = { + syntax_highlighter: { + path: './themes"', + }, + } + processed_config = Commonmarker::Config.process_plugins(user_config, merge_default_options: true) + expected_config = [:syntax_highlighter].each_with_object({}) do |type, hash| + hash[type] = Commonmarker::Config::PLUGINS[type].merge(user_config[type]) + end + + assert_equal(expected_config, processed_config) + end +end diff --git a/test/spec_test.rb b/test/spec_test.rb index c8797bcd..5330f658 100644 --- a/test/spec_test.rb +++ b/test/spec_test.rb @@ -8,7 +8,7 @@ class SpecTest < Minitest::Test spec.each do |testcase| define_method(:"test_to_html_example_#{testcase[:example]}") do - opts = { + options = { render: { unsafe: true, gfm_quirks: true, @@ -18,7 +18,6 @@ class SpecTest < Minitest::Test end, } - options = Commonmarker::Config.merged_with_defaults(opts) options[:extension].delete(:header_ids) # this interefers with the spec.txt extension-less capability options[:extension][:tasklist] = true actual = Commonmarker.to_html(testcase[:markdown], options: options, plugins: nil).rstrip