> Dear Clojure Group, > > I am currently reading the online book Pro Git. In chapter 7.4 (section > “Enforcing a User-Based ACL System”) there is a task of reading in an access > control list (ACL) file, such as the following > > # avail/unavail | users | path > avail|nickh,pjhyett,defunkt,tpw > avail|usinclair,cdickens,ebronte|doc > avail|schacon|lib > avail|schacon|tests > > and printing out a map of the form { "user1" [path 1, path 2], "user2" > [path2, path3] ...}. > > The author of the book provides a solution in Ruby, which I find relatively > easy to follow, despite not having written any Ruby code before: > > def get_acl_access_data(acl_file) > # read in ACL data > acl_file = File.read(acl_file).split("\n").reject { |line| line == '' } > access = {} > acl_file.each do |line| > avail, users, path = line.split('|') > next unless avail == 'avail' > users.split(',').each do |user| > access[user] ||= [] > access[user] << path > end > end > access > end > > I then tried the same in Clojure, but found my solution to be much less > readable compared to the Ruby code: > > (use '[clojure.string :only (split)]) > > (defn get-acl-access-data [file] > (let [acl (split (slurp file) #"\n")] > (apply merge-with #(into %1 %2) > (map (fn [[avail users path]] > (let [users (split users #",")] > (reduce (fn [acc user] > (when (= avail "avail") > (assoc acc user [path]))) > {} users))) > (map #(split % #"\|") acl))))) > > ;; Output: > ;; {"schacon" ["lib" "tests"], > ;; "usinclair" ["doc"], > ;; "cdickens" ["doc"], > ;; "ebronte" ["doc"], > ;; "tpw" [nil], > ;; "defunkt" [nil], > ;; "pjhyett" [nil], > ;; "nickh" [nil]} > > Maybe it is just because I am still a beginner, but I am afraid I won’t be > able to figure out immediately what this code is doing a few weeks from now. > > However, I am sure there must be a better way of translating the Ruby version > into Clojure. My main goal is on clarity, as I often struggle organizing my > code in a way I would consider readable. > > I therefore would be glad for any ideas of improvement. Any suggestions are > highly welcome! > > Best regards, > > Stefan
Both the approaches above have the weakness that the steps are commingled, making it difficult to test (or reuse) part of the work done by the fn. Here is a Clojure version that makes the steps more distinct: (require '[clojure.string :as str]) (require '[clojure.java.io :as io]) (with-open [r (io/reader "somefile")] (->> (line-seq r) (map #(str/split % #"\|")) (filter #(= "avail" (first %))) (mapcat (fn [[_ users path]] (map hash-map (str/split users #",") (repeat [path])))) (apply merge-with into))) >From this it is easy to see that the fn: 1. splits the lines on | 2. filters on "avail" 3. builds a list of user|path pairs 4. merges the user|path pairs into a map Stuart Halloway Clojure/core http://clojure.com -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en