Skip to content

Instantly share code, notes, and snippets.

@alexeypegov
Created January 7, 2018 15:17
Show Gist options
  • Save alexeypegov/1ff0df6e7db1a53f61667ed4d8b01831 to your computer and use it in GitHub Desktop.
Save alexeypegov/1ff0df6e7db1a53f61667ed4d8b01831 to your computer and use it in GitHub Desktop.
Simple INI-file parser
(ns ini.core
(:use [clojure.string :only [index-of last-index-of trim split-lines]]
[slingshot.slingshot :only [throw+]]))
(defn- section?
"Whatever the given string is a section or not"
[s]
(and
(= (first s) \[)
(= (last s) \])))
(defn- comment?
"Whatever given string is a comment"
[s]
(= (first s) \;))
(defn- value?
"Whatever given string is a key=value pair"
[s]
(let [eq-ndx (index-of s \=)]
(and (some? eq-ndx) (= eq-ndx (last-index-of s \=)))))
(defn- line-type
"Returns type of the string"
[s]
(cond
(comment? s) :comment
(section? s) :section
(value? s) :value
(empty? s) :empty
:else :unknown))
(defn- section-parser
"Parses a section, weak validation"
[s]
(symbol (trim (subs s 1 (- (count s) 1)))))
(defn- value-parser
"Parses a key=value pair"
[s]
(let [eq-ndx (index-of s \=)]
[(symbol (trim (subs s 0 eq-ndx))) (trim (subs s (+ eq-ndx 1)))]))
(defn- error-parser
"Throws an error for invalid lines"
[s]
(throw+ {:type :invalid-line :line s}))
(defn- parse-line
"Parses a line"
[line]
(let [trimmed (trim line) type (line-type trimmed)]
(case type
:empty nil
:comment nil
:section (section-parser trimmed)
:value (value-parser trimmed)
(error-parser trimmed))))
(defn parse-ini-lines
"Parses a bunch of ini-file lines"
[lines]
(:mapping
(reduce (fn [{:keys [section mapping]} cur]
(if (symbol? cur)
{:section cur :mapping mapping}
{:section section
:mapping (if (some? section)
(assoc-in mapping [section (first cur)] (last cur))
(assoc mapping (first cur) (last cur)))}))
#{}
(filter some? (map parse-line lines)))))
(defn parse-file
"Parses given file"
[file]
)
(ns ini.core-test
(:require [clojure.test :refer :all]
[b5v.core :refer :all])
(:use [clojure.string :only [join split-lines]]))
(defn long-string [& strings] (join "\n" strings))
(deftest parse-lines-test
(testing "init file parsing"
(is (= {:owner {:name "John Doe"
:organization "Acme Widgets Inc."}
:database {:server "192.0.2.62"
:port "143"
:file "\"payroll.dat\""}}
(parse-ini-lines (split-lines (long-string "; last modified 1 April 2001 by John Doe"
"[owner]"
"name=John Doe"
"organization=Acme Widgets Inc."
""
"[database]"
"; use IP address in case network name resolution is not working"
"server=192.0.2.62 "
"port=143"
"file=\"payroll.dat\"")))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment