From 6912f0640040b2b13455e7b6499dd19b102b78ca Mon Sep 17 00:00:00 2001 From: Jereme Corrado Date: Sat, 30 Mar 2019 21:20:53 -0400 Subject: [PATCH 1/3] Weather: added support for overriding l10n of units (temp, speed) Closes #823 We accept -c or -m for Metric, as [c]elsius is an easy mnemonic; same thinking with -f or -i for Imperial units. Started some general cleanup. Will pull formatters out, next, before PR. --- src/yetibot/commands/weather.clj | 110 ++++++++++++------------- test/yetibot/test/commands/weather.clj | 22 +++-- 2 files changed, 71 insertions(+), 61 deletions(-) diff --git a/src/yetibot/commands/weather.clj b/src/yetibot/commands/weather.clj index 85bbf36f..a8261ea6 100644 --- a/src/yetibot/commands/weather.clj +++ b/src/yetibot/commands/weather.clj @@ -60,34 +60,31 @@ (defn c-to-f [c] (-> (* c 9/5) (+ 32) float)) (defn km-to-mi [km] (-> (/ km 1.609) float)) -(defn get-units +(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 - ;; 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}} - + :lbr imperial-units ;; Liberia + :mm imperial-units ;; Myanmar + :us imperial-units ;; The United States of America ;; THE ENTIRE REST OF THE WORLD - {:temp {:sym "C" :conv float} - :speed {:sym "km/h" :conv float}}))) + metric-units))) -(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 get-formatters + [unit cc] + (if (nil? unit) + (get-formatters-by-cc cc) + (if (= unit :i) + imperial-units + metric-units))) (defn fmt-location-title [{:keys [city_name state_code country_code]}] @@ -96,35 +93,26 @@ (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")))))) + [{fmt :temp} {temp :temp {:keys [icon code description]} :weather}] + (format "%s - %s" + (fmt temp) + (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)))) + [{fmt :temp} {app_temp :app_temp}] + (format "Feels like %s" (fmt app_temp))) (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))) + [{fmt :speed} {:keys [wind_spd wind_cdir]}] + (format "Winds %s %s" (fmt wind_spd) 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 +122,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 (get-formatters unit (:country_code cs))] + {:result/value (format-current formatters cs) :result/data cs})))) (defn default-weather-cmd @@ -156,24 +154,26 @@ (defn fmt-forecast-item "Format a forecast item like: date: min - max" - [cc {:keys [min_temp max_temp valid_date]}] + [{fmt :temp} {: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)))) + (fmt min_temp) + (fmt max_temp))) (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 + formatters (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/test/yetibot/test/commands/weather.clj b/test/yetibot/test/commands/weather.clj index bda39dab..f3aec172 100644 --- a/test/yetibot/test/commands/weather.clj +++ b/test/yetibot/test/commands/weather.clj @@ -23,16 +23,26 @@ :wind_spd 10 :wind_cdir "SSE"}) +(def formatters-us (get-formatters nil (:country_code loc-nyc))) +(def formatters-us-metric (get-formatters :c (:country_code loc-nyc))) + +(def formatters-ro (get-formatters nil (:country_code loc-bcr))) +(def formatters-ro-imperl (get-formatters :f (: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") + (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 loc-nyc) => "Feels like 77.0°F" - (fmt-feels-like loc-bcr) => "Feels like 100.0°C") + (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 loc-nyc) => "Winds 6.2 mph N" - (fmt-wind loc-bcr) => "Winds 10.0 km/h SSE")) + (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")) From f183865affd0623796fda1f51370663831e066db Mon Sep 17 00:00:00 2001 From: Jereme Corrado Date: Sat, 30 Mar 2019 21:26:22 -0400 Subject: [PATCH 2/3] Fixed new metric/imperial flag keywords --- test/yetibot/test/commands/weather.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/yetibot/test/commands/weather.clj b/test/yetibot/test/commands/weather.clj index f3aec172..2ecedcb4 100644 --- a/test/yetibot/test/commands/weather.clj +++ b/test/yetibot/test/commands/weather.clj @@ -24,10 +24,10 @@ :wind_cdir "SSE"}) (def formatters-us (get-formatters nil (:country_code loc-nyc))) -(def formatters-us-metric (get-formatters :c (:country_code loc-nyc))) +(def formatters-us-metric (get-formatters :m (:country_code loc-nyc))) (def formatters-ro (get-formatters nil (:country_code loc-bcr))) -(def formatters-ro-imperl (get-formatters :f (:country_code loc-bcr))) +(def formatters-ro-imperl (get-formatters :i (:country_code loc-bcr))) (facts "about fomatting fns" (fact fmt-location-title From b2d6a49a326d6e7c6ae3d3a7c079dc9505794075 Mon Sep 17 00:00:00 2001 From: Jereme Corrado Date: Sat, 30 Mar 2019 22:02:58 -0400 Subject: [PATCH 3/3] Weather: pulled formatters out to their own ns --- src/yetibot/commands/weather.clj | 77 +++------------------ src/yetibot/commands/weather/formatters.clj | 62 +++++++++++++++++ test/yetibot/test/commands/weather.clj | 43 ++++++------ 3 files changed, 94 insertions(+), 88 deletions(-) create mode 100644 src/yetibot/commands/weather/formatters.clj diff --git a/src/yetibot/commands/weather.clj b/src/yetibot/commands/weather.clj index a8261ea6..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,62 +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)) - -(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 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-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 fmt-feels-like - [{fmt :temp} {app_temp :app_temp}] - (format "Feels like %s" (fmt app_temp))) - -(defn fmt-wind - [{fmt :speed} {:keys [wind_spd wind_cdir]}] - (format "Winds %s %s" (fmt wind_spd) wind_cdir)) - (defn- format-current [formatters c] - (cons (fmt-location-title c) - (map #(% formatters c) [fmt-description - fmt-feels-like - fmt-wind]))) + (cons (fmt/location-title c) + (map #(% formatters c) [fmt/description + fmt/feels-like + fmt/wind]))) (defn current [loc] @@ -139,7 +90,7 @@ (or (error-response result) (let [{[cs] :data} result - formatters (get-formatters unit (:country_code cs))] + formatters (fmt/get-formatters unit (:country_code cs))] {:result/value (format-current formatters cs) :result/data cs})))) @@ -152,14 +103,6 @@ {: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" - [{fmt :temp} {:keys [min_temp max_temp valid_date]}] - (format "%s: %s - %s" - valid_date - (fmt min_temp) - (fmt max_temp))) - (defn forecast-cmd "weather forecast # look up forecast for by name or postal code, optional country code, -c or -f to force units" {:yb/cat #{:info}} @@ -169,11 +112,11 @@ (or (error-response result) (let [{:keys [city_name country_code data]} result - formatters (get-formatters unit country_code) - 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 formatters) + (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 2ecedcb4..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,26 +24,26 @@ :wind_spd 10 :wind_cdir "SSE"}) -(def formatters-us (get-formatters nil (:country_code loc-nyc))) -(def formatters-us-metric (get-formatters :m (:country_code loc-nyc))) +(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 (get-formatters nil (:country_code loc-bcr))) -(def formatters-ro-imperl (get-formatters :i (:country_code loc-bcr))) +(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 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")) + (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"))