在Clojure中将配置文件作为数据结构加载

34
有没有在Clojure中解析Clojure数据结构的读取函数?我的使用情况是读取配置属性文件,其中一个属性的值应该是一个列表。我希望能够这样写:
file.properties:
property1 = ["value1" "value2"]

在Clojure中:

(load-props "file.properties")

并获得一个值为{property1,["value1" "value2"]}的映射

目前我正在执行以下操作,使用相同的输入文件"file.properties":

(defn load-props [filename]
    (let [io (java.io.FileInputStream. filename)
        prop (java.util.Properties.)]
    (.load prop io)
    (into {} prop)))

;; returns:
;; {"property1" "[\"valu1\", \"valu2\"]"}
(load-props "file.properties")

但是我无法找到一种将结果解析为Clojure向量的方法。我基本上正在寻找类似于Erlang的file:consult/1函数的东西。有什么想法吗?


如果您不确定使用属性文件,Jonas的答案也是一个不错的选择。 - Dave Ray
Korny在2013年的回答是最好的。 - noahlz
5个回答

45
如果您想要阅读Java风格的属性文件,请查看Dave Ray的答案-虽然属性文件有许多限制。
如果您使用的是Clojure 1.5或更高版本,我建议您使用edn,这是Datomic中使用的可扩展数据符号-它基本上是Clojure数据结构,没有任意代码执行,并且可以添加标签以用于实例或任意类型。
最简单的使用方法是通过read-stringslurp
(require 'clojure.edn)
(clojure.edn/read-string (slurp "filename.edn"))

就是这样。请注意,read-string 只读取一个变量,因此您应该将配置设置为映射:

{ :property1 ["value1" "value2"] }

然后:

(require 'clojure.edn)
(def config (clojure.edn/read-string (slurp "config.edn")))
(println (:property1 config))

返回

["value1" "value2"]

32

java.util.Properties 实现了 Map 接口,因此可以很容易地完成此操作,而不需要手动解析属性文件:

(require 'clojure.java.io)
(defn load-props
  [file-name]
  (with-open [^java.io.Reader reader (clojure.java.io/reader file-name)] 
    (let [props (java.util.Properties.)]
      (.load props reader)
      (into {} (for [[k v] props] [(keyword k) (read-string v)])))))

(load-props "test.properties")
;=> {:property3 {:foo 100, :bar :test}, :property2 99.9, :property1 ["foo" "bar"]}

特别是属性文件比你想象的更加复杂(注释、转义等等),java.util.Properties非常擅长加载它们。


3
一个稍微简化的版本:(转换为{}道具) - Lei
2
你不需要做这些事情,只需要使用一个叫做 propertyid 的小型库即可 (https://github.com/michaelklishin/propertied)。 - The Alchemist

25

在Clojure中是否有一个读取函数来解析Clojure数据结构?

有的。它被称为read。你也可以使用它来读取配置数据。

一个包含以下内容的文件props.clj

{:property1 ["value1" 2]
 :property2 {:some "key"}}

可以这样读:

(ns somens.core
  (:require [clojure.java.io :as io])
  (:import [java.io PushbackReader]))

(def conf (with-open [r (io/reader "props.clj")]
            (read (PushbackReader. r))))

在阅读不可信来源时,关闭*read-eval*可能是个好主意:

(def conf (binding [*read-eval* false]
            (with-open [r (io/reader "props.clj")]
              (read (PushbackReader. r)))))

如果你想要将配置数据写回到文件中,你应该查看像pr和相关函数的打印功能。


请注意,在您的代码中文件从未关闭。请使用 with-open - Dave Ray
7
请注意,自Clojure 1.5版本以来,您应该使用clojure.edn/read作为读取器,因为read可以执行任意代码,而edn被设计成安全且易于移植的格式。 - Korny
哦,你可以直接使用(read(slurp“props.clj”))-如果你正在读取大量数据,则使用阅读器是有意义的,但对于简单的配置文件来说则过于复杂。 - Korny

3

我的机器上没有安装java-utils contrib包。稍后会进行测试。 - Ahmed
2
Clojure的API文档已经迁移到http://clojure.github.com/clojure,并且clojure-contrib已被弃用/移出到同一github账户下的单独库中。 - Rayne

0
(use '[clojure.contrib.duck-streams :only (read-lines)])
(import '(java.io StringReader PushbackReader))

(defn propline->map [line] ;;property1 = ["value1" "value2"] -> { :property1  ["value1" "value2"] }
  (let [[key-str value-str] (seq (.split line "="))
        key (keyword (.trim key-str))
        value (read (PushbackReader. (StringReader. value-str)))]
        { key value } ))

(defn load-props [filename]
  (reduce into (map propline->map (read-lines filename))))

演示

user=> (def prop (load-props "file.properties"))
#'user/prop
user=> (prop :property1)
["value1" "value2"]
user=> ((prop :property1) 1)
"value2"

更新

(defn non-blank?   [line] (if (re-find #"\S" line) true false))
(defn non-comment? [line] (if (re-find #"^\s*\#" line) false true))

(defn load-props [filename]
  (reduce into (map propline->map (filter #(and (non-blank? %)(non-comment? %)) (read-lines filename)))))

太好了,这正是我所需要的。我已经进行了更改,以便propline->map函数避免注释。更新版本如下:(defn propline->map [line] (let [[key-str value-str] (seq (.split line "=")) key (keyword (.trim key-str)) value (read (PushbackReader. (StringReader. value-str))) comment? (.startsWith key-str "#")] (if (not comment?) { key value }) )) - Ahmed
如果你排除掉注释行,那么在load-props中过滤器可能会更好。 - BLUEPIXY
2
两件事情:1)Properties已经知道如何正确解析这些文件。没有必要部分重新实现该功能。2)duck-streams已被弃用。你需要使用clojure.java.io/readerclojure.core/line-seq - Dave Ray
  1. 是的。例如:(def v (read (PushbackReader. (StringReader. "["valu1", "valu2"]"))))。
  2. 嗯,我不知道过时了。但是,如果需要可以进行替换。
- BLUEPIXY
没错,你的代码更好了。简洁而且很棒。我点赞支持你。 - BLUEPIXY
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接