【问题标题】:Validate ERB template before rendering在渲染之前验证 ERB 模板
【发布时间】:2019-07-19 17:44:18
【问题描述】:

如果模板中缺少值,ruby ERB 类将静默继续。

例如 @config['bar'] 和 ENV['DOESNTEXIST'] 未定义。

require 'erb'
@config = {}
@config['foo'] = 42
template = ERB.new <<-EOF
  FOO=<%= @config['foo'] %>
  BAR=<%= @config['bar'] %>
  LANG=<%= ENV['LANG'] %>
  BAD=<%= ENV['DOESNTEXIST'] %>
EOF
template.result(binding)

输出

FOO=42
BAR=  
LANG=en_US.UTF-8
BAD=

期望的输出

Warning: @config['bar'] is undefined
Warning: ENV['DOESNTEXIST'] is undefined
FOO=42
BAR=  
LANG=en_US.UTF-8
BAD=

如果 ERB 模板具有 nil 或未定义值,是否有方法发出警告?
有没有办法在 erb 模板中显示所有变量,以便我可以遍历它们进行验证?

我不提前知道模板的所有值,因为用户可以指定自己的模板。


我尝试过的:

template.inspect 给了我一个显示占位符的字符串。我可以过滤子字符串,但这是不可靠的。

template.inspect
 => "#<ERB:0x00007ff4e50b1288 @safe_level=nil, @src=\"#coding:UTF-8\\n_erbout = +''; _erbout.<< \\\"  FOO=\\\".freeze; _erbout.<<(( @config['foo'] ).to_s); _erbout.<< \\\"\\\\n  BAR=\\\".freeze\\n; _erbout.<<(( @config['bar'] ).to_s); _erbout.<< \\\"\\\\n  LANG=\\\".freeze\\n; _erbout.<<(( ENV['LANG'] ).to_s); _erbout.<< \\\"\\\\n  BAD=\\\".freeze\\n; _erbout.<<(( ENV['DOESNT EXIST'] ).to_s); _erbout.<< \\\"\\\\n\\\".freeze\\n; _erbout\", @encoding=#<Encoding:UTF-8>, @frozen_string=nil, @filename=nil, @lineno=0>"`

更新:我承认这个问题的表述方式不是很清楚。问题的实质是如何将"@config['foo']" 之类的字符串转换为@config['foo'] 之类的变量? 我了解到这通常是使用已知问题的eval() 完成的。 相关问题:how can i avoid eval in this situation

【问题讨论】:

  • ERB 不会为您执行此操作。我建议编写某种validate_config(@config) 方法来进行检查。
  • 很公平。我仍然需要以某种方式从模板中干净地获取变量。 'grep' 是最好的方法吗?
  • "用户可以指定自己的模板" ...您对这些用户的信任程度如何?因为这听起来很不安全
  • 这是公司内部使用的命令行实用程序。与helm template 非常相似。用户定义整个配置文件,该实用程序只是帮助对其进行参数化。
  • 一个更好的方法是通过 JSON 或者如果你需要 HTML 然后 XSLT

标签: ruby erb


【解决方案1】:

使用fetch。来自文档:

从给定键的哈希中返回一个值。如果找不到密钥,有以下几种选择: 没有其他参数,它将引发 KeyError 异常;如果给定默认值,则将返回该值;如果指定了可选代码块,则将运行该代码块并返回其结果。

不只是 ERB 具有您所描述的行为;任何 Ruby 数组都将返回 nil 任何时候哈希中没有的东西。我们希望它“快速失败”,一旦缺少某些东西。

2.5.1 :000 > {foo: "something"}[:foo]
 => "something"
2.5.1 :001 > {foo: "something"}[:bar]
 => nil

使用fetch 访问@config 中的项目:

2.5.1 :000 > @config = {foo: "something"}
 => "something"
2.5.1 :001 > @config.fetch(:foo)
 => "something"
2.5.1 :002 > @config.fetch(:bar)
KeyError (key not found: :bar)

在您的代码中使用 fetch 来检索不存在的 bar 会返回以下内容:

undefined local variable or method `‘bar’' for main:Object

这不是原始问题所提出的警告,而是错误。如果只需要一个警告,fetch 接受一个默认值作为可以充当该角色的第二个参数。

fetch docs

【讨论】:

  • 这很有帮助,可以解决一半的问题。问题的真正根源是如何从erb模板中取出变量。
  • 这听起来很难,从你的问题中不清楚为什么这是必要的。如果您可以访问@config,如代码所示,为什么不在模板中使用它之前对其进行验证?
  • 同意,我没有很好地表达这个问题。我现在知道我真正要找的是eval()。
【解决方案2】:

一种选择是使用正则表达式过滤掉所有模式,如&lt;%= -%&gt; 和&lt;%= %&gt;。

text.scan(/<%= ?([^>]+) -?%>/)

哪个会返回

 => [["@config['foo']"], ["@config['bar']"], ["ENV['LANG']"], ["ENV['DOESNTEXIST']"]]

然后使用eval()来验证变量@config['foo']是否存在。因为eval() 很危险,所以最好将允许评估的变量列入白名单。在这种情况下,只允许使用像 @config[; 和 ENV[' 这样的字符串。

require 'erb'
@config = {}
@config['foo'] = 42
text = <<-EOF
  FOO=<%= @config['foo'] %>
  BAR=<%= @config['bar'] %>
  LANG=<%= ENV['LANG'] %>
  BAD=<%= ENV['DOESNTEXIST'] %>
EOF
# Match strings like <%= -%> and <%= %>
variables = text.scan(/<%= ?([^>]+) -?%>/)
template = ERB.new text
template.result(binding)

# Only evaluate variables that start with '@config' or 'ENV'
variables.each do |variable|
  if variable.start_with?("@config")
    variable.sub!("@config", "")
    @config2 = builder.instance_variable_get("@config")
    puts "WARNING: Undefined variable: @config#{variable}" unless eval("@config2#{variable}")
  elsif variable.start_with?("ENV")
    puts "WARNING: Undefined variable: #{variable}" unless eval(variable)
  else
    raise "Variable '#{variable}' not allowed"
  end
end

【讨论】:

    猜你喜欢
    • 2012-06-03
    • 2014-12-08
    • 2014-01-24
    • 2021-06-01
    • 2017-07-04
    • 2012-12-31
    • 1970-01-01
    • 1970-01-01
    • 2019-01-11
    相关资源
    最近更新 更多