Merge pull request #2 from KevinMidboe/setup

Setup
This commit is contained in:
2017-08-01 18:32:15 +02:00
committed by GitHub
14 changed files with 485 additions and 2 deletions

101
.gitignore vendored
View File

@@ -1,2 +1,99 @@
.DS_Store # Byte-compiled / optimized / DLL files
__pycache__ __pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Kevin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2
MANIFEST.in Normal file
View File

@@ -0,0 +1,2 @@
include LICENSE
recursive-include docs *.gif

BIN
docs/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 KiB

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
fire==0.1.1
geoip2==2.5.0
fuzzywuzzy==0.15.1
python-Levenshtein==0.12.0

38
setup.py Executable file
View File

@@ -0,0 +1,38 @@
from setuptools import setup
with open('requirements.txt') as f:
requirements = f.read().splitlines()
setup(
name='term-forecast',
version='0.1.dev0',
author='Kevin Midboe',
author_email='support@kevinmidboe.com',
description='Terminal Forcast is a easily accessible terminal based weather forecaster',
url='https://github.com/KevinMidboe/termWeather/',
license='MIT',
packages=['term_forecast'],
classifiers = [
"Environment :: Console",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3 :: Only",
"License :: OSI Approved :: MIT License",
"Development Status :: 4 - Beta",
'Operating System :: OS Independent',
'Operating System :: POSIX',
'Operating System :: MacOS',
'Operating System :: Unix',
],
install_requires=requirements,
entry_points={
'console_scripts': [
'forecast = term_forecast.term_weather:main',
],
}
)

View File

Before

Width:  |  Height:  |  Size: 51 MiB

After

Width:  |  Height:  |  Size: 51 MiB

127
termWeather/term_weather.py Executable file
View File

@@ -0,0 +1,127 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-07-27 21:26:53
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-07-30 18:56:27
# TODO LIST
# Get coordinates from IP ✔
# Fetch coordinates from YR ✔
# Convert coordinates to place name w/ google GeoCode api ✔
# Parse return data
# Match weather description to icons ✔
# Check internet connection in a strict way
# Add table for time periode
# Add cache for quicker location for same ip
import fire, json, geoip2.database, ssl, os
from yr.libyr import Yr
from requests import get
from pprint import pprint
from sys import stdout
from emojiParser import EmojiParser
from loadingAnimation import LoadingAnimation
class Location(object):
def __init__(self):
abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)
self.reader = geoip2.database.Reader('conf/GeoLite2-City.mmdb')
self.getIP()
def getIP(self):
ip = get('https://api.ipify.org').text
self.ip = self.reader.city(ip)
def getCoordinates(self):
lat = self.ip.location.latitude
long = self.ip.location.longitude
return [lat, long]
def getAreaName(self):
lat, long = self.getCoordinates()
coordinates = ','.join([str(lat), str(long)])
areaURL = 'https://maps.googleapis.com/maps/api/geocode/json?&latlng='
areaAPIResponse = json.loads(get(areaURL + coordinates).text)
closestArea = areaAPIResponse['results'][0]['address_components']
area = {}
for item in closestArea:
if 'route' in item['types']:
area['street'] = item['long_name']
if 'locality' in item['types']:
area['town'] = item['long_name']
if 'administrative_area_level_1' in item['types']:
area['municipality'] = item['long_name']
if 'country' in item['types']:
area['country'] = item['long_name']
return area
class WeatherForecast(object):
def __init__(self, area=None):
# TODO search for area coordinates in a map
self.area = area
self.name = None
self.number = None
self.variable = None
def symbolVariables(self, symbol_obj):
self.name = symbol_obj['@name']
self.number = symbol_obj['@number']
self.variable = symbol_obj['@var']
def parseYrTemperature(self, temperature_obj):
return temperature_obj['@value'] + ' ' + temperature_obj['@unit']
def now(self):
location = Location()
self.area = location.getAreaName()
# Create seperate function for formatting
locationName = '/'.join([self.area['country'], self.area['municipality'], self.area['town'], self.area['street']])
# Use the defined location name with yr API for location based weather information
weather = Yr(location_name=locationName)
now = json.loads(weather.now(as_json=True))
temperature_output = self.parseYrTemperature(now['temperature'])
emojiParser = EmojiParser(now['symbol']['@name'])
weatherIcon_output = emojiParser.convertSematicsToEmoji()
return ('%s %s' % (temperature_output, weatherIcon_output))
class TermWeather(object):
# Add now, forecast as args
def auto(self):
loadingAnimation = LoadingAnimation()
loadingAnimation.start()
weatherForecast = WeatherForecast()
forecast = weatherForecast.now()
loadingAnimation.stop()
stdout.write('\r%s \n' % forecast)
def fetch(self, area=None):
weatherForecast = WeatherForcast(area)
weatherForecast.now()
if __name__ == '__main__':
ssl._create_default_https_context = ssl._create_unverified_context
fire.Fire(TermWeather())

0
term_forecast/__init__.py Executable file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 MiB

154
term_forecast/emojiParser.py Executable file
View File

@@ -0,0 +1,154 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-07-29 11:56:24
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-07-30 13:17:19
from fuzzywuzzy import process
# Find the first word, if it is a noun or a adjective. ✔️
# Remove the adjective and split if there is a AND ✔️
# Then match the first noun to list and add that emoji ✔️
# and then match the second to list and add that emoji ✔️
# REGEX this bitch up
symbol_table = {
'clear sky': '☀️',
'fair': '🌤',
'partly cloudy': '⛅️',
'cloudy': ' ☁️ ',
'thunder': '⚡️',
'rain showers': '🌦',
'rain': '🌧',
'sleet showers': '🌦 💦',
'sleet': '🌨 💦',
'snow showers': '⛅ ❄️',
'snow': '🌨',
'rain': '🌧',
'sleet': '🌧',
'snow': '🌨',
'showers': '🌤'
}
severity = {
'rain': ['', ' ☂️', ' ☔️'],
'sleet': [' 💦 ', ' 💧 ', ' 💧 💦 '],
'snow': [' ❄️ ', ' ❄️ ❄️ ', ' ❄️ ❄️ ❄️ ']
}
class EmojiParser(object):
def __init__(self, condition_text):
self.condition_expression = condition_text.lower()
self.severity = None
self.nouns = []
self.weather_nouns = ['cleary sky', 'fair', 'cloudy', 'rain', 'rain showers', 'sleet',
'sleet showers', 'snow showers', 'thunder', 'snow']
self.weather_adjectives = {'light': 0, 'normal': 1, 'heavy': 2}
def __str__(self):
return str([self.condition_expression, self.severity, self.nouns])
# Splits and lowers the condition text for easier parsing
def splitCondition(self, condition):
return condition.split()
# Takes a input or uses condition_expression to find adjective in sentence
def findAdjective(self, sentence=None):
if sentence is None:
sentence = self.condition_expression
# Splits and iterates over each word in sentence
expression = self.splitCondition(sentence)
for word in expression:
if word in self.weather_adjectives:
# Return the word if matched with weather_adjectives
return word
return None
# Removes the first adjective in the a given sentence
def removeAdjective(self):
adjective = self.findAdjective()
if adjective: # Adjective is not None
expression = self.splitCondition(self.condition_expression)
expression.remove(adjective)
return ' '.join(expression)
else:
return self.condition_expression
def severityValue(self):
adjective = self.findAdjective()
if adjective:
self.severity = self.weather_adjectives[adjective]
else:
self.severity = self.weather_adjectives['normal']
def findWeatherTokens(self):
# If present removes the leading adjective
sentence = self.removeAdjective()
# If multiple tokens/weather_nouns split all between the 'and'
if 'and' in sentence:
self.nouns = sentence.split(' and ')
else:
self.nouns = [sentence]
# Use the symbol_table to convert the forecast name to emoji
def emojify(self, noun):
return symbol_table[noun]
# Does as emojify above, but iterates over a list if multiple elements
def emojifyList(self, noun_list):
returnList = []
# TODO use more like a map function?
for noun in noun_list:
returnList.append(self.emojify(noun))
return ' '.join(returnList)
def findPrimaryForecast(self):
# Copies the contents not the refrence to the list
noun_list = list(self.nouns)
forecast = noun_list.pop(0)
# Translates to emoji once here instead of twice below
forecast_emoji = self.emojify(forecast)
if forecast in severity:
return ('%s %s' % (forecast_emoji, severity[forecast]))
else:
return forecast_emoji
# Trying to analyze the semantics of the condition text
def emojifyWeatherForecast(self):
# Finds the tokens/nouns of weather for the given input text and severity value
self.findWeatherTokens()
self.severityValue()
primary_forecast = self.findPrimaryForecast()
secondary_forecast = self.emojifyList(self.nouns[1:])
return ('%s %s' % (primary_forecast, secondary_forecast))
def convertSematicsToEmoji(self):
return self.emojifyWeatherForecast()
def main():
emojiParser = EmojiParser('Cloudy')
print(emojiParser.convertSematicsToEmoji())
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-07-30 13:53:38
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-07-30 13:53:46
import itertools
from threading import Thread
from time import sleep
from sys import stdout
class LoadingAnimation(object):
def __init__(self):
self.done = False
def start(self):
t = Thread(target=self.animate)
t.start()
def animate(self):
for c in itertools.cycle(['|', '/', '-', '\\']):
if self.done:
break
stdout.write('\rFetching ' + c)
stdout.flush()
sleep(0.1)
def stop(self):
self.done = True
def main():
loadingAnimation = LoadingAnimation()
loadingAnimation.start()
sleep(2)
loadingAnimation.stop()
stdout.write('\rTemp \n')
if __name__ == '__main__':
main()