diff --git a/README.md b/README.md index c3c0a53..ec5a6dc 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ This project is built on work of [jb](https://github.com/amatsuda/jb). ## Template Syntax -SimpleJson templates are simply lambda objects that return data(Hashes or Arrays) for json. +SimpleJson templates should be written in Ruby lambda format. The template code is converted into a method and then invoked to produce data (Hashes or Arrays) for JSON. ```ruby -> { diff --git a/lib/simple_json.rb b/lib/simple_json.rb index 5f5894e..17f54e5 100644 --- a/lib/simple_json.rb +++ b/lib/simple_json.rb @@ -11,7 +11,7 @@ module SimpleJson @config = { - cache_enabled: false, + template_cache_enabled: false, template_paths: ['app/views'], cache_key_prefix: 'simple_json/views', default_json_module: ActiveSupport::JSON diff --git a/lib/simple_json/simple_json_renderer.rb b/lib/simple_json/simple_json_renderer.rb index 0cff63f..6283385 100644 --- a/lib/simple_json/simple_json_renderer.rb +++ b/lib/simple_json/simple_json_renderer.rb @@ -20,7 +20,7 @@ def load_all_templates! template_files = Rails.root.glob("#{path}/**/*.simple_json.rb") template_files.each do |file_path| template_path = file_path.relative_path_from(Rails.root.join(path)).to_path.delete_suffix('.simple_json.rb') - @renderers[template_path] = SimpleJsonTemplate.new(file_path.to_path).renderer + define_template_method(template_path, file_path.to_path) end end @templates_loaded = true @@ -29,7 +29,7 @@ def load_all_templates! def load_template(template_path) if SimpleJson.template_cache_enabled? load_all_templates! unless templates_loaded? - renderers[template_path] + render_methods[template_path] else load_template_from_file(template_path) end @@ -38,18 +38,36 @@ def load_template(template_path) def load_template_from_file(template_path) SimpleJson.template_paths.each do |path| file_path = Rails.root.join("#{path}/#{template_path}.simple_json.rb").to_path - return SimpleJsonTemplate.new(file_path).renderer if File.exist?(file_path) + + return define_template_method(template_path, file_path) if File.exist?(file_path) end nil end - def renderers - @renderers ||= {} + def define_template_method(template_path, file_path) + template = SimpleJsonTemplate.new(file_path) + code_hash = template.code.hash + + render_methods[template_path] = render_methods_cache.fetch(code_hash) do + @template_num ||= 0 + @template_num += 1 + method_name = :"template_#{@template_num}" + template.define_to_class(self, method_name) + render_methods_cache[code_hash] = method_name + end + end + + def render_methods + @render_methods ||= {} + end + + def render_methods_cache + @render_methods_cache ||= {} end def clear_renderers - @renderers = {} + @render_methods = {} @templates_loaded = false end end @@ -70,10 +88,11 @@ def renderers end def render(template_name, **params) + method_name = renderer(template_name) if !params.empty? - instance_exec(**params, &renderer(template_name)) + send(method_name, **params) else - instance_exec(&renderer(template_name)) + send(method_name) end end diff --git a/lib/simple_json/simple_json_template.rb b/lib/simple_json/simple_json_template.rb index deab7b3..84befc1 100644 --- a/lib/simple_json/simple_json_template.rb +++ b/lib/simple_json/simple_json_template.rb @@ -7,8 +7,11 @@ def initialize(path) @source = File.read(path) end - def renderer - @renderer ||= eval(code, TOPLEVEL_BINDING, @path) # rubocop:disable Security/Eval + def define_to_class(klass, method_name) + method_string = to_method_string(method_name) + klass.class_eval(method_string, @path) + + method_name end def code @@ -22,5 +25,24 @@ def lambda_stringify(source) "-> { #{source} }" end + + def method_string_from_lambda(source, method_name) + replaced = source.sub(/\A(?(?:[ \t]*(?:#.*?)?\n)*)(?[ \t]*)->\s*(?\([^)]*\))?[ \t]*\{?[ \t]*(?[^\n]*)/) do + header = "#{Regexp.last_match(:prefix)}#{Regexp.last_match(:indent)}def #{method_name}#{Regexp.last_match(:args)}" + rest = Regexp.last_match(:rest) + rest.strip.empty? ? header : "#{header}; #{rest.lstrip}" + end + return unless replaced != source + + brace_index = replaced.rindex('}') + return replaced unless brace_index + + replaced[brace_index] = 'end' + replaced + end + + def to_method_string(method_name) + method_string_from_lambda(code, method_name) + end end end