You are not logged in.
Pages: 1
I have been displeased with the state of CLI weather programs of late (since weatherman broke when the underlying API changed). As there are clearly not enough of these darn tings, I bring you another.
This program, written in Python, will fetch weather from the Weather Underground ( http://www.wunderground.com/ ) Disclaimer. I have no affiliation with Weather Underground.
EDIT: See post #4 for an improved version of this code
#! /usr/bin/python
"""
Obtain cuurent weather from Weather Underground
Copyright 2015 Eric Waller
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 of the License, 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. If not, see <http://www.gnu.org/licenses/>.
"""
from urllib.request import urlopen
import json
import time
import shelve
import os
logger = None
class Weather:
# Define the fields of interest. Each field is a tuple of four items -- (1) a tuple that defines a path to the field, (2) an string prefix
# that is printed before the field value, (3) a string that is printed after the field value and (4) a flag that, if not None,
# is a string that defines whether the field is valid for imperial or metric units. .
# The tuple that defines the key path (first item in the field)
# defines the path (tree) of keys for the field of interest. The size of the tuples for the keys
# are variable in length as this json scheme uses multiple layers of hierarchical dictionaries.
# All of the entries are keys that point to dictionaries with the
# exception of the last, which is a key to a string, int, float, in the second to last dictionary.
fields=[ ( ('current_observation','display_location','full') ,'Location : ' ,'\n' ,None),
( ('current_observation','local_time_rfc822') ,'' ,'\n' ,None),
( ('current_observation','weather') ,'Condition : ' ,'\n' ,None),
( ('current_observation','temperature_string') ,'Temperature :' ,'\n' ,None),
( ('current_observation','relative_humidity') ,'Humidity :' ,' / ' ,None),
( ('current_observation','dewpoint_string') ,'Dewpoint :' ,'\n' ,None),
( ('current_observation','pressure_in') ,'Pressure :' ,' in hg. ','imperial'),
( ('current_observation','pressure_mb') ,'Pressure :' ,' mb ' ,'metric'),
( ('current_observation','pressure_trend') ,'Trend ' ,'\n' ,None),
( ('current_observation','visibility_mi') ,'Visibility :' ,'mi\n' ,'imperial'),
( ('current_observation','visibility_km') ,'Visibility :' ,'km\n' ,'metric'),
( ('current_observation','wind_dir') ,'Wind: ' , '' ,None),
( ('current_observation','wind_mph') ,' @ ' ,' mph\n' ,'imperial'),
( ('current_observation','wind_kph') ,' @ ' ,' kph\n' ,'metric'),
( ('current_observation','precip_today_string') ,'precip :' ,'\n' ,None),
( ('moon_phase','phaseofMoon') ,'Moon : ' ,'' ,None),
( ('moon_phase','percentIlluminated') ,' ' ,'%\n' ,None),
( ('sun_phase','sunrise','hour') ,'Sunrise : ' ,'' ,None),
( ('sun_phase','sunrise','minute') ,':' ,' ; ' ,None),
( ('sun_phase','sunset','hour') ,'Sunset : ' ,'' ,None),
( ('sun_phase','sunset','minute') ,':' ,'\n' ,None)
]
def __init__(self,theLocation,theKey,imperial):
self.location=theLocation
self.key=theKey
self.imperial = imperial
logger.print("location set to "+self.location)
logger.print("Units are "+( "imperial" if self.imperial else "metric"))
logger.print("Fetching weather data using API key "+self.key)
def GetWeather(self):
""" Get the json information for the current location
returns: A dictionary of items in the json file
"""
url='http://api.wunderground.com/api/'+self.key+'/conditions/astronomy/q/'+self.location+'.json'
logger.print("Retrieving weather report from "+url)
response =urlopen(url)
theString = response.read().decode('utf-8')
theDict= json.loads(theString)
logger.print("json response received:"+theString)
return (theDict)
def PrintReport(self,theWeather):
"""Generate a report of the current weather using 'interesting' json fields """
logger.print("Generating report")
for x in self.fields:
theThing=theWeather
for y in x[0]:
theThing=theThing[y]
if not type(theThing) is str:
theThing=theThing.__str__()
if not x[3] or (self.imperial and (x[3] == 'imperial')) or ((not self.imperial) and (x[3]=='metric')):
print (x[1]+theThing,end=x[2])
print("Weather data by Weather Underground\n(http://www.wunderground.com)")
class log:
""" Tools for optionally logging data
The rather than using 'print' for debugging, debug output are processed here
where they may turned on or off by a parameter passed in at the time the class is
instantiated. Messages sent here are time tied to the time the instance was created"""
def __init__(self,enabled):
self.enabled=enabled
self.initialtime=time.time()
def print(self,theData):
if self.enabled:
print ("{:0>9.7f} : ".format(time.time()-self.initialtime)+theData)
def main():
global logger
from argparse import ArgumentParser
# the variable theParameters defines the command line options, where and how their data are stored, and define the relation
# of the command line parameters to things that are stored in persistent storage. Element 0 is the member name in the ArgumentPaser object,
# element 1 is the action, element 2 is the short option name, element 3 is the long option name,
# element 4 is the key name for storage in theShelve (None implies the value is not stored)
# element 5 is the help string, and element 6 is the error message if the element is not set (None implies it is not required)
theParameters=[
('verbose' ,'store_true' ,'-v','--verbose' , None ,"Generate debugging information" ,None),
('api_key' ,'store' ,'-k','--key' , 'APIkey' ,"Set and store the API key" ,"API key not set"),
('location' ,'store' ,'-l','--location' , 'location' ,"Set and store the location" ,"Location not set"),
('units' ,'store_true' ,'-i','--imperial' , 'units' ,"Set and store choice of Imperial units" ,"Units not set (Imperial/Metric)"),
('units' ,'store_false' ,'-m','--metric' , 'units' ,"Set and store choice of Metric units" ,"Units not set (Imperial/Metric)"),
]
# open the persistent storage and create any missing keys
theShelve = shelve.open(os.environ['HOME'] + '/.wunderground')
for x in theParameters:
if x[4]:
if not x[4] in theShelve:
theShelve[x[4]]=None
# Handle all the command line nonsense.
# There are five options -- one is to set the location, one is to set the API key, two to set the
# unit system that is desired, and one to enable verbose reporting.
# Defaults come from persistent storage
description = "Fetch weather from Weather Underground"
parser = ArgumentParser(description=description)
for x in theParameters:
parser.add_argument(x[2],x[3], action=x[1], dest=x[0], help=x[5] , default = theShelve[x[4]] if x[4] else False)
args = parser.parse_args()
# Set up the log function and enable the output if the user wants it
logger=log(args.verbose)
# If anything needs to be updated in persistent storage, then do so.
for x in theParameters:
if x[4] != None:
if theShelve[x[4]] != getattr(args,x[0]):
theShelve[x[4]] = getattr(args,x[0])
result = theShelve[x[4]]
if type(result) != str:
result = result.__str__()
logger.print('Persistent storage updated: '+x[4]+" set to "+ result)
# If we have all the data we need, then proceed, die otherwise
for x in theParameters:
if x[4] != None:
if theShelve[x[4]] == None:
parser.error(x[6])
# Here is where the magic happens.
weather = Weather(theShelve['location'],theShelve['APIkey'],theShelve['units'])
theWeather = weather.GetWeather()
weather.PrintReport(theWeather)
logger.print("Done")
theShelve.close()
if __name__ == "__main__":
main()
Examples:
ewaller@turing ~/devel/python 1188 %./wunderground.py -h
usage: wunderground.py [-h] [-v] [-k API_KEY] [-l LOCATION] [-i] [-m]
Fetch weather from Weather Underground
optional arguments:
-h, --help show this help message and exit
-v, --verbose Generate debugging information
-k API_KEY, --key API_KEY
Set and store the API key
-l LOCATION, --location LOCATION
Set and store the location
-i, --imperial Set and store choice of Imperial units
-m, --metric Set and store choice of Metric units
ewaller@turing ~/devel/python 1189 %./wunderground.py
Location : Pasadena, CA
Sun, 06 Mar 2016 10:21:57 -0800
Condition : Mostly Cloudy
Temperature :57.0 F (13.9 C)
Humidity :77% / Dewpoint :50 F (10 C)
Pressure :29.98 in hg. Trend +
Visibility :10.0mi
Wind: East @ 0.0 mph
precip :1.52 in (39 mm)
Moon : Waning Crescent 7%
Sunrise : 6:14 ; Sunset : 17:53
Weather data by Weather Underground
(http://www.wunderground.com)
ewaller@turing ~/devel/python 1190 %./wunderground.py -m
Location : Pasadena, CA
Sun, 06 Mar 2016 10:23:10 -0800
Condition : Mostly Cloudy
Temperature :57.2 F (14.0 C)
Humidity :79% / Dewpoint :51 F (10 C)
Pressure :1015 mb Trend +
Visibility :16.1km
Wind: SE @ 1.4 kph
precip :1.52 in (39 mm)
Moon : Waning Crescent 7%
Sunrise : 6:14 ; Sunset : 17:53
Weather data by Weather Underground
(http://www.wunderground.com)
ewaller@turing ~/devel/python 1191 %./wunderground.py -l 91324
Location : Northridge, CA
Sun, 06 Mar 2016 10:23:21 -0800
Condition : Clear
Temperature :61.7 F (16.5 C)
Humidity :61% / Dewpoint :48 F (9 C)
Pressure :1015 mb Trend +
Visibility :16.1km
Wind: NNW @ 10.8 kph
precip :0.83 in (21 mm)
Moon : Waning Crescent 7%
Sunrise : 6:15 ; Sunset : 17:55
Weather data by Weather Underground
(http://www.wunderground.com)
ewaller@turing ~/devel/python 1192 %./wunderground.py -m -v
0.0000501 : location set to 91324
0.0000763 : Units are metric
0.0000925 : Fetching weather data using API key <REDACTED>
0.0001001 : Retrieving weather report from http://api.wunderground.com/api/<REDACTED>/conditions/astronomy/q/91324.json
0.3449099 : json response received:
{
"response": {
"version":"0.1",
"termsofService":"http://www.wunderground.com/weather/api/d/terms.html",
"features": {
"conditions": 1
,
"astronomy": 1
}
}
, "current_observation": {
"image": {
"url":"http://icons.wxug.com/graphics/wu2/logo_130x80.png",
"title":"Weather Underground",
"link":"http://www.wunderground.com"
},
"display_location": {
"full":"Northridge, CA",
"city":"Northridge",
"state":"CA",
"state_name":"California",
"country":"US",
"country_iso3166":"US",
"zip":"91324",
"magic":"1",
"wmo":"99999",
"latitude":"34.24044037",
"longitude":"-118.54892731",
"elevation":"261.00000000"
},
"observation_location": {
"full":"Northridge Center, Los Angeles, California",
"city":"Northridge Center, Los Angeles",
"state":"California",
"country":"US",
"country_iso3166":"US",
"latitude":"34.232887",
"longitude":"-118.539673",
"elevation":"833 ft"
},
"estimated": {
},
"station_id":"KCALOSAN160",
"observation_time":"Last Updated on March 6, 10:23 AM PST",
"observation_time_rfc822":"Sun, 06 Mar 2016 10:23:22 -0800",
"observation_epoch":"1457288602",
"local_time_rfc822":"Sun, 06 Mar 2016 10:23:37 -0800",
"local_epoch":"1457288617",
"local_tz_short":"PST",
"local_tz_long":"America/Los_Angeles",
"local_tz_offset":"-0800",
"weather":"Clear",
"temperature_string":"61.7 F (16.5 C)",
"temp_f":61.7,
"temp_c":16.5,
"relative_humidity":"60%",
"wind_string":"From the WNW at 4.5 MPH Gusting to 7.4 MPH",
"wind_dir":"WNW",
"wind_degrees":286,
"wind_mph":4.5,
"wind_gust_mph":"7.4",
"wind_kph":7.2,
"wind_gust_kph":"11.9",
"pressure_mb":"1015",
"pressure_in":"29.99",
"pressure_trend":"+",
"dewpoint_string":"48 F (9 C)",
"dewpoint_f":48,
"dewpoint_c":9,
"heat_index_string":"NA",
"heat_index_f":"NA",
"heat_index_c":"NA",
"windchill_string":"NA",
"windchill_f":"NA",
"windchill_c":"NA",
"feelslike_string":"61.7 F (16.5 C)",
"feelslike_f":"61.7",
"feelslike_c":"16.5",
"visibility_mi":"10.0",
"visibility_km":"16.1",
"solarradiation":"149",
"UV":"0.0","precip_1hr_string":"0.00 in ( 0 mm)",
"precip_1hr_in":"0.00",
"precip_1hr_metric":" 0",
"precip_today_string":"0.83 in (21 mm)",
"precip_today_in":"0.83",
"precip_today_metric":"21",
"icon":"clear",
"icon_url":"http://icons.wxug.com/i/c/k/clear.gif",
"forecast_url":"http://www.wunderground.com/US/CA/Northridge.html",
"history_url":"http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=KCALOSAN160",
"ob_url":"http://www.wunderground.com/cgi-bin/findweather/getForecast?query=34.232887,-118.539673",
"nowcast":""
}
, "moon_phase": {
"percentIlluminated":"7",
"ageOfMoon":"27",
"phaseofMoon":"Waning Crescent",
"hemisphere":"North",
"current_time": {
"hour":"10",
"minute":"23"
},
"sunrise": {
"hour":"6",
"minute":"15"
},
"sunset": {
"hour":"17",
"minute":"55"
},
"moonrise": {
"hour":"4",
"minute":"28"
},
"moonset": {
"hour":"15",
"minute":"37"
}
},
"sun_phase": {
"sunrise": {
"hour":"6",
"minute":"15"
},
"sunset": {
"hour":"17",
"minute":"55"
}
}
}
0.3451507 : Generating report
Location : Northridge, CA
Sun, 06 Mar 2016 10:23:37 -0800
Condition : Clear
Temperature :61.7 F (16.5 C)
Humidity :60% / Dewpoint :48 F (9 C)
Pressure :1015 mb Trend +
Visibility :16.1km
Wind: WNW @ 7.2 kph
precip :0.83 in (21 mm)
Moon : Waning Crescent 7%
Sunrise : 6:15 ; Sunset : 17:55
Weather data by Weather Underground
(http://www.wunderground.com)
0.3453128 : Done
ewaller@turing ~/devel/python 1193 %
ewaller@turing ~/devel/python 1193 %./wunderground.py -i -l 91104
Location : Pasadena, CA
Sun, 06 Mar 2016 10:23:10 -0800
Condition : Mostly Cloudy
Temperature :57.2 F (14.0 C)
Humidity :79% / Dewpoint :51 F (10 C)
Pressure :29.98 in hg. Trend +
Visibility :10.0mi
Wind: SE @ 0.9 mph
precip :1.52 in (39 mm)
Moon : Waning Crescent 7%
Sunrise : 6:14 ; Sunset : 17:53
Weather data by Weather Underground
(http://www.wunderground.com)
ewaller@turing
To use this program, you must obtain an API key from Weather Underground http://www.wunderground.com/weather/api/
The geolocation, choice of units, and api key are stored in persistent storage. After they are entered once, they need not be entered from the CLI each time.
Note that the verbose option does expose the API key. I have redacted mine in the sample outputs
To use this from conky, I use a helper script called getweather:
ewaller@turing ~/devel/python 1195 %cat ~/.weatherdata/getWeather
#!/bin/bash
~/devel/python/wunderground.py > ~/.weatherdata/theWeather
ewaller@turing ~/devel/python 1196 %
and the following in my conkyrc
${color black}Weather :${execi 300 ~/.weatherdata/getWeather}
${exec cat ~/.weatherdata/theWeather}
This serves a static file most of the time. That static file is updated every 5 minutes.
Look like this: http://i.imgur.com/QzU3XBr.jpg
Southern California finally got some rain
Enjoy
Edit: Oh yeah, comments on the program or programming style are welcome
Last edited by ewaller (2016-03-09 16:09:18)
Nothing is too wonderful to be true, if it be consistent with the laws of nature -- Michael Faraday
Sometimes it is the people no one can imagine anything of who do the things no one can imagine. -- Alan Turing
---
How to Ask Questions the Smart Way
Offline
Oh that looks really interesting. I bet it would also work really well if you wanted to put something together that could keep logs of your past weather conditions. I'll have to save that and give it a try when I have some time. Thanks. The code looks good and readable to me but I'm not a python professional or anything.
If quantum mechanics hasn't profoundly shocked you, you haven't understood it yet.
Niels Bohr
Offline
I'm using https://github.com/HalosGhost/shaman
I've cobbled together a script that prints a one-line summary of the forecast. I can prepend it with time and date and pipe it to 'tee' which in turn appends it to a file.
Offline
Here is an updated version I like better
Changed it to use native logging and refactor to use list comprehensions when rational
#! /usr/bin/python
"""
Obtain cuurent weather from Weather Underground
Copyright 2015 Eric Waller
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 of the License, 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. If not, see <http://www.gnu.org/licenses/>.
"""
from urllib.request import urlopen
import json
import time
import shelve
import os
import logging
class Weather:
"""
Class for retrieving weather data from wunderground.com and printing those data
"""
# Definition of the fields of interest. Each field is a tuple of four items --
# (1) a tuple that defines a path to the field,
# (2) an string prefix that is printed before the field value,
# (3) a string that is printed after the field value and
# (4) a flag that, if not None, is a string that defines whether the
# field is valid for imperial or metric units. .
# The tuple that defines the key path (first item in the field)
# defines the path (tree) of keys for the field of interest.
# The size of the tuples for the keys are variable in length as
# this json scheme uses multiple layers of hierarchical dictionaries.
# All of the entries are keys that point to dictionaries with the
# exception of the last, which is a key for something that is not a
# dictionary (string, int, float,etc)
fields=[ ( ('current_observation','display_location','full') ,'Location : ' ,'\n' ,None),
( ('current_observation','local_time_rfc822') ,'' ,'\n' ,None),
( ('current_observation','weather') ,'Condition : ' ,'\n' ,None),
( ('current_observation','temperature_string') ,'Temperature :' ,'\n' ,None),
( ('current_observation','relative_humidity') ,'Humidity :' ,' / ' ,None),
( ('current_observation','dewpoint_string') ,'Dewpoint :' ,'\n' ,None),
( ('current_observation','pressure_in') ,'Pressure :' ,' in hg. ','imperial'),
( ('current_observation','pressure_mb') ,'Pressure :' ,' mb ' ,'metric'),
( ('current_observation','pressure_trend') ,'Trend ' ,'\n' ,None),
( ('current_observation','visibility_mi') ,'Visibility :' ,'mi\n' ,'imperial'),
( ('current_observation','visibility_km') ,'Visibility :' ,'km\n' ,'metric'),
( ('current_observation','wind_dir') ,'Wind: ' , '' ,None),
( ('current_observation','wind_mph') ,' @ ' ,' mph\n' ,'imperial'),
( ('current_observation','wind_kph') ,' @ ' ,' kph\n' ,'metric'),
( ('current_observation','precip_today_string') ,'precip :' ,'\n' ,None),
( ('moon_phase','phaseofMoon') ,'Moon : ' ,'' ,None),
( ('moon_phase','percentIlluminated') ,' ' ,'%\n' ,None),
( ('sun_phase','sunrise','hour') ,'Sunrise : ' ,'' ,None),
( ('sun_phase','sunrise','minute') ,':' ,' ; ' ,None),
( ('sun_phase','sunset','hour') ,'Sunset : ' ,'' ,None),
( ('sun_phase','sunset','minute') ,':' ,'\n' ,None)
]
def __init__(self,theLocation,theKey,imperial):
"""
Instanciate a weather object
theLocation : a string representing the geolocation
theKey : a string representing the Weather Underground API key
"""
self.location=theLocation
self.key=theKey
self.imperial = imperial
logging.info("location set to "+self.location)
logging.info("Units are "+( "imperial" if self.imperial else "metric"))
logging.info("Fetching weather data using API key "+self.key)
def GetWeather(self):
""" Get the json information for the current location
returns: A dictionary of items in the json file
"""
url='http://api.wunderground.com/api/'+self.key+'/conditions/astronomy/q/'+self.location+'.json'
logging.info("Retrieving weather report from "+url)
response =urlopen(url)
theString = response.read().decode('utf-8')
theDict= json.loads(theString)
logging.debug("json response received:"+theString)
return (theDict)
def PrintReport(self,theWeather):
"""Generate a report of the current weather using 'interesting' json fields
theWeather : a dictionary of weather items (derived from the json report)
Returns: None
"""
logging.info("Generating report")
for x in self.fields:
theThing=theWeather
for y in x[0]:
theThing=theThing[y]
if not type(theThing) is str:
theThing=theThing.__str__()
if not x[3] or (self.imperial and (x[3] == 'imperial')) or ((not self.imperial) and (x[3]=='metric')):
print (x[1]+theThing,end=x[2])
print("Weather data by Weather Underground\n(http://www.wunderground.com)")
def main():
from argparse import ArgumentParser
logFormat='%(relativeCreated)6dmS (%(threadName)s) %(levelname)s : %(message)s'
# variable theParameters defines the command line options, where
# and how their data are stored, and define the relation
# of the command line parameters to things that are stored in
# persistent storage.
#
# Element 0 is the member name in the ArgumentPaser object,
# element 1 is the action,
# element 2 is the short option name,
# element 3 is the long option name,
# element 4 is the key name for storage in theShelve (None implies the value is not stored)
# element 5 is the help string, and
# element 6 is the error message if the element is not set (None implies it is not required)
theParameters=[
('verbose' ,'store_true' ,'-v' ,'--verbose' , None ,"Generate information" ,None),
('debug' ,'store_true' ,'-d' ,'--debug' , None ,"Generate debugging information" ,None),
('api_key' ,'store' ,'-k' ,'--key' , 'APIkey' ,"Set and store the API key" ,"API key not set"),
('location' ,'store' ,'-l' ,'--location' , 'location' ,"Set and store the location" ,"Location not set"),
('units' ,'store_true' ,'-i' ,'--imperial' , 'units' ,"Set and store choice of Imperial units" ,"Units not set (Imperial/Metric)"),
('units' ,'store_false' ,'-m' ,'--metric' , 'units' ,"Set and store choice of Metric units" ,"Units not set (Imperial/Metric)"),
]
# open the persistent storage and create any missing keys
theShelve = shelve.open(os.environ['HOME'] + '/.wunderground')
for x in theParameters:
if x[4] and not x[4] in theShelve:
theShelve[x[4]]=None
# Handle all the command line nonsense.
# There are six options -- one is to set the location, one is to set the API key, two to set the
# unit system that is desired, and two to set verbose and debug level reporting.
# Defaults come from persistent storage
description = "Fetch weather from Weather Underground"
parser = ArgumentParser(description=description)
[ parser.add_argument(x[2],x[3],action=x[1],dest=x[0],help=x[5] ,
default = theShelve[x[4]] if x[4] else False )
for x in theParameters ]
args = parser.parse_args()
# Set up the log function and enable the output if the user wants it
if (args.verbose):
logging.basicConfig(level=logging.INFO, format=logFormat)
if (args.debug):
logging.basicConfig(level=logging.DEBUG, format=logFormat)
# If anything needs to be updated in persistent storage, then do so.
for x in theParameters:
if x[4] != None and ( theShelve[x[4]] != getattr(args,x[0]) ):
theShelve[x[4]] = getattr(args,x[0])
result = theShelve[x[4]]
if type(result) != str:
result = result.__str__()
logging.info('Persistent storage updated: '+x[4]+" set to "+ result)
# If we have all the data we need, then proceed, die otherwise
[parser.error(x[6]) for x in theParameters if x[4] != None if theShelve[x[4]] == None]
# Here is where the magic happens.
weather = Weather(theShelve['location'],theShelve['APIkey'],theShelve['units'])
theWeather = weather.GetWeather()
weather.PrintReport(theWeather)
logging.info("Done")
theShelve.close()
if __name__ == "__main__":
main()
Nothing is too wonderful to be true, if it be consistent with the laws of nature -- Michael Faraday
Sometimes it is the people no one can imagine anything of who do the things no one can imagine. -- Alan Turing
---
How to Ask Questions the Smart Way
Offline
I finally got around to making a package.
Nothing is too wonderful to be true, if it be consistent with the laws of nature -- Michael Faraday
Sometimes it is the people no one can imagine anything of who do the things no one can imagine. -- Alan Turing
---
How to Ask Questions the Smart Way
Offline
Pages: 1