如何在Ruby中使用哈希键进行这些字符串替换?

时间:2018-06-13 15:57:03

标签: ruby

我有一堆JSON文件,在Python和Ruby中处理,看起来像这样:

{
    "KEY1": "foo",
    "KEY2": "bar",

    "URL": "https://{KEY2}.com/{KEY1}",
    "IMPORTANT_THING": "repos/{KEY1}",
    "NOTE": "This thing is {KEY1}{KEY2}ed",
    "PYTHON_ONLY_THING": "{}/test/{}.py"
}

请注意,密钥显示的顺序不一致,我宁愿不更改JSON。

这是我的测试代码,显示了我迄今为止尝试过的内容:

my_config = {"KEY1"=>"foo",
             "KEY2"=>"bar",
             "URL"=>"https://{KEY2}.com/{KEY1}",
             "IMPORTANT_THING"=>"repos/{KEY1}",
             "NOTE"=>"This thing is {KEY1}{KEY2}ed",
             "PYTHON_ONLY_THING"=>"{}/test/{}.py"}

my_config.each_key do |key|
    # Braindead, hard-coded solution that works:
    # my_config[key].gsub!("{KEY1}", my_config["KEY1"])
    # my_config[key].gsub!("{KEY2}", my_config["KEY2"])

    # More flexible (if it would work):
    # my_config[key].gsub!(/{.*}/, my_config['\0'.slice(1,-2)])
    my_config[key].gsub!(/{.*}/) {|s| my_config[s.slice(1,-2)]}
end

puts my_config

我现在正在使用braindead解决方案,它会产生预期的输出:

{"KEY1"=>"foo", "KEY2"=>"bar", "URL"=>"https://bar.com/foo", "IMPORTANT_THING"=>"repos/foo", "NOTE"=>"This thing is foobared", "PYTHON_ONLY_THING"=>"{}/test/{}.py"}

但我想让它更灵活,更易于维护。第一个"更好"解决方案显然会抛出错误,因为切片操作' \ 0'本身而不是匹配,加上我不确定它会不止一次匹配。当前没有注释的解决方案不起作用,因为第二部分似乎一次只能操作一个字母,而不是像我预期的那样每个匹配,所以它只是删除花括号中的东西。更糟糕的是,它删除了PYTHON_ONLY_THING中外括号之间的所有内容,这是不行的。

我想我需要更改我的正则表达式和Ruby代码,如果它能够工作,但我不知道在哪里寻求更多的帮助。或者也许gsub不是这项工作的正确工具。有什么想法吗?

我在Linux x86_64上使用Ruby 2.3.7。

2 个答案:

答案 0 :(得分:4)

使用String#gsub和替换的初始哈希:

my_config.map do |k, v|
  [
    k,
    v.gsub(/(?<={)[^}]+(?=})/, my_config).gsub(/{(?!})|(?<!{)}/, '')
  ]
end.to_h
#⇒ {"KEY1"=>"foo",
#   "KEY2"=>"bar",
#   "URL"=>"https://bar.com/foo",
#   "IMPORTANT_THING"=>"repos/foo",
#   "NOTE"=>"This thing is foobared",
#   "PYTHON_ONLY_THING"=>"{}/test/{}.py"}

从Ruby 2.4(或使用Rails)开始,使用Hash#transform_values可以更简单。

如果您不喜欢第二个gsubbing,请事先转换哈希:

my_substs = my_config.map { |k, v| ["{#{k}}", v] }.to_h
my_config.map do |k, v|
  [k, v.gsub(/{[^}]+}/, my_substs)]
end.to_h

答案 1 :(得分:2)

这是一个可能的解决方案:

my_config = {"KEY1"=>"foo",
             "KEY2"=>"bar",
             "URL"=>"https://{KEY2}.com/{KEY1}",
             "IMPORTANT_THING"=>"repos/{KEY1}",
             "NOTE"=>"This thing is {KEY1}{KEY2}ed",
             "PYTHON_ONLY_THING"=>"{}/test/{}.py"}

my_config.each_key do |key|
  placeholders = my_config[key].scan(/{([^}]+)}/).flatten
  placeholders.each do |placeholder|
    my_config[key].gsub!("{#{placeholder}}", my_config[placeholder]) if my_config.keys.include?(placeholder)
  end
end

puts my_config
  • 使用scan,这将取代所有匹配,而不仅仅是第一场比赛。
  • 在正则表达式中使用[[^}]+,而不是.*,意味着您不会吞下&#34;吞下&#34;在这部分比赛中太多了。例如,如果输入包含"{FOO} bar {BAZ}",那么您希望该模式仅捕获FOOBAZ,而不是FOO} bar {BAZ
  • 对扫描结果进行分组,然后调用flatten,这是拒绝捕获组外部内容的简单方法,即在此情况下为{}个字符。 (这使得代码比使用slice(1,-2)等索引更加神秘!
  • my_config.keys.include?(placeholder)检查这是否确实存在。已知值,因此您不能使用nil替换内容。