diff --git a/src/yetibot/commands/weather.clj b/src/yetibot/commands/weather.clj index 85bbf36f..b97da0eb 100644 --- a/src/yetibot/commands/weather.clj +++ b/src/yetibot/commands/weather.clj @@ -6,7 +6,8 @@ [taoensso.timbre :refer [info warn error]] [yetibot.core.config :refer [get-config]] [yetibot.core.hooks :refer [cmd-hook]] - [yetibot.models.postal-code :refer [chk-postal-code]])) + [yetibot.models.postal-code :refer [chk-postal-code]] + [yetibot.commands.weather.formatters :as fmt])) (def config (:value (get-config sch/Any [:weather :weatherbitio]))) @@ -57,74 +58,12 @@ error {:result/error error} (= 429 status_code) {:result/error status_message})) -(defn c-to-f [c] (-> (* c 9/5) (+ 32) float)) -(defn km-to-mi [km] (-> (/ km 1.609) float)) - -(defn get-units - [cc] - (let [cc (-> cc str/lower-case keyword)] - (condp = cc - ;; Liberia - :lbr {:temp {:sym "F" :conv c-to-f} - :speed {:sym "mph" :conv km-to-mi}} - - ;; Myanmar - :mm {:temp {:sym "F" :conv c-to-f} - :speed {:sym "mph" :conv km-to-mi}} - - ;; The United States of America - :us {:temp {:sym "F" :conv c-to-f} - :speed {:sym "mph" :conv km-to-mi}} - - ;; THE ENTIRE REST OF THE WORLD - {:temp {:sym "C" :conv float} - :speed {:sym "km/h" :conv float}}))) - -(defn- l10n-value - [v u cc] - (let [{:keys [sym conv]} (get (get-units cc) u)] - [(conv v) sym])) - -(defn- l10n-temp [temp cc] (l10n-value temp :temp cc)) - -(defn- l10n-speed [speed cc] (l10n-value speed :speed cc)) - -(defn fmt-location-title - [{:keys [city_name state_code country_code]}] - (let [loc (if (re-matches #"\d+" state_code) - city_name - (str city_name ", " state_code))] - (format "%s (%s)" loc country_code))) - -(defn fmt-temp - [temp unit] - (format "%.1f°%s" temp unit)) - -(defn fmt-description - [{cc :country_code temp :temp {:keys [icon code description]} :weather}] - (let [[temp unit] (l10n-temp temp cc)] - (format "%s - %s" - (fmt-temp temp unit) - (str/join (map str/capitalize (str/split description #"\b")))))) - -(defn fmt-feels-like - [{cc :country_code app_temp :app_temp}] - (let [[app_temp unit] (l10n-temp app_temp cc)] - (format "Feels like %s" - (fmt-temp app_temp unit)))) - -(defn fmt-wind - [{cc :country_code :keys [wind_spd wind_cdir]}] - (let [[wind_spd unit] (l10n-speed wind_spd cc)] - (format "Winds %.1f %s %s" - wind_spd unit wind_cdir))) - (defn- format-current - [c] - (map #(% c) [fmt-location-title - fmt-description - fmt-feels-like - fmt-wind])) + [formatters c] + (cons (fmt/location-title c) + (map #(% formatters c) [fmt/description + fmt/feels-like + fmt/wind]))) (defn current [loc] @@ -134,15 +73,25 @@ [loc] (get-by-loc "forecast/daily" loc)) +(defn parse-args + "parse args to vec of unit kw and args str" + [s] + (let [[_ unit args] (re-matches #"(?i)(?:\s*(-[micf]))?\s*(.+)", s) + unit (when-not (nil? unit) + (if (or (= unit "-i") (= unit "-f")) :i :m))] + [unit args])) + (defn weather-cmd - "weather # look up current weather for by name or postal code, with optional country code" + "weather # look up current weather for by name or postal code, optional country code, -c or -f to force units" {:yb/cat #{:info}} [{:keys [match]}] - (let [result (current match)] + (let [[unit loc] (parse-args match) + result (current loc)] (or (error-response result) - (let [{[cs] :data} result] - {:result/value (format-current cs) + (let [{[cs] :data} result + formatters (fmt/get-formatters unit (:country_code cs))] + {:result/value (format-current formatters cs) :result/data cs})))) (defn default-weather-cmd @@ -154,26 +103,20 @@ {:result/error "A default zip code is not configured. Configure it at path weather.weatherbitio.default.zip"})) -(defn fmt-forecast-item - "Format a forecast item like: date: min - max" - [cc {:keys [min_temp max_temp valid_date]}] - (format "%s: %s - %s" - valid_date - (apply fmt-temp (l10n-temp min_temp cc)) - (apply fmt-temp (l10n-temp max_temp cc)))) - (defn forecast-cmd - "weather forecast # look up forecast for by name or postal code, with optional country code" + "weather forecast # look up forecast for by name or postal code, optional country code, -c or -f to force units" {:yb/cat #{:info}} [{[_ match] :match}] - (let [result (forecast match)] + (let [[unit loc] (parse-args match) + result (forecast loc)] (or (error-response result) (let [{:keys [city_name country_code data]} result - location (fmt-location-title result)] + formatters (fmt/get-formatters unit country_code) + location (fmt/location-title result)] {:result/value (into [location] (map - (partial fmt-forecast-item country_code) + (partial fmt/forecast-item formatters) data)) :result/data result})))) diff --git a/src/yetibot/commands/weather/formatters.clj b/src/yetibot/commands/weather/formatters.clj new file mode 100644 index 00000000..4bbb4f20 --- /dev/null +++ b/src/yetibot/commands/weather/formatters.clj @@ -0,0 +1,62 @@ +(ns yetibot.commands.weather.formatters + (:require + [clojure.string :as str])) + +(defn c-to-f [c] (-> (* c 9/5) (+ 32) float)) + +(defn km-to-mi [km] (-> (/ km 1.609) float)) + +(def imperial-units + {:temp (fn [v] (format "%.1f°%s" (c-to-f v) "F")) + :speed (fn [v] (format "%.1f %s" (km-to-mi v) "mph"))}) + +(def metric-units + {:temp (fn [v] (format "%.1f°%s" (float v) "C")) + :speed (fn [v] (format "%.1f %s" (float v) "km/h"))}) + +(defn- get-formatters-by-cc + [cc] + (let [cc (-> cc str/lower-case keyword)] + (condp = cc + :lbr imperial-units ;; Liberia + :mm imperial-units ;; Myanmar + :us imperial-units ;; The United States of America + ;; THE ENTIRE REST OF THE WORLD + metric-units))) + +(defn get-formatters + [unit cc] + (if (nil? unit) + (get-formatters-by-cc cc) + (if (= unit :i) + imperial-units + metric-units))) + +(defn location-title + [{:keys [city_name state_code country_code]}] + (let [loc (if (re-matches #"\d+" state_code) + city_name + (str city_name ", " state_code))] + (format "%s (%s)" loc country_code))) + +(defn description + [{fmt :temp} {temp :temp {:keys [icon code description]} :weather}] + (format "%s - %s" + (fmt temp) + (str/join (map str/capitalize (str/split description #"\b"))))) + +(defn feels-like + [{fmt :temp} {app_temp :app_temp}] + (format "Feels like %s" (fmt app_temp))) + +(defn wind + [{fmt :speed} {:keys [wind_spd wind_cdir]}] + (format "Winds %s %s" (fmt wind_spd) wind_cdir)) + +(defn forecast-item + "Format a forecast item like: date: min - max" + [{fmt :temp} {:keys [min_temp max_temp valid_date]}] + (format "%s: %s - %s" + valid_date + (fmt min_temp) + (fmt max_temp))) diff --git a/test/yetibot/test/commands/weather.clj b/test/yetibot/test/commands/weather.clj index bda39dab..a0412ef3 100644 --- a/test/yetibot/test/commands/weather.clj +++ b/test/yetibot/test/commands/weather.clj @@ -1,7 +1,8 @@ (ns yetibot.test.commands.weather (:require [midje.sweet :refer [facts fact =>]] - [yetibot.commands.weather :refer :all])) + [yetibot.commands.weather :refer :all] + [yetibot.commands.weather.formatters :as fmt])) (def loc-nyc {:city_name "New York" @@ -23,16 +24,26 @@ :wind_spd 10 :wind_cdir "SSE"}) +(def formatters-us (fmt/get-formatters nil (:country_code loc-nyc))) +(def formatters-us-metric (fmt/get-formatters :m (:country_code loc-nyc))) + +(def formatters-ro (fmt/get-formatters nil (:country_code loc-bcr))) +(def formatters-ro-imperl (fmt/get-formatters :i (:country_code loc-bcr))) + (facts "about fomatting fns" - (fact fmt-location-title - (fmt-location-title loc-nyc) => "New York, NY (US)" - (fmt-location-title loc-bcr) => "Bucharest (RO)") - (fact fmt-description - (fmt-description loc-nyc) => "32.0°F - Titlecase Me" - (fmt-description loc-bcr) => "50.0°C - Titlecase Me") - (fact fmt-feels-like - (fmt-feels-like loc-nyc) => "Feels like 77.0°F" - (fmt-feels-like loc-bcr) => "Feels like 100.0°C") - (fact fmt-wind - (fmt-wind loc-nyc) => "Winds 6.2 mph N" - (fmt-wind loc-bcr) => "Winds 10.0 km/h SSE")) + (fact fmt/location-title + (fmt/location-title loc-nyc) => "New York, NY (US)" + (fmt/location-title loc-bcr) => "Bucharest (RO)") + (fact fmt/description + (fmt/description formatters-us loc-nyc) => "32.0°F - Titlecase Me" + (fmt/description formatters-us-metric loc-nyc) => "0.0°C - Titlecase Me" + (fmt/description formatters-ro loc-bcr) => "50.0°C - Titlecase Me" + (fmt/description formatters-ro-imperl loc-bcr) => "122.0°F - Titlecase Me") + (fact fmt/feels-like + (fmt/feels-like formatters-us loc-nyc) => "Feels like 77.0°F" + (fmt/feels-like formatters-ro loc-bcr) => "Feels like 100.0°C") + (fact fmt/wind + (fmt/wind formatters-us loc-nyc) => "Winds 6.2 mph N" + (fmt/wind formatters-us-metric loc-nyc) => "Winds 10.0 km/h N" + (fmt/wind formatters-ro loc-bcr) => "Winds 10.0 km/h SSE" + (fmt/wind formatters-ro-imperl loc-bcr) => "Winds 6.2 mph SSE"))