> 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

Reply via email to