Source

hh-web-tags / useragents.lisp

;; Copyright (c) 2010 Phil Hargett

;; Permission is hereby granted, free of charge, to any person obtaining a copy
;; of this software and associated documentation files (the "Software"), to deal
;; in the Software without restriction, including without limitation the rights
;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
;; copies of the Software, and to permit persons to whom the Software is
;; furnished to do so, subject to the following conditions:

;; The above copyright notice and this permission notice shall be included in
;; all copies or substantial portions of the Software.

;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
;; THE SOFTWARE.

(in-package :hh-web-tags)

(export `( *user-agent-strings* *enabled-user-agents* *user-agent* enabled-user-agents ))

(defvar *user-agent-strings* 
  (let ((new-user-agent-strings (make-hash-table)))
    (macrolet ((user-agent-string (key string)
				   `(setf (gethash ,key new-user-agent-strings) ,string)))
      	       (user-agent-string :bing "Bingbot")
	       (user-agent-string :chrome "Chrome")
	       (user-agent-string :firefox "Firefox")
	       (user-agent-string :firefox3 "Firefox 3")
	       (user-agent-string :firefox3.5 "Firefox 3.5")
	       (user-agent-string :firefox3.6 "Firefox 3.6")
	       (user-agent-string :firefox3.7 "Firefox 3.7")
	       (user-agent-string :firefox3.8 "Firefox 3.8")
	       (user-agent-string :firefox4 "Firefox 4")
	       (user-agent-string :gecko "Gecko")
	       (user-agent-string :google "Googlebot")
	       (user-agent-string :ie "MSIE ")
	       (user-agent-string :ie6 "MSIE 6")
	       (user-agent-string :ie7 "MSIE 7")
	       (user-agent-string :ie8 "MSIE 8")
	       (user-agent-string :ie9 "MSIE 9")
	       (user-agent-string :iphone "iPhone")
	       (user-agent-string :ipad "iPad")
	       (user-agent-string :khtml "KHTML")
	       (user-agent-string :mozilla "Mozilla")
	       (user-agent-string :msie "MSIE ")
	       (user-agent-string :msie6 "MSIE 6")
	       (user-agent-string :msie7 "MSIE 7")
	       (user-agent-string :msie8 "MSIE 8")
	       (user-agent-string :msie9 "MSIE 9")
	       (user-agent-string :msn "msnbot") ;; bing
	       (user-agent-string :opera "Opera")
	       (user-agent-string :webkit "WebKit")
	       (user-agent-string :safari "Safari")
	       (user-agent-string :slurp "Yahoo! Slurp")
	       )
    new-user-agent-strings))

(defvar *enabled-user-agents* ())

(defvar *user-agent* :default)

(defun request-user-agent (&optional (hunchentoot:*request* hunchentoot:*request*))
  (when hunchentoot:*request*
    (hunchentoot:user-agent hunchentoot:*request*)))

(defun detect-user-agents (&optional (hunchentoot:*request* hunchentoot:*request*))
  (let ((user-agent-string (request-user-agent)))
    (append (loop for user-agent-key in *enabled-user-agents*
	       if (search (gethash user-agent-key *user-agent-strings*) user-agent-string)
	       collect user-agent-key)
	    `( :default ))))

(defmacro enabled-user-agents (&rest user-agents)
  `(setf *enabled-user-agents* ',user-agents))

;; TODO note that we are making a forward reference to the HTML macro defined later in tags.lisp
;; not ideal, but since we don't use these macros together until after all are defined, not a near-term problem
(defmacro html-for-user-agent (&rest body) 
  "Given the markup for a page, build a cond statement that will return the rendering appropriate to the current user agent"
  (multiple-value-bind (user-agent-bodies default-body) (loop for remainder on body
							   if (and (listp (car remainder)) (keywordp  (car (car remainder))))
							   collect (car remainder) into bodies else do ( return (values bodies remainder))
							   finally (return (values nil body)))
    (let ((user-agent-cases (loop for user-agent-body in user-agent-bodies
			       collect (destructuring-bind (user-agent-key &rest render-body) user-agent-body
					     `( (eql ,user-agent-key *user-agent*) (html (list ,@render-body)))))))
      `(cond ,@user-agent-cases
	     ( ( eql ':default *user-agent* ) (html (list ,@default-body)))))))