001  (ns walkmap.tag
002    "Code for tagging, untagging, and finding tags on objects. Note the use of
003    the namespaced keyword, `:walkmap.tag/tags`, denoted in this file `::tags`.
004    This is in an attempt to avoid name clashes with other uses of this key."
005    (:require [clojure.set :refer [difference union]]))
006  
007  (defn tagged?
008    "True if this `object` is tagged with each of these `tags`. It is an error
009    (and an exception will be thrown) if
010  
011    1. `object` is not a map;
012    2. any of `tags` is not a keyword."
013    [object & tags]
014    (if
015      (map? object)
016      (if
017        (every? keyword? tags)
018        (let [ot (::tags object)]
019          (and
020            (set? ot)
021            (every? ot tags)))
022        (throw (IllegalArgumentException.
023                 (str "Must be keyword(s): " (map type tags)))))
024      (throw (IllegalArgumentException.
025               (str "Must be a map: " (type object))))))
026  
027  (defn tag
028    "Return an object like this `object` but with these `tags` added to its tags,
029    if they are not already present.It is an error (and an exception will be
030    thrown) if
031  
032    1. `object` is not a map;
033    2. any of `tags` is not a keyword."
034    [object & tags]
035    (if
036      (map? object)
037      (if
038        (every? keyword? tags)
039        (assoc object ::tags (union (set tags) (::tags object)))
040        (throw (IllegalArgumentException.
041                 (str "Must be keyword(s): " (map type tags)))))
042      (throw (IllegalArgumentException.
043               (str "Must be a map: " (type object))))))
044  
045  (defmacro tags
046    "Return the tags of this object, if any."
047    [object]
048    `(::tags ~object))
049  
050  (defn untag
051    "Return an object like this `object` but with these `tags` removed from its
052    tags, if present. It is an error (and an exception will be thrown) if
053  
054    1. `object` is not a map;
055    2. any of `tags` is not a keyword."
056    [object & tags]
057    (if
058      (map? object)
059      (if
060        (every? keyword? tags)
061        (assoc object ::tags (difference (::tags object) (set tags)))
062        (throw (IllegalArgumentException.
063                 (str "Must be keywords: " (map type tags)))))
064      (throw (IllegalArgumentException.
065               (str "Must be a map: " (type object))))))