xml-weather.el

Difference between revision 16 and current revision

Summary: Rollback to 2010-08-29 06:18 UTC

No diff available.

Download

;;; xml-weather.el --- Get xml-weather infos in emacs. 
;; 
;; Author: Thierry Volpiatto
;; Maintainer: Thierry Volpiatto
;; Copyright (C) 2009, Thierry Volpiatto, all rights reserved.

;; Created: lun. août  3 10:41:46 2009 (+0200)
;; Version: 
;; X-URL: http://mercurial.intuxication.org/hg/xml-weather  
;; Keywords: 
;; Compatibility: emacs23
;; 
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 3, or
;; (at your option) any later version.
;; 
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.
;; 
;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
;; Floor, Boston, MA 02110-1301, USA.
;; 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;; 
;;; Commentary:
;;
;; Install:
;; =======
;;
;; 1)
;; To obtain the feed, you must first register with weather.com[1] to obtain a license key.
;; The registration is free but mandatory.
;; Once you've registered, you'll receive a confirmation email and a link to the software developer's kit.
;; Included in the kit is a comprehensive user's guide, numerous icons, and instructions (and restrictions)
;; on use of The Weather Channel logos.
;; [1]http://www.weather.com/services/xmloap.html
;;
;; 2)
;; Once you have your Partner ID and License Key, you have two ways to set them up:
;;
;; A) The bad way:
;;    Add to .emacs:
;;    (setq xml-weather-login "your Partner ID")
;;    (setq xml-weather-key "your License Key")
;;
;; B) The good way:
;;    Be sure to require `auth-sources' and set it up correctly.
;;    Add to your ~/.authinfo file this line:(change login and pwd)
;;    machine xoap.weather.com port http login <Partner ID> password <License Key>
;;
;; 3) Add to .emacs (be sure xml-weather.el is in your load-path):
;;    (require 'xml-weather)
;;
;; 4) (Facultative) Get the icons set from the link to the software developer's kit
;;    you will find in your email.
;;    Put the icons in the directory of your choice and set it in your .emacs:
;;    (setq xml-weather-default-icons-directory "path/to/your/icons/31x31")
;;    NOTE: I use the 31x31 directory but you can choose bigger icons if you want.
;;          If `xml-weather-default-icons-directory' is nil or doesn't exist,
;;          your builtin will be displayed with text only.
;;
;; 5) (Facultative) Get the moon icons set:
;;    http://mercurial.intuxication.org/hg/xml-weather/file/66f18bcb2ed8/moon-icons2
;;    And
;;    (setq xml-weather-moon-icons-directory "Path/to/moon/icons/31X31")
;;    NOTE: If `xml-weather-moon-icons-directory' is not found, an empty image will be
;;    displayed.
;;
;; Usage:
;; =====
;; M-x xml-weather-today-at (you will have a button for forecast)
;; M-x xml-weather-forecast-at (go straight to forecast)
;; In these two functions you will have two prompt:
;; One (e.g CityName) where you enter the name of the place where you want meteo
;; and another where you will have completion for all the possible city names
;; xml-weather know.(Hit TAB ==> must match).
;; xml-weather.el provide a ticker that show a builtin for current conditions
;; all the `xml-weather-timer-delay' seconds.
;; You will have to set your current location with `xml-weather-default-id'.
;; You can fetch it with M-x xml-weather-show-id.
;; Run the ticker with M-x xml-weather-run-ticker
;; Stop timer with M-x xml-weather-ticker-cancel-timer.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;;; Change log:
;;
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;;; Code:

(eval-when-compile (require 'cl))
(require 'xml)
(require 'derived)
(require 'url)


;;;###autoload
(defvar xml-weather-format-id-url
  "http://xoap.weather.com/search/search?where=%s")

;;;###autoload
(defvar xml-weather-format-xml-from-id-url ; id, unit=m,day-forecast=5,login,key
  "http://xoap.weather.com/weather/local/%s?cc=*&unit=%s&dayf=%s&prod=xoap&par=%s&key=%s")

;;;###autoload
(defvar xml-weather-unit "m"
  "*m mean metric, you will have wind speed in km/h, temperature in °C and so on.")

;;;###autoload
(defvar xml-weather-temperature-sigle (if (equal xml-weather-unit "m") "°C" "°F")
  "*Temperature sigle to use depending you use metric or english system.")

;;;###autoload
(defvar xml-weather-wind-speed-sigle (if (equal xml-weather-unit "m") " Km/h" " Mp/h")
  "*Wind speed sigle to use depending you use metric or english system.")

;;;###autoload
(defvar xml-weather-login nil
  "*Your xml-weather Login.
You should not set this variable directly. See `xml-weather-authentify'.
If you have an xml-weather entry in ~/.authinfo, leave it to nil.")

;;;###autoload
(defvar xml-weather-key nil
  "*Your xml-weather key.
You should not set this variable directly. See `xml-weather-authentify'.
If you have an xml-weather entry in ~/.authinfo, leave it to nil.")

;;;###autoload
(defvar xml-weather-day-forecast-num 5
  "*Number of days for forecast; Maximum 5.")

;;;###autoload
(defvar xml-weather-default-show-message-times 1
  "*Number of times ticker will show message before stopping.")

;;;###autoload
(defvar xml-weather-default-location "Toulon, France"
  "*Your favorite location for xml-weather builtin.
You should get it with `xml-weather-show-id' to avoid error.")

;;;###autoload
(defvar xml-weather-default-id "FRXX0098"
  "*The ID corresponding to `xml-weather-default-location'.
You should get it with `xml-weather-show-id' to avoid error.")

;;;###autoload
(defvar xml-weather-timer-delay 3600
  "*Refresh the xml-weather Builtin all the `xml-weather-timer-delay' seconds.
Only used with ticker.")

;;;###autoload
(defvar xml-weather-default-icons-directory
  "/home/thierry/download/xml-weather-icons/icons/31x31"
  "Path to your icons directory given with the xml-weather kit.
You will have errors if you use another icons set than the xml-weather one.")

(defvar xml-weather-moon-icons-directory
  "~/download/xml-weather-icons/moon-icons2/31X31/"
  "The directory where your moon icons are.")

;;;###autoload
(defvar xml-weather-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map [?q] 'xml-weather-quit)
    (define-key map (kbd "<tab>") 'xml-weather-next-day)
    (define-key map (kbd "C-<tab>")   'xml-weather-precedent-day)
    (define-key map (kbd "S-<down>") 'xml-weather-next-day)
    (define-key map (kbd "S-<up>")   'xml-weather-precedent-day)
    (define-key map (kbd "<down>")   'xml-weather-next-button)
    (define-key map (kbd "<up>")     'xml-weather-precedent-button)
    (define-key map (kbd "<right>")  'xml-weather-press-button)
    (define-key map (kbd "<left>")   'xml-weather-toggle-today-forecast)
    map)
  "Keymap used for `xml-weather' commands.")

;;;###autoload
(define-derived-mode xml-weather-mode
    text-mode "xml-weather"
    "Major mode to get info from xml-weather.

Special commands:
\\{xml-weather-mode-map}")

;;;###autoload
(defun xml-weather-quit ()
  "Quit xml-weather without killing buffer."
  (interactive)
  (quit-window))

;;;###autoload
(defun xml-weather-next-day ()
  "Go to next day in xml-weather forecast."
  (interactive)
  (forward-char 1) (search-forward "*" nil t) (forward-line 0)
  (recenter 0))

;;;###autoload
(defun xml-weather-precedent-day ()
  "Go to precedent day in xml-weather forecast."
  (interactive)
  (forward-char -1) (search-backward "*" nil t) (forward-line 0)
  (recenter 0))

;;;###autoload
(defun xml-weather-next-button ()
  (interactive)
  (forward-button 1))

;;;###autoload
(defun xml-weather-precedent-button ()
  (interactive)
  (forward-button -1))

;;;###autoload
(defun xml-weather-press-button ()
  (interactive)
  (when (button-at (point))
    (push-button)))

(defvar xml-weather-today-buffer-p nil)
;;;###autoload
(defun xml-weather-toggle-today-forecast ()
  (interactive)
  (setq xml-weather-today-buffer-p (not xml-weather-today-buffer-p))
  (if xml-weather-today-buffer-p
      (xml-weather-forecast xml-weather-last-id)
      (xml-weather-now xml-weather-last-id)))

(defun xml-weather-authentify ()
  "Authentify user from .authinfo file.

You have to setup correctly `auth-source' to make this function
finding the path of your .authinfo file that is normally ~/.authinfo.
See \(info \"\(auth\)Top\"\).
Entry in .authinfo should be:

machine xoap.weather.com port http login xxxxx password xxxxxx

This function is intended to be called inside a `let' binding."
  (let ((xml-weather-auth
         (auth-source-user-or-password  '("login" "password")
                                        "xoap.weather.com"
                                        "http")))
    (when xml-weather-auth
      (setq xml-weather-login (car xml-weather-auth)
            xml-weather-key (cadr xml-weather-auth))
      nil)))

;; First step: Get ID of places
(defun xml-weather-get-place-id (place)
  "Return an alist of all ID corresponding to PLACE.
Each element is composed of a pair like \(\"Toulon, France\" . \"FRXX0098\"\)."
  (let* ((url              (format xml-weather-format-id-url place))
         (url-request-data (encode-coding-string place 'utf-8))
         (data             (with-current-buffer (url-retrieve-synchronously url)
                             (buffer-string))))
    (with-temp-buffer
      (erase-buffer)
      (insert data)
      (loop
         with l = (xml-get-children (car (xml-parse-region (point-min)
                                                           (point-max)))
                                    'loc)
         for i in l
         collect (cons (car (last i)) (xml-get-attribute i 'id))))))

;; Second step: Get the xml info for the ID choosen:
;; http://xoap.weather.com/weather/local/[locid]

;; Replace the [locid], of course, with the location ID obtained in the previous step.
;; Appended to this URL is a mix of other parameters,
;; some required and some optional. A typical example might be:

;; http://xoap.weather.com/weather/local/NLXX0002?cc=*&dayf=5
;; &prod=xoap&par=[partner id]&key=[license key]

(defun xml-weather-get-info-on-id (id)
  "Return an xml buffer with xml-weather infos on ID."
  (let* (xml-weather-login
         xml-weather-key
         (url  (progn
                 (unless (and xml-weather-login xml-weather-key)
                   (xml-weather-authentify))
                 (when (and xml-weather-login xml-weather-key)
                   (format xml-weather-format-xml-from-id-url
                           id
                           xml-weather-unit
                           xml-weather-day-forecast-num
                           xml-weather-login
                           xml-weather-key))))
         (data (when url (with-current-buffer (url-retrieve-synchronously url)
                           (buffer-string)))))
    (if data
        (with-current-buffer (get-buffer-create "*xml-weather*")
          (erase-buffer)
          (insert data))
        (error "Fail to retrieve data, please set up your login and password \
correctly or verify if your network is up."))))

;;;###autoload
(defun xml-weather-show-id (place)
  "Interactively show ID corresponding to PLACE."
  (interactive "sName: ")
  (let* ((id-list   (xml-weather-get-place-id place))
         (name-list (loop for i in id-list collect (car i)))
         (id-name   (completing-read "Choose a place: " name-list nil t))
         (id        (cdr (assoc id-name id-list))))
    (message "ID code for %s is %s" id-name id)))

(defun xml-weather-set-number-file-name (arg)
  "When ARG < 10 add a 0 before it.
ARG can be a string or a number."
  (let ((n (if (stringp arg) (string-to-number arg) arg)))
    (if (and (< n 10) (> n 0))
        (substring (int-to-string (/ (float n) 100)) 2)
        (int-to-string n))))

;; Third step convert xml info to alist
(defun xml-weather-get-alist ()
  "Parse the xml buffer and return an alist of all infos."
  (with-current-buffer "*xml-weather*"
    (let* ((loc           (xml-get-children (car (xml-parse-region (point-min)
                                                                   (point-max)))
                                            'loc))
           (cc            (xml-get-children (car (xml-parse-region (point-min)
                                                                   (point-max)))
                                            'cc))
           (info-cc       (loop for i in cc
                             for lsup = (caddr (assoc 'lsup i))
                             for obst = (caddr (assoc 'obst i))
                             for tmp = (caddr (assoc 'tmp i))
                             for flik = (caddr (assoc 'flik i))
                             for wea = (caddr (assoc 't i))
                             for icon = (xml-weather-set-number-file-name (caddr (assoc 'icon i)))
                             for bar = (caddr (assoc 'r (assoc 'bar i)))
                             for wind-dir-d = (caddr (assoc 'd (assoc 'wind i)))
                             for wind-dir = (caddr (assoc 't (assoc 'wind i)))
                             for gust = (caddr (assoc 'gust (assoc 'wind i)))
                             for moon = (caddr (assoc 't (assoc 'moon i)))
                             collect (list (cons "Date:" (or lsup ""))
                                           (cons "Observatory:" (or obst ""))
                                           (cons "Temperature:" (concat (or tmp "") xml-weather-temperature-sigle))
                                           (cons "Feel Like:" (concat (or flik "") xml-weather-temperature-sigle))
                                           (cons "Cond:" (or (list (concat icon ".png") wea) ""))
                                           (cons "Pression:" (or bar ""))
                                           (cons "Wind dir:" (or (concat wind-dir  "(" wind-dir-d "°)") ""))
                                           (cons "Gust:" (or gust ""))
                                           (cons "Moon:" (or moon "")))))
           (today-info    (loop for i in loc
                             for dnam = (caddr (assoc 'dnam i))
                             for tm = (caddr (assoc 'tm i))
                             for lat = (caddr (assoc 'lat i))
                             for lon = (caddr (assoc 'lon i))
                             for sunr = (caddr (assoc 'sunr i))
                             for suns = (caddr (assoc 'suns i))
                             collect (cons (concat dnam " " tm)
                                           (append (list (cons "Latitude:" (or lat ""))
                                                         (cons "Longitude: " (or lon ""))
                                                         (cons "Sunrise:" (or sunr ""))
                                                         (cons "Sunset:" (or suns "")))
                                                   (car info-cc)))))
           (dayf          (xml-get-children (car (xml-parse-region (point-min)
                                                                   (point-max)))
                                            'dayf))
           (day-list      (loop for i in (xml-get-children (car dayf) 'day)
                             collect i))
           (morning-alist (loop for i in day-list
                             for d = (concat (cdr (assoc 't (cadr i))) " " (cdr (assoc 'dt (cadr i))))
                             for hi-temp = (caddr (assoc 'hi (cdr i)))
                             for low-temp = (caddr (assoc 'low (cdr i)))
                             for sunr = (caddr (assoc 'sunr (cdr i)))
                             for suns = (caddr (assoc 'suns (cdr i)))
                             for wind-dir = (caddr (assoc 't (assoc 'wind (assoc 'part (cdr i)))))
                             for wind-spd = (caddr (assoc 's (assoc 'wind (assoc 'part (cdr i)))))
                             for wea = (caddr (assoc 't (assoc 'part (cdr i))))
                             for icon = (xml-weather-set-number-file-name (caddr (assoc 'icon (assoc 'part (cdr i)))))
                             for hmid = (caddr (assoc 'hmid (assoc 'part (cdr i))))
                             collect (cons d (list (cons "maxi:" (concat (or hi-temp "") xml-weather-temperature-sigle))
                                                   (cons "mini:" (concat (or low-temp "") xml-weather-temperature-sigle))
                                                   (cons "sunrise:" (or sunr ""))
                                                   (cons "sunset:" (or suns ""))
                                                   (cons "Wind direction:" (or wind-dir ""))
                                                   (cons "Wind speed:" (concat (or wind-spd "") xml-weather-wind-speed-sigle))
                                                   (cons "Cond:" (or (list (concat icon ".png") wea) ""))
                                                   (cons "Humidity:" (concat (or hmid "") "%"))))))
           (night-alist   (loop for i in day-list
                             for d = (concat (cdr (assoc 't (cadr i))) " " (cdr (assoc 'dt (cadr i))))
                             for hi-temp = (caddr (assoc 'hi (cdr i)))
                             for low-temp = (caddr (assoc 'low (cdr i)))
                             for sunr = (caddr (assoc 'sunr (cdr i)))
                             for suns = (caddr (assoc 'suns (cdr i)))
                             for all-part1 = (remove (assoc 'part (cdr i)) (cdr i))
                             for part2 = (assoc 'part all-part1)
                             for wind-dir = (caddr (assoc 't (assoc 'wind part2)))
                             for wind-spd = (caddr (assoc 's (assoc 'wind part2)))
                             for wea = (caddr (assoc 't part2))
                             for icon = (xml-weather-set-number-file-name (caddr (assoc 'icon (assoc 'part (cdr i)))))
                             for hmid = (caddr (assoc 'hmid part2))
                             collect (cons d (list (cons "maxi:" (concat (or hi-temp "") xml-weather-temperature-sigle))
                                                   (cons "mini:" (concat (or low-temp "") xml-weather-temperature-sigle))
                                                   (cons "sunrise:" (or sunr ""))
                                                   (cons "sunset:" (or suns ""))
                                                   (cons "Wind direction:" (or wind-dir ""))
                                                   (cons "Wind speed:" (concat (or wind-spd "") xml-weather-wind-speed-sigle))
                                                   (cons "Cond:" (or (list (concat icon ".png") wea) ""))
                                                   (cons "Humidity:" (concat (or hmid "") "%")))))))
      (setq morning-alist (cons 'morning morning-alist))
      (setq night-alist (cons 'night night-alist))
      (setq today-info (cons 'info today-info))
      (list today-info morning-alist night-alist))))

;; Last step pprint the infos in alist
(defun xml-weather-pprint-today ()
  "Print the xml-weather info of current day in *xml-weather-meteo* buffer."
  (let ((data (xml-weather-get-alist)))
    (with-current-buffer (get-buffer-create "*xml-weather-meteo*")
      (erase-buffer)
      (insert (propertize "* XML-WEATHER\n  ===========\n\n" 'face '((:foreground "Lightgreen"))))
      (loop for i in (cadr (assoc 'info data))
         if (listp i)
         do
           (xml-weather-insert-maybe-icons i)
         else
         do
           (insert (concat i "\n\n")))))
  (switch-to-buffer "*xml-weather-meteo*")
  (goto-char (point-max))
  (newline)
  (insert-button "[Forecast for next 4 days]"
                 'action 'xml-weather-button-func1
                 'face '((:background "green")))
  (newline 2)
  (insert-button "[New Search]"
                 'action 'xml-weather-button-func3
                 'face '((:background "green")))
  (newline 2)
  (insert-button "[Refresh]"
                 'action 'xml-weather-button-func4
                 'face '((:background "green")))
  (goto-char (point-min))
  (save-excursion
    (align-regexp (point-min) (point-max) "\\(:\\)" 1 1 nil))
  (xml-weather-mode))


(defun xml-weather-insert-maybe-icons (elm)
  "Insert infos in all entries of an xml-weather builtin.
Insert an icon in the Cond: entry only if `xml-weather-default-icons-directory' exists."
  (insert (concat "  " (car elm)))
  (let ((info (if (eq (safe-length elm) 1)
                  (cdr elm)
                  (car (last elm)))))
    (if info
        (cond ((and (file-exists-p xml-weather-default-icons-directory) (equal (car elm) "Cond:"))
               (let* ((fname (cadr elm))
                      (img   (unless (equal fname ".png")
                               (create-image (expand-file-name fname xml-weather-default-icons-directory)))))
                 (if img
                     (progn
                       (insert-image img)
                       (insert (propertize info 'face '((:foreground "red"))) "\n"))
                     (insert ""))))
              ((and (file-exists-p xml-weather-moon-icons-directory) (equal (car elm) "Moon:"))
               (let* ((lsname (split-string info))
                      (fname  (if (< (length lsname) 2)
                                  (concat (downcase (car lsname)) ".jpg")
                                  (concat (downcase (car lsname)) "_" (downcase (cadr lsname)) ".jpg")))
                      (img    (unless (or (not fname) (equal fname ".jpg"))
                                (create-image (expand-file-name fname xml-weather-moon-icons-directory)))))
                 (if img
                     (progn
                       (insert-image img)
                       (insert (propertize info 'face '((:foreground "red"))) "\n"))
                     (insert ""))))
              (t (insert (propertize info 'face '((:foreground "red"))) "\n")))
        (insert ""))))
    
(defun xml-weather-pprint-forecast (station)
  "Print the xml-weather info of forecast for STATION in *xml-weather-meteo* buffer."
  (let ((data (xml-weather-get-alist)))
    (with-current-buffer (get-buffer-create "*xml-weather-meteo*")
      (erase-buffer)
      (insert (propertize "* XML-WEATHER\n  ===========\n\n" 'face '((:foreground "Lightgreen"))))
      (insert (concat (propertize station 'face '((:foreground "magenta"))) "\n"))
      (loop
         for i in (assoc 'morning data)
         if (listp i) 
         do
           (loop
              for m in i
              if (listp m)
              do
                (xml-weather-insert-maybe-icons m)
              else
              do
                (insert (concat "\n* " (propertize m 'face '((:foreground "blue")))"\n\n"))
                (insert (propertize "Morning:\n" 'face '((:foreground "lightgreen")))))
         for j in (assoc 'night data)
         if (listp j)
         do
           (insert (propertize "Afternoon:\n" 'face '((:foreground "lightgreen"))))
           (loop
              for a in j
              if (listp a)
              do
                (xml-weather-insert-maybe-icons a)))
      (insert "\n\n")
      (insert-button "[Back To Today weather]"
                     'action 'xml-weather-button-func2
                     'face '((:background "green"))))
    (switch-to-buffer "*xml-weather-meteo*")
    (goto-char (point-min))
    (save-excursion
      (align-regexp (point-min) (point-max) "\\(:\\)" 1 1 nil))
    (xml-weather-mode)))

(defvar xml-weather-last-id nil
  "Remember the last ID used. it is a pair.")
(defun xml-weather-now (id-pair &optional update)
  "Call non interactively the pprinter for today weather."
  (let ((id      (cdr id-pair)))
    (setq xml-weather-last-id id-pair)
    (when update
      (xml-weather-get-info-on-id id))
    (xml-weather-pprint-today)))

(defun xml-weather-forecast (id-pair &optional update)
  "Call non interactively the pprinter for forecast."
  (let ((id      (cdr id-pair))
        (station (car id-pair)))
  (setq xml-weather-last-id id-pair)
  (when update
    (xml-weather-get-info-on-id  id))
  (xml-weather-pprint-forecast station)))

(defun xml-weather-button-func1 (button)
  "Function used by the forecast button."
  (setq xml-weather-today-buffer-p (not xml-weather-today-buffer-p))
  (xml-weather-forecast xml-weather-last-id))

(defun xml-weather-button-func2 (button)
  "Function used by the today weather button."
  (setq xml-weather-today-buffer-p (not xml-weather-today-buffer-p))
  (xml-weather-now xml-weather-last-id))

(defun xml-weather-button-func3 (button)
  "Function used by the search button."
  (let ((place (read-string "CityName: ")))
    (xml-weather-today-at place)))

(defun xml-weather-button-func4 (button)
  "Function used by the refresh button."
  (xml-weather-now xml-weather-last-id 'update))

;;;###autoload
(defun xml-weather-today-at (place)
  "Call interactively xml weather for meteo of today."
  (interactive "sCityName: ")
  (let* ((id-list   (xml-weather-get-place-id place))
         (name-list (loop for i in id-list collect (car i)))
         (id        (completing-read "Choose a place: " name-list nil t))
         (id-pair   (assoc id id-list)))
    (xml-weather-now id-pair 'update)))

;;;###autoload
(defun xml-weather-forecast-at (place)
  "Call interactively xml weather for forecast."
  (interactive "sCityName: ")
  (let* ((id-list   (xml-weather-get-place-id place))
         (name-list (loop for i in id-list collect (car i)))
         (id        (completing-read "Choose a place: " name-list nil t))
         (id-pair   (assoc id id-list)))
    (xml-weather-forecast id-pair 'update)))

;;;###autoload
(defun* xml-weather-today-favorite (&optional (location xml-weather-default-location)
                                              (id xml-weather-default-id)) 
  "Display an xml-weather builtin for `location'.
If `location' and/or `id' are nil, `xml-weather-today-at' will be used."
  (interactive)
  (if (and location
           id)
      (let ((id-pair (cons location
                           id)))
        (xml-weather-now id-pair 'update))
      (let ((place (read-string "CityName: ")))
        (xml-weather-today-at place))))

;;; xml-weather ticker
(defvar xml-weather-ticker-timer1 nil)
(defvar xml-weather-ticker-timer2 nil)

(defun xml-weather-get-today-list ()
  "Return a list that will be used to setup the ticker message.
The list is made with the current xml weather buffer."
  (let ((data (xml-weather-get-alist)))
    (loop for i in (cadr (assoc 'info data))
       if (listp i)
       collect (if (eq (safe-length i) 1)
                   (concat (car i) (cdr i))
                   (concat (car i) (car (last i)))) into a
       else
       collect i into b
       finally return (append a b))))

(defun xml-weather-get-today-string ()
  "Setup the message for ticker from the current xml buffer."
  (mapconcat 'identity (xml-weather-get-today-list) " | "))


(defun* xml-weather-message (&rest msg)
  "Send a rolling message of today xml-weather in minibuffer.
It will stop if keyboard is used or after `xml-weather-default-show-message-times'."
  (setq msg (concat "  <XML-WEATHER-BUILTIN>: "
                    (apply 'format msg) ; == (car msg)
                    "            "))
  (let* ((minibuf-size   (window-width (minibuffer-window)))
         (start-msg-size (+ 1 (length "[<] ")))
         (width          (- minibuf-size start-msg-size))
         (msglen         (length msg))
         submsg
         (count          0)
         (normal-range   (- msglen width)))
    (if (< msglen width)
        (message "%s" msg)
        (while t
          (when (> count xml-weather-default-show-message-times)
            (return-from xml-weather-message
              (when xml-weather-ticker-timer1
                (message "Next xml-weather Builtin in %d mn" (/ xml-weather-timer-delay 60)))))
          (incf count)
          (dotimes (i msglen)
            (setq submsg (if (< i normal-range)
                             (substring msg i (+ i width))
                             ;; Rolling is needed.
                             (concat (substring msg i)
                                     (substring msg 0 (- (+ i width) msglen)))))
            (message "[<] %s" submsg)
            (when (eq i 0) (incf count))
            (unless (sit-for (cond
                               ((eq i 0) 1.0)
                               (t 0.2)))
              (return-from xml-weather-message)))
          (garbage-collect)))))


(defun xml-weather-run-message-builtin (&optional id)
  "Run `xml-weather-message' with current infos.
If optional arg `id' is used refresh infos of this `id'."
  (when id
    (xml-weather-get-info-on-id id))
  (xml-weather-message (xml-weather-get-today-string)))

(defun xml-weather-start-ticker-timers ()
  "Start all timers used by `xml-weather-run-ticker'.
`xml-weather-ticker-timer1' update xml-weather buffer all the `xml-weather-timer-delay'
`xml-weather-ticker-timer2' run an idle timer every 120 sec."
  (setq xml-weather-ticker-timer1
        (run-with-timer 60
                        xml-weather-timer-delay
                        #'(lambda ()
                            (xml-weather-get-info-on-id xml-weather-default-id))))
  (setq xml-weather-ticker-timer2
        (run-with-idle-timer 120 'repeat
                             #'(lambda ()
                                 (xml-weather-run-message-builtin)))))
  
  
;;;###autoload
(defun xml-weather-run-ticker ()
  "Start interactively the xml weather timers for ticker.
A rolling message will be sent all the 65 sec and updated all the `xml-weather-timer-delay' sec."
  (interactive)
  (when (or xml-weather-ticker-timer1
            xml-weather-ticker-timer2)
    (xml-weather-ticker-cancel-timer))
  (xml-weather-start-ticker-timers))

;;;###autoload
(defun xml-weather-ticker-cancel-timer ()
  "Kill all the xml weather timers and stop ticker."
  (interactive)
  (cancel-timer xml-weather-ticker-timer1)
  (setq xml-weather-ticker-timer1 nil)
  (cancel-timer xml-weather-ticker-timer2)
  (setq xml-weather-ticker-timer2 nil))

;; Provide
(provide 'xml-weather)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; xml-weather.el ends here