weathernaut
Version:
CLI tool for querying the Weather Underground API
330 lines (288 loc) • 12.9 kB
JavaScript
// Generated by CoffeeScript 1.7.1
(function() {
var WeathernautDefaultFormatter, WeathernautStore, async, fs, handleError, path, request,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
require('colors');
request = require('request');
fs = require('fs');
path = require('path');
async = require('async');
handleError = function(func, err) {
console.error("Error in " + func + ": " + (err.toString().red));
return process.exit(1);
};
WeathernautStore = (function() {
WeathernautStore.prototype.apiKey = null;
WeathernautStore.prototype.cacheFile = null;
WeathernautStore.prototype.cache = null;
WeathernautStore.prototype.maxCacheAgeInMsec = null;
function WeathernautStore(apiKey) {
this.apiKey = apiKey;
this.getDataFor = __bind(this.getDataFor, this);
this.updateCacheFor = __bind(this.updateCacheFor, this);
this.writeMemoryCacheToDisk = __bind(this.writeMemoryCacheToDisk, this);
this.doAPIRequest = __bind(this.doAPIRequest, this);
this.cachedDataIsValidFor = __bind(this.cachedDataIsValidFor, this);
this.cachedDataIsNotStaleFor = __bind(this.cachedDataIsNotStaleFor, this);
this.cachedDataExistsFor = __bind(this.cachedDataExistsFor, this);
this.readCacheFile = __bind(this.readCacheFile, this);
this.cacheFile = path.join(process.env.HOME, '.weathernaut-cache');
this.maxCacheAgeInMsec = 1000 * 60 * 60 * 12;
this.cache = this.readCacheFile();
}
WeathernautStore.prototype.readCacheFile = function() {
var err;
if (!fs.existsSync(this.cacheFile)) {
fs.writeFileSync(this.cacheFile, JSON.stringify({}));
}
try {
return JSON.parse(fs.readFileSync(this.cacheFile).toString());
} catch (_error) {
err = _error;
return {};
}
};
WeathernautStore.prototype.cachedDataExistsFor = function(service, zipcode) {
var _ref, _ref1;
return ((_ref = this.cache) != null ? (_ref1 = _ref[service]) != null ? _ref1[zipcode] : void 0 : void 0) != null;
};
WeathernautStore.prototype.cachedDataIsNotStaleFor = function(service, zipcode) {
var _ref, _ref1, _ref2;
return (new Date().getTime() - ((_ref = this.cache) != null ? (_ref1 = _ref[service]) != null ? (_ref2 = _ref1[zipcode]) != null ? _ref2.retrievedOn : void 0 : void 0 : void 0)) < this.maxCacheAgeInMsec;
};
WeathernautStore.prototype.cachedDataIsValidFor = function(service, zipcode) {
return this.cachedDataExistsFor(service, zipcode) && this.cachedDataIsNotStaleFor(service, zipcode);
};
WeathernautStore.prototype.doAPIRequest = function(service, zipcode, callback) {
var url;
url = "http://api.wunderground.com/api/" + this.apiKey + "/" + service + "/q/" + zipcode + ".json";
return request(url, (function(_this) {
return function(err, response, body) {
var responseObj, _ref, _ref1;
if (err != null) {
handleError('doAPIRequest', err);
}
if ((response != null ? response.statusCode : void 0) !== 200) {
handleError('doAPIRequest', "http status code was " + (response != null ? (_ref = response.statusCode) != null ? _ref.toString().yellow : void 0 : void 0));
}
try {
responseObj = JSON.parse(body != null ? typeof body.toString === "function" ? body.toString() : void 0 : void 0);
if ((responseObj != null ? (_ref1 = responseObj.response) != null ? _ref1.error : void 0 : void 0) != null) {
throw new Error(responseObj.response.error.type);
}
responseObj.retrievedOn = new Date().getTime();
delete responseObj.response;
return callback(null, responseObj);
} catch (_error) {
err = _error;
return callback(err);
}
};
})(this));
};
WeathernautStore.prototype.writeMemoryCacheToDisk = function(callback) {
var err;
try {
fs.writeFileSync(this.cacheFile, JSON.stringify(this.cache));
return callback();
} catch (_error) {
err = _error;
handleError('writeMemoryCacheToDisk', err);
return callback(err);
}
};
WeathernautStore.prototype.updateCacheFor = function(service, zipcode, callback) {
return this.doAPIRequest(service, zipcode, (function(_this) {
return function(err, dataForZip) {
var _base;
if (err != null) {
return callback(err);
}
if ((_base = _this.cache)[service] == null) {
_base[service] = {};
}
_this.cache[service][zipcode] = dataForZip;
return _this.writeMemoryCacheToDisk(function(err) {
if (err) {
handleError('updateCacheFor', err);
}
return callback();
});
};
})(this));
};
WeathernautStore.prototype.getDataFor = function(service, zipcode, callback) {
if (this.cachedDataIsValidFor(service, zipcode)) {
return callback(null, this.cache[service][zipcode]);
} else {
return this.updateCacheFor(service, zipcode, (function(_this) {
return function(err) {
if (err) {
handleError('getDataFor', err);
}
return callback(err, _this.cache[service][zipcode]);
};
})(this));
}
};
return WeathernautStore;
})();
WeathernautDefaultFormatter = (function() {
function WeathernautDefaultFormatter() {
this.formatForecastDay = __bind(this.formatForecastDay, this);
this.formatForecastHeader = __bind(this.formatForecastHeader, this);
this.printForecastData = __bind(this.printForecastData, this);
this.formatAstronomicalLine = __bind(this.formatAstronomicalLine, this);
this.formatAstronomicalHeader = __bind(this.formatAstronomicalHeader, this);
this.printAstronomicalData = __bind(this.printAstronomicalData, this);
}
WeathernautDefaultFormatter.prototype.printAstronomicalData = function(zip, data) {
var err, formattedLines, header, line, lines, moon_phase, percentIlluminated, sunrise, sunset, _ref, _ref1, _ref2, _ref3;
try {
moon_phase = data.moon_phase;
percentIlluminated = moon_phase.percentIlluminated, sunrise = moon_phase.sunrise, sunset = moon_phase.sunset;
lines = [
{
label: 'moon: ',
data: "" + (percentIlluminated != null ? typeof percentIlluminated.toString === "function" ? percentIlluminated.toString() : void 0 : void 0) + "% full"
}, {
label: 'sunrise: ',
data: "" + (sunrise != null ? (_ref = sunrise.hour) != null ? typeof _ref.toString === "function" ? _ref.toString() : void 0 : void 0 : void 0) + ":" + (sunrise != null ? (_ref1 = sunrise.minute) != null ? typeof _ref1.toString === "function" ? _ref1.toString() : void 0 : void 0 : void 0)
}, {
label: 'sunset: ',
data: "" + (sunset != null ? (_ref2 = sunset.hour) != null ? typeof _ref2.toString === "function" ? _ref2.toString() : void 0 : void 0 : void 0) + ":" + (sunset != null ? (_ref3 = sunset.minute) != null ? _ref3.toString() : void 0 : void 0)
}
];
header = this.formatAstronomicalHeader(zip);
formattedLines = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = lines.length; _i < _len; _i++) {
line = lines[_i];
_results.push(this.formatAstronomicalLine(line));
}
return _results;
}).call(this);
console.log(header);
return console.log(formattedLines.join('\n'));
} catch (_error) {
err = _error;
return handleError('WeathernautDefaultFormatter::printAstronomicalData', err);
}
};
WeathernautDefaultFormatter.prototype.formatAstronomicalHeader = function(zipcode) {
return '***'.white.bold + (" astronomy for " + zipcode + " ").red.bold + '***'.white.bold;
};
WeathernautDefaultFormatter.prototype.formatAstronomicalLine = function(line) {
return ' --> '.white.bold + line.label.toString().red + ' ' + line.data.toString().white;
};
WeathernautDefaultFormatter.prototype.printForecastData = function(zip, data, maxDays) {
var day, err, formattedDays, header, highs, lows, spacesBetweenDays;
if (maxDays == null) {
maxDays = 10;
}
try {
header = this.formatForecastHeader(zip, maxDays);
formattedDays = (function() {
var _i, _len, _ref, _results;
_ref = data.forecast.simpleforecast.forecastday.splice(0, maxDays);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
day = _ref[_i];
_results.push(this.formatForecastDay(day));
}
return _results;
}).call(this);
spacesBetweenDays = 4;
spacesBetweenDays = Array(spacesBetweenDays + 1).join(' ');
highs = ((function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = formattedDays.length; _i < _len; _i++) {
day = formattedDays[_i];
_results.push(day.high);
}
return _results;
})()).join(spacesBetweenDays);
lows = ((function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = formattedDays.length; _i < _len; _i++) {
day = formattedDays[_i];
_results.push(day.low);
}
return _results;
})()).join(spacesBetweenDays);
return console.log([header, highs, lows].join('\n'));
} catch (_error) {
err = _error;
return handleError('WeathernautDefaultFormatter::printForecastData', err);
}
};
WeathernautDefaultFormatter.prototype.formatForecastHeader = function(zip, maxDays) {
return '***'.white.bold + (" " + maxDays + "-day forecast for " + zip + " ").red.bold + '***'.white.bold;
};
WeathernautDefaultFormatter.prototype.formatForecastDay = function(day) {
var dateNum, dateNumColored, dateNumInner, dateWeekday, high, length, low, padding;
low = day.low.fahrenheit.toString();
high = day.high.fahrenheit.toString();
dateNumInner = "" + day.date.month + "/" + day.date.day;
dateNum = "(" + dateNumInner + ") ";
dateNumColored = "(" + dateNumInner.cyan + ") ";
dateWeekday = "" + day.date.weekday_short + ": ";
length = [dateWeekday.length, ("(" + dateNumInner + ") ").length];
padding = [0, 0];
padding[0] = length[0] < length[1] ? length[1] - length[0] : 0;
padding[1] = length[0] > length[1] ? length[0] - length[1] : 0;
padding[0] = Array(padding[0] + 1).join(' ');
padding[1] = Array(padding[1] + 1).join(' ');
return {
high: "" + dateWeekday + padding[0] + high.red + "F",
low: "" + dateNumColored + padding[1] + low.blue.bold + "F"
};
};
return WeathernautDefaultFormatter;
})();
exports.Weathernaut = (function() {
Weathernaut.prototype.apiKey = null;
Weathernaut.prototype.store = null;
Weathernaut.prototype.formatter = null;
function Weathernaut(apiKey) {
this.apiKey = apiKey;
this.printForecastDataFor = __bind(this.printForecastDataFor, this);
this.printAstronomicalDataFor = __bind(this.printAstronomicalDataFor, this);
this.store = new WeathernautStore(this.apiKey);
this.formatter = new WeathernautDefaultFormatter();
}
Weathernaut.VALID_SERVICES = {
astronomy: 'astronomy',
forecast: 'forecast10day'
};
Weathernaut.validServices = function() {
return Object.keys(Weathernaut.VALID_SERVICES);
};
Weathernaut.prototype.printAstronomicalDataFor = function(zipcode) {
return this.store.getDataFor(Weathernaut.VALID_SERVICES.astronomy, zipcode, (function(_this) {
return function(err, data) {
if (err != null) {
return handleError('Weathernaut::printAstronomicalDataFor', err);
} else {
return _this.formatter.printAstronomicalData(zipcode, data);
}
};
})(this));
};
Weathernaut.prototype.printForecastDataFor = function(zipcode, maxDays) {
return this.store.getDataFor(Weathernaut.VALID_SERVICES.forecast, zipcode, (function(_this) {
return function(err, data) {
if (err != null) {
return handleError('Weathernaut::printForecastDataFor', err);
} else {
return _this.formatter.printForecastData(zipcode, data, maxDays);
}
};
})(this));
};
return Weathernaut;
})();
}).call(this);