Skip to content

Commit a97cf51

Browse files
authored
refactor(weather): Fix timeout, reduce calls, apply shellcheck (#327)
* refactor(weather): Fix timeout, reduce calls, apply shellcheck Fix Darwin timeout function duration argument. Remove unnecessary API calls to “ipinfo.io”. Confirm to Google Shell style guide and appease shellcheck. * fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck remove forgotten test string * fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck add return when unknown location * fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck update weather_wrapped local vars * fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck add curl --fail flag * fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck update fetch_weather_information argument comments, missing API_URLwq * fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck comment example raw response body * fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck change locale * fixup! refactor(weather): Fix timeout, reduce calls, apply shellcheck use tr for lowercase due to OSX bash3.2
1 parent dd1a7ab commit a97cf51

File tree

2 files changed

+126
-89
lines changed

2 files changed

+126
-89
lines changed

scripts/weather.sh

Lines changed: 103 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,129 @@
11
#!/usr/bin/env bash
22
# setting the locale, some users have issues with different locales, this forces the correct one
3-
export LC_ALL=C.utf8
3+
export LC_ALL=en_US.UTF-8
44

5-
fahrenheit=$1
6-
location=$2
7-
fixedlocation=$3
5+
API_URL="https://wttr.in"
6+
DELIM=":"
87

98
# emulate timeout command from bash - timeout is not available by default on OSX
109
if [ "$(uname)" == "Darwin" ]; then
11-
timeout() {
12-
perl -e 'alarm shift; exec @ARGV' "$duration" "$@"
10+
function timeout() {
11+
local _duration
12+
_duration="${1:-1}"
13+
command -p perl -e 'alarm shift; exec @ARGV' "$_duration" "$@"
1314
}
1415
fi
1516

16-
display_location()
17-
{
18-
if $location && [[ ! -z "$fixedlocation" ]]; then
19-
echo " $fixedlocation"
20-
elif $location; then
21-
city=$(curl -s https://ipinfo.io/city 2> /dev/null)
22-
region=$(curl -s https://ipinfo.io/region 2> /dev/null)
23-
echo " $city, $region"
24-
else
25-
echo ''
26-
fi
27-
}
17+
# Fetch weather information from remote API
18+
# Globals:
19+
# API_URL
20+
# DELIM
21+
# Arguments:
22+
# show fahrenheit, either "true" or "false"
23+
# optional fixed location to query weather data about
24+
function fetch_weather_information() {
25+
local _show_fahrenheit _location _unit
26+
_show_fahrenheit="$1"
27+
_location="$2"
2828

29-
fetch_weather_information()
30-
{
31-
display_weather=$1
32-
# it gets the weather condition textual name (%C), and the temperature (%t)
33-
api_response=$(curl -sL wttr.in/${fixedlocation// /%20}\?format="%C+%t$display_weather")
34-
35-
if [[ $api_response = "Unknown location;"* ]]; then
36-
echo "Unknown location error"
29+
if "$_show_fahrenheit"; then
30+
_unit="u"
3731
else
38-
echo $api_response
32+
_unit="m"
3933
fi
34+
35+
# If the user provies a "fixed location", `@dracula-fixed-location`, that the
36+
# API does not recognize, the API may suggest a users actual geoip GPS
37+
# location in the response body. This can lead to user PI leak.
38+
# Drop response body when status code >= 400 and return nonzero by passing the
39+
# `--fail` flag. Execute curl last to allow the consumer to leverage the
40+
# return code. Pass `--show-error` and redirect stderr for the consumer.
41+
command -p curl -L --silent --fail --show-error \
42+
"${API_URL}/${_location// /%20}?format=%C${DELIM}%t${DELIM}%l&${_unit}" 2>&1
4043
}
4144

42-
#get weather display
43-
display_weather()
44-
{
45-
if $fahrenheit; then
46-
display_weather='&u' # for USA system
47-
else
48-
display_weather='&m' # for metric system
49-
fi
50-
weather_information=$(fetch_weather_information $display_weather)
45+
# Format raw weather information from API
46+
# Globals:
47+
# DELIM
48+
# Arguments:
49+
# The raw weather data as returned by "fetch_weather_information()"
50+
# show location, either "true" or "false"
51+
function format_weather_info() {
52+
local _raw _show_location
53+
_raw="$1" # e.g. "Rain:+63°F:Houston, Texas, United States"
54+
_show_location="$2"
5155

52-
weather_condition=$(echo "$weather_information" | awk -F' -?[0-9]' '{print $1}' | xargs) # Extract condition before temperature, e.g. Sunny, Snow, etc
53-
temperature=$(echo "$weather_information" | grep -oE '[-+]?[0-9]+°[CF]') # Extract temperature, e.g. +31°C, -3°F, etc
54-
unicode=$(forecast_unicode $weather_condition)
56+
local _weather _temp _location
57+
_weather="${_raw%%"${DELIM}"*}" # slice temp and location to get weather
58+
_weather=$(printf '%s' "$_weather" | tr '[:upper:]' '[:lower:]') # lowercase weather, OSX’s bash3.2 does not support ${v,,}
59+
_temp="${_raw#*"${DELIM}"}" # slice weather to get temp and location
60+
_temp="${_temp%%"${DELIM}"*}" # slice location to get temp
61+
_temp="${_temp/+/}" # slice "+" from "+74°F"
62+
_location="${_raw##*"${DELIM}"}" # slice weather and temp to get location
63+
[ "${_location//[^,]/}" == ",," ] && _location="${_location%,*}" # slice country if it exists
5564

56-
# Mac Only variant should be transparent on Linux
57-
if [[ "${temperature/+/}" == *"===="* ]]; then
58-
temperature="error"
59-
fi
65+
case "$_weather" in
66+
'snow')
67+
_weather=''
68+
;;
69+
'rain' | 'shower')
70+
_weather=''
71+
;;
72+
'overcast' | 'cloud')
73+
_weather=''
74+
;;
75+
'na')
76+
_weather=''
77+
;;
78+
*)
79+
_weather=''
80+
;;
81+
esac
6082

61-
if [[ "${temperature/+/}" == "error" ]]; then
62-
# Propagate Error
63-
echo "error"
83+
if "$_show_location"; then
84+
printf '%s %s %s' "$_weather" "$_temp" "$_location"
6485
else
65-
echo "$unicode ${temperature/+/}" # remove the plus sign to the temperature
86+
printf '%s %s' "$_weather" "$_temp"
6687
fi
6788
}
6889

69-
forecast_unicode()
70-
{
71-
weather_condition=$(echo $weather_condition | awk '{print tolower($0)}')
72-
73-
if [[ $weather_condition =~ 'snow' ]]; then
74-
echo ''
75-
elif [[ (($weather_condition =~ 'rain') || ($weather_condition =~ 'shower')) ]]; then
76-
echo ''
77-
elif [[ (($weather_condition =~ 'overcast') || ($weather_condition =~ 'cloud')) ]]; then
78-
echo ''
79-
elif [[ $weather_condition = 'NA' ]]; then
80-
echo ''
81-
else
82-
echo ''
83-
fi
84-
}
90+
# Display weather, temperature, and location
91+
# Globals
92+
# none
93+
# Arguments
94+
# show fahrenheit, either "true" (default) or "false"
95+
# show location, either "true" (default) or "false"
96+
# optional fixed location to query data about, e.g. "Houston, Texas"
97+
function main() {
98+
local _show_fahrenheit _show_location _location
99+
_show_fahrenheit="${1:-true}"
100+
_show_location="${2:-true}"
101+
_location="$3"
85102

86-
main()
87-
{
88103
# process should be cancelled when session is killed
89-
if timeout 1 bash -c "</dev/tcp/ipinfo.io/443" && timeout 1 bash -c "</dev/tcp/wttr.in/443" && [[ "$(display_weather)" != "error" ]]; then
90-
echo "$(display_weather)$(display_location)"
91-
else
92-
echo "Weather Unavailable"
104+
if ! timeout 1 bash -c "</dev/tcp/wttr.in/443"; then
105+
printf 'Weather Unavailable\n'
106+
return
107+
fi
108+
109+
# BashFAQ/002: assignment of substitution does not effect status code.
110+
local _resp
111+
if ! _resp=$(fetch_weather_information "$_show_fahrenheit" "$_location"); then
112+
113+
# e.g. "curl: (22) The requested URL returned error: 404"
114+
case "${_resp##* }" in
115+
404)
116+
printf 'Unknown Location\n'
117+
;;
118+
*)
119+
printf 'Weather Unavailable\n'
120+
;;
121+
esac
122+
123+
return
93124
fi
125+
126+
format_weather_info "$_resp" "$_show_location"
94127
}
95128

96-
#run main driver program
97-
main
129+
main "$@"

scripts/weather_wrapper.sh

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,35 @@
22
# setting the locale, some users have issues with different locales, this forces the correct one
33
export LC_ALL=en_US.UTF-8
44

5-
#wrapper script for running weather on interval
6-
7-
fahrenheit=$1
8-
location=$2
9-
fixedlocation=$3
10-
11-
DATAFILE=/tmp/.dracula-tmux-data
5+
DATAFILE="/tmp/.dracula-tmux-data"
126
LAST_EXEC_FILE="/tmp/.dracula-tmux-weather-last-exec"
13-
RUN_EACH=1200
14-
TIME_NOW=$(date +%s)
15-
TIME_LAST=$(cat "${LAST_EXEC_FILE}" 2>/dev/null || echo "0")
7+
INTERVAL=1200
168

17-
main()
18-
{
19-
current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
9+
# Call weather script on interval to prevent exhausting remote API
10+
# Globals:
11+
# DATAFILE
12+
# LAST_EXEC_FILE
13+
# INTERVAL
14+
# Arguments:
15+
# show fahrenheit, either "true" (default) or "false"
16+
# show location, either "true" (default) or "false"
17+
# optional fixed location to query data about, e.g. "Houston, Texas"
18+
function main() {
19+
local _show_fahrenheit _show_location _location _current_dir _last _now
20+
_show_fahrenheit="$1"
21+
_show_location="$2"
22+
_location="$3"
23+
_current_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24+
_last=$(cat "$LAST_EXEC_FILE" 2>/dev/null || echo 0)
25+
_now=$(date +%s)
2026

21-
if [ "$(expr ${TIME_LAST} + ${RUN_EACH})" -lt "${TIME_NOW}" ]; then
27+
if (((_now - _last) > INTERVAL)); then
2228
# Run weather script here
23-
$current_dir/weather.sh $fahrenheit $location "$fixedlocation" > "${DATAFILE}"
24-
echo "${TIME_NOW}" > "${LAST_EXEC_FILE}"
29+
"${_current_dir}/weather.sh" "$_show_fahrenheit" "$_show_location" "$_location" >"${DATAFILE}"
30+
printf '%s' "$_now" >"${LAST_EXEC_FILE}"
2531
fi
2632

2733
cat "${DATAFILE}"
2834
}
2935

30-
#run main driver function
31-
main
36+
main "$@"

0 commit comments

Comments
 (0)