@ -1 +0,0 @@
|
||||
Subproject commit 24666fa0572ff0152f827f83817f23cf0e5055d7
|
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,52 @@
|
||||
## Current
|
||||
|
||||
## 1.0.0 - 2015.11.01
|
||||
### Changed
|
||||
- Use [Alphred](https://github.com/kejadlen/alphred)
|
||||
|
||||
## 0.0.8 - 2015.05.31
|
||||
### Added
|
||||
- Re-add support for `DEFAULT_LAT_LONG` and `DEFAULT_LOCATION`.
|
||||
|
||||
## 0.0.7 - 2015.01.05
|
||||
### Added
|
||||
- The current location is retrieved via IP geolocation rather than set in the
|
||||
workflow configuration.
|
||||
- Fixed opening the forecast in the browser.
|
||||
|
||||
### Removed
|
||||
- Support for `DEFAULT_LAT_LONG` and `DEFAULT_LOCATION` has been deprecated in favor
|
||||
of getting the current location from the IP.
|
||||
|
||||
## 0.0.6 - 2014.12.13
|
||||
### Added
|
||||
- Add option to force Celsius/Fahrenheit using `FORECAST_UNITS`.
|
||||
|
||||
## 0.0.5 - 2014.11.22
|
||||
### Changed
|
||||
- Use `forecast-config` for managing API keys.
|
||||
- Fixed bug when precipitation intensity/probability was all 0's.
|
||||
|
||||
## 0.0.4 - 2014.11.21
|
||||
### Added
|
||||
- Sparklines for precipitation intensity and probability for the next hour
|
||||
(where applicable) and day.
|
||||
|
||||
### Changed
|
||||
- Bugfix for when `DEFAULT_LAT_LONG` is set and `DEFAULT_LOCATION` is not.
|
||||
|
||||
## 0.0.3 - 2014.11.19
|
||||
### Added
|
||||
- Forecast now uses units appropriate to the location.
|
||||
|
||||
### Changed
|
||||
- Fix `DEFAULT_LAT_LONG`.
|
||||
|
||||
## 0.0.2 - 2014.11.19
|
||||
### Changed
|
||||
- Remove minutely result for non-US locations since Forecast doesn't have this
|
||||
data.
|
||||
|
||||
## 0.0.1 - 2014.11.18
|
||||
### Added
|
||||
- Initial release
|
@ -0,0 +1,7 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "alphred", "~> 1.1"
|
||||
|
||||
group :development do
|
||||
gem "pry"
|
||||
end
|
@ -0,0 +1,23 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
alphred (1.1.0)
|
||||
builder (~> 3.2)
|
||||
builder (3.2.2)
|
||||
coderay (1.1.0)
|
||||
method_source (0.8.2)
|
||||
pry (0.10.3)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
slop (3.6.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
alphred (~> 1.1)
|
||||
pry
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.6
|
@ -0,0 +1,44 @@
|
||||
# Forecast Workflow for Alfred
|
||||
|
||||
![screenshot][screenshot]
|
||||
|
||||
[screenshot]: http://i.imgur.com/mxGnovo.png
|
||||
|
||||
# Requirements
|
||||
|
||||
- [Alfred](http://www.alfredapp.com/)
|
||||
- [Alfred Powerpack](http://www.alfredapp.com/powerpack/)
|
||||
- OS X Mavericks
|
||||
|
||||
# Installation
|
||||
|
||||
Download and install the [workflow][download].
|
||||
|
||||
[download]: https://github.com/kejadlen/forecast.alfredworkflow/releases/download/0.0.5/Forecast.alfredworkflow
|
||||
|
||||
Run `forecast-config VALUE` to set API keys and the default location:
|
||||
|
||||
- `FORECAST_API_KEY`: Get an API key [here][forecast-api-key].
|
||||
- `GOOGLE_API_KEY`: Get an API key [here][google-api-key]. (Used for geocoding
|
||||
queries. *This can be omitted if you only want the forecast for the current
|
||||
location*.)
|
||||
- `FORECAST_UNITS`: Defaults to `auto`, which sets the units based on the
|
||||
location. Use `si` for Celsius and `us` for Fahrenheit.
|
||||
- `DEFAULT_LAT_LONG`: Set this to override IP geolocation. Ex: `47.7396,-122.3426` for Seattle.
|
||||
- `DEFAULT_LOCATION`: Used for displaying the location name when using `DEFAULT_LAT_LONG`.
|
||||
|
||||
[forecast-api-key]: https://developer.forecast.io/register
|
||||
[google-api-key]: https://developers.google.com/maps/documentation/geocoding/#api_key
|
||||
|
||||
# TODO
|
||||
|
||||
- Handle errors gracefully
|
||||
- Caching? (Probably unnecessary...)
|
||||
- Use `Accept-Encoding: gzip` for Forecast calls
|
||||
|
||||
# Attributions
|
||||
|
||||
- [Climacons](http://adamwhitcroft.com/climacons/)
|
||||
- [Forecast API](https://developer.forecast.io/docs/v2)
|
||||
- [Google Geocoding API](https://developers.google.com/maps/documentation/geocoding/)
|
||||
- [ipinfo.io](http://ipinfo.io/)
|
@ -0,0 +1 @@
|
||||
require "alphred/tasks"
|
@ -0,0 +1,14 @@
|
||||
$LOAD_PATH.unshift(File.expand_path("../vendor/bundle", __FILE__))
|
||||
require "bundler/setup"
|
||||
|
||||
require "alphred"
|
||||
|
||||
module Forecast
|
||||
Config = Alphred::Config.load(
|
||||
FORECAST_API_KEY: nil,
|
||||
GOOGLE_API_KEY: nil,
|
||||
FORECAST_UNITS: nil,
|
||||
DEFAULT_LOCATION: nil,
|
||||
DEFAULT_LAT_LONG: nil,
|
||||
)
|
||||
end
|
@ -0,0 +1,145 @@
|
||||
$LOAD_PATH.unshift(File.expand_path("../vendor/bundle", __FILE__))
|
||||
require "bundler/setup"
|
||||
|
||||
require "alphred"
|
||||
|
||||
require_relative 'config'
|
||||
require_relative 'forecaster'
|
||||
require_relative 'location'
|
||||
require_relative 'spark'
|
||||
|
||||
ICONS = {
|
||||
'clear-day' => 'Sun',
|
||||
'clear-night' => 'Moon',
|
||||
'rain' => 'Cloud-Rain',
|
||||
'snow' => 'Cloud-Snow',
|
||||
'sleet' => 'Cloud-Snow-Alt',
|
||||
'wind' => 'Wind',
|
||||
'fog' => 'Cloud-Fog',
|
||||
'cloudy' => 'Cloud',
|
||||
'partly-cloudy-day' => 'Cloud-Sun',
|
||||
'partly-cloudy-night' => 'Cloud-Moon',
|
||||
}
|
||||
|
||||
Precipitation = Struct.new(:intensity, :probability) do
|
||||
def self.from_forecast(forecast)
|
||||
self.new(*forecast.values_at('precipIntensity', 'precipProbability'))
|
||||
end
|
||||
|
||||
def human_intensity
|
||||
case intensity
|
||||
when 0...0.002
|
||||
'no'
|
||||
when 0.002...0.017
|
||||
'very light'
|
||||
when 0.017...0.1
|
||||
'light'
|
||||
when 0.1...0.4
|
||||
'moderate'
|
||||
else
|
||||
'heavy'
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{(probability*100).to_i}% chance of #{human_intensity} rain."
|
||||
end
|
||||
end
|
||||
|
||||
query = ARGV.shift || ''
|
||||
location = if query.empty?
|
||||
if Forecast::Config['DEFAULT_LAT_LONG'].empty?
|
||||
Location.from_ip
|
||||
else
|
||||
lat, long = Forecast::Config['DEFAULT_LAT_LONG'].split(?,).map(&:to_f)
|
||||
Location.new(Forecast::Config['DEFAULT_LOCATION'], lat, long)
|
||||
end
|
||||
else
|
||||
Location.new(query)
|
||||
end
|
||||
forecast = Forecaster.forecast(location)
|
||||
|
||||
items = Alphred::Items.new
|
||||
|
||||
items << Alphred::Item.new(
|
||||
uid: :location,
|
||||
arg: "#{location.lat.round(4)},#{location.long.round(4)}",
|
||||
valid: true,
|
||||
title: location.name,
|
||||
icon: 'icons/forecast.png',
|
||||
)
|
||||
|
||||
currently = forecast['currently']
|
||||
precip = Precipitation.from_forecast(currently)
|
||||
subtitle = [ "#{currently['temperature'].round}°" ]
|
||||
subtitle << "Feels like #{currently['apparentTemperature'].round}°"
|
||||
subtitle << precip.to_s if precip.probability > 0
|
||||
items << Alphred::Item.new(
|
||||
uid: :currently,
|
||||
title: currently['summary'],
|
||||
subtitle: subtitle.join(' · '),
|
||||
icon: "icons/#{ICONS[currently['icon']]}.png",
|
||||
)
|
||||
|
||||
minutely = forecast['minutely']
|
||||
if minutely
|
||||
intensity = minutely['data'].map {|m| m['precipIntensity'] }
|
||||
intensity = intensity.select.with_index {|_,i| i % 4 == 0 }
|
||||
min, max = intensity.minmax
|
||||
intensity = intensity.map {|i| 1000 * i }
|
||||
|
||||
subtitle = ["#{min.round(3)}\" #{Spark.new(intensity)} #{max.round(3)}\""]
|
||||
|
||||
probability = minutely['data'].map {|m| (100 * m['precipProbability']).round }
|
||||
probability = probability.select.with_index {|_,i| i % 5 == 0 }
|
||||
min, max = probability.minmax
|
||||
|
||||
subtitle << "#{min}% #{Spark.new(probability, max: 100)} #{max}%"
|
||||
|
||||
items << Alphred::Item.new(
|
||||
uid: :minutely,
|
||||
title: minutely['summary'],
|
||||
subtitle: subtitle.join(' · '),
|
||||
icon: "icons/#{ICONS[minutely['icon']]}.png",
|
||||
)
|
||||
end
|
||||
|
||||
hourly = forecast['hourly']
|
||||
|
||||
intensity = hourly['data'].map {|m| m['precipIntensity'] }
|
||||
intensity = intensity.select.with_index {|_,i| i % 4 == 0 }
|
||||
min, max = intensity.minmax
|
||||
intensity = intensity.map {|i| 1000 * i }
|
||||
|
||||
subtitle = ["#{min.round(3)}\" #{Spark.new(intensity)} #{max.round(3)}\""]
|
||||
|
||||
probability = hourly['data'].map {|m| (100 * m['precipProbability']).round }
|
||||
probability = probability.select.with_index {|_,i| i % 4 == 0 }
|
||||
min, max = probability.minmax
|
||||
|
||||
subtitle << "#{min}% #{Spark.new(probability, max: 100)} #{max}%"
|
||||
|
||||
items << Alphred::Item.new(
|
||||
uid: :hourly,
|
||||
title: hourly['summary'],
|
||||
subtitle: subtitle.join(' · '),
|
||||
icon: "icons/#{ICONS[hourly['icon']]}.png",
|
||||
)
|
||||
|
||||
forecast['daily']['data'][1..6].each do |data|
|
||||
wday = Time.at(data['time']).strftime('%A')
|
||||
precip = Precipitation.from_forecast(data)
|
||||
|
||||
subtitle = [ "Low: #{data['apparentTemperatureMin'].round}°",
|
||||
"High: #{data['apparentTemperatureMax'].round}°" ]
|
||||
subtitle << precip.to_s if precip.probability > 0
|
||||
|
||||
items << Alphred::Item.new(
|
||||
uid: wday,
|
||||
title: "#{wday} - #{data['summary']}",
|
||||
subtitle: subtitle.join(' · '),
|
||||
icon: "icons/#{ICONS[data['icon']]}.png",
|
||||
)
|
||||
end
|
||||
|
||||
puts items.to_xml
|
@ -0,0 +1,24 @@
|
||||
require 'json'
|
||||
require 'open-uri'
|
||||
|
||||
require_relative 'config'
|
||||
|
||||
Forecaster = Struct.new(:api_key) do
|
||||
def self.forecast(location)
|
||||
forecaster.forecast(location)
|
||||
end
|
||||
|
||||
def self.forecaster
|
||||
return @forecaster if defined?(@forecaster)
|
||||
|
||||
@forecaster = self.new(Forecast::Config['FORECAST_API_KEY'])
|
||||
end
|
||||
|
||||
def forecast(location)
|
||||
lat, long = location.lat, location.long
|
||||
units = Forecast::Config['FORECAST_UNITS']
|
||||
url = "https://api.forecast.io/forecast/#{api_key}/#{lat},#{long}?units=#{units}"
|
||||
response = JSON.load(open(url))
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,30 @@
|
||||
require 'json'
|
||||
require 'open-uri'
|
||||
require 'uri'
|
||||
|
||||
require_relative 'config'
|
||||
|
||||
Geocoder = Struct.new(:api_key) do
|
||||
def self.geocode(location)
|
||||
geocoder.geocode(location)
|
||||
end
|
||||
|
||||
def self.geocoder
|
||||
return @geocoder if defined?(@geocoder)
|
||||
|
||||
@geocoder = self.new(Forecast::Config['GOOGLE_API_KEY'])
|
||||
end
|
||||
|
||||
def geocode(location)
|
||||
url = 'https://maps.googleapis.com/maps/api/geocode/json'
|
||||
query = URI.encode_www_form(address: location, api_key: api_key)
|
||||
response = JSON.load(open("#{url}?#{query}"))
|
||||
result = response['results'][0]
|
||||
|
||||
name = result['formatted_address']
|
||||
location = result['geometry']['location']
|
||||
lat, long = location.values_at('lat', 'lng')
|
||||
|
||||
[name, lat, long]
|
||||
end
|
||||
end
|
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 976 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 609 B |
After Width: | Height: | Size: 818 B |
After Width: | Height: | Size: 879 B |
After Width: | Height: | Size: 968 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 695 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1008 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 979 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 951 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 963 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 879 B |
After Width: | Height: | Size: 835 B |
After Width: | Height: | Size: 783 B |
After Width: | Height: | Size: 846 B |
After Width: | Height: | Size: 849 B |
After Width: | Height: | Size: 785 B |
After Width: | Height: | Size: 655 B |
After Width: | Height: | Size: 581 B |
After Width: | Height: | Size: 591 B |
After Width: | Height: | Size: 635 B |
After Width: | Height: | Size: 598 B |
After Width: | Height: | Size: 518 B |
After Width: | Height: | Size: 640 B |
After Width: | Height: | Size: 635 B |
After Width: | Height: | Size: 630 B |
After Width: | Height: | Size: 631 B |
After Width: | Height: | Size: 626 B |
After Width: | Height: | Size: 446 B |
After Width: | Height: | Size: 589 B |
After Width: | Height: | Size: 738 B |
After Width: | Height: | Size: 637 B |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 796 B |
After Width: | Height: | Size: 800 B |
After Width: | Height: | Size: 597 B |
After Width: | Height: | Size: 608 B |
After Width: | Height: | Size: 616 B |
After Width: | Height: | Size: 613 B |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 552 B |
After Width: | Height: | Size: 522 B |
After Width: | Height: | Size: 715 B |
After Width: | Height: | Size: 560 B |
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,179 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>bundleid</key>
|
||||
<string>com.kejadlen.forecast</string>
|
||||
<key>category</key>
|
||||
<string>Internet</string>
|
||||
<key>connections</key>
|
||||
<dict>
|
||||
<key>04EA4664-95DC-4438-8978-79A17ED2FF40</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>destinationuid</key>
|
||||
<string>EEDF82A8-40A8-4C05-9CFA-6669A97C0E46</string>
|
||||
<key>modifiers</key>
|
||||
<integer>0</integer>
|
||||
<key>modifiersubtext</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>2A5C0A87-204E-49EA-94A7-8E62BB4EFD8A</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>destinationuid</key>
|
||||
<string>F496E087-4042-44E7-B07E-71986ED6CDBA</string>
|
||||
<key>modifiers</key>
|
||||
<integer>0</integer>
|
||||
<key>modifiersubtext</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>createdby</key>
|
||||
<string>Alpha Chen</string>
|
||||
<key>description</key>
|
||||
<string></string>
|
||||
<key>disabled</key>
|
||||
<false/>
|
||||
<key>name</key>
|
||||
<string>Forecast</string>
|
||||
<key>objects</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>config</key>
|
||||
<dict>
|
||||
<key>concurrently</key>
|
||||
<false/>
|
||||
<key>escaping</key>
|
||||
<integer>0</integer>
|
||||
<key>script</key>
|
||||
<string>open "http://forecast.io/#/f/{query}"</string>
|
||||
<key>type</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>type</key>
|
||||
<string>alfred.workflow.output.script</string>
|
||||
<key>uid</key>
|
||||
<string>F496E087-4042-44E7-B07E-71986ED6CDBA</string>
|
||||
<key>version</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>config</key>
|
||||
<dict>
|
||||
<key>argumenttype</key>
|
||||
<integer>1</integer>
|
||||
<key>escaping</key>
|
||||
<integer>127</integer>
|
||||
<key>keyword</key>
|
||||
<string>forecast</string>
|
||||
<key>queuedelaycustom</key>
|
||||
<integer>1</integer>
|
||||
<key>queuedelayimmediatelyinitially</key>
|
||||
<false/>
|
||||
<key>queuedelaymode</key>
|
||||
<integer>0</integer>
|
||||
<key>queuemode</key>
|
||||
<integer>1</integer>
|
||||
<key>runningsubtext</key>
|
||||
<string>Retriving location/weather...</string>
|
||||
<key>script</key>
|
||||
<string>ruby forecast.rb {query}</string>
|
||||
<key>title</key>
|
||||
<string>Forecast</string>
|
||||
<key>type</key>
|
||||
<integer>0</integer>
|
||||
<key>withspace</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>type</key>
|
||||
<string>alfred.workflow.input.scriptfilter</string>
|
||||
<key>uid</key>
|
||||
<string>2A5C0A87-204E-49EA-94A7-8E62BB4EFD8A</string>
|
||||
<key>version</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>config</key>
|
||||
<dict>
|
||||
<key>argumenttype</key>
|
||||
<integer>1</integer>
|
||||
<key>escaping</key>
|
||||
<integer>102</integer>
|
||||
<key>keyword</key>
|
||||
<string>config-forecast</string>
|
||||
<key>queuedelaycustom</key>
|
||||
<integer>3</integer>
|
||||
<key>queuedelayimmediatelyinitially</key>
|
||||
<true/>
|
||||
<key>queuedelaymode</key>
|
||||
<integer>0</integer>
|
||||
<key>queuemode</key>
|
||||
<integer>1</integer>
|
||||
<key>script</key>
|
||||
<string>ruby -r./config -e'puts Forecast::Config.filter_xml("{query}")'</string>
|
||||
<key>title</key>
|
||||
<string>Configure Forecast</string>
|
||||
<key>type</key>
|
||||
<integer>0</integer>
|
||||
<key>withspace</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>type</key>
|
||||
<string>alfred.workflow.input.scriptfilter</string>
|
||||
<key>uid</key>
|
||||
<string>04EA4664-95DC-4438-8978-79A17ED2FF40</string>
|
||||
<key>version</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>config</key>
|
||||
<dict>
|
||||
<key>concurrently</key>
|
||||
<false/>
|
||||
<key>escaping</key>
|
||||
<integer>102</integer>
|
||||
<key>script</key>
|
||||
<string>ruby -r./config -e'Forecast::Config.update!("{query}")'</string>
|
||||
<key>type</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>type</key>
|
||||
<string>alfred.workflow.output.script</string>
|
||||
<key>uid</key>
|
||||
<string>EEDF82A8-40A8-4C05-9CFA-6669A97C0E46</string>
|
||||
<key>version</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>readme</key>
|
||||
<string></string>
|
||||
<key>uidata</key>
|
||||
<dict>
|
||||
<key>04EA4664-95DC-4438-8978-79A17ED2FF40</key>
|
||||
<dict>
|
||||
<key>ypos</key>
|
||||
<real>120</real>
|
||||
</dict>
|
||||
<key>2A5C0A87-204E-49EA-94A7-8E62BB4EFD8A</key>
|
||||
<dict>
|
||||
<key>ypos</key>
|
||||
<real>10</real>
|
||||
</dict>
|
||||
<key>EEDF82A8-40A8-4C05-9CFA-6669A97C0E46</key>
|
||||
<dict>
|
||||
<key>ypos</key>
|
||||
<real>120</real>
|
||||
</dict>
|
||||
<key>F496E087-4042-44E7-B07E-71986ED6CDBA</key>
|
||||
<dict>
|
||||
<key>ypos</key>
|
||||
<real>10</real>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>webaddress</key>
|
||||
<string>http://github.com/kejadlen/forecast.alfredworkflow</string>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,28 @@
|
||||
require 'json'
|
||||
require 'open-uri'
|
||||
|
||||
require_relative 'geocoder'
|
||||
|
||||
class Location
|
||||
def self.from_ip(ip=nil)
|
||||
url = ['http://ipinfo.io', ip, 'json'].compact.join(?/)
|
||||
response = JSON.load(open(url))
|
||||
|
||||
lat, long = response['loc'].split(?,).map(&:to_f)
|
||||
name = "#{response['city']}, #{response['region']}"
|
||||
self.new(name, lat, long)
|
||||
end
|
||||
|
||||
attr_accessor :name, :lat, :long, :geocoder
|
||||
|
||||
def initialize(name, lat=nil, long=nil, geocoder=Geocoder)
|
||||
@name, @lat, @long, @geocoder = name, lat, long, geocoder
|
||||
|
||||
geocode! unless lat && long
|
||||
end
|
||||
|
||||
def geocode!
|
||||
self.name, self.lat, self.long = geocoder.geocode(name)
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,17 @@
|
||||
class Spark
|
||||
# TICKS = %w[▁ ▂ ▃ ▄ ▅ ▆ ▇ █] # Alfred doesn't render the last bar correctly
|
||||
# for some reason...
|
||||
TICKS = %w[▁ ▂ ▃ ▄ ▅ ▆ ▇]
|
||||
|
||||
attr_reader :data, :min, :max
|
||||
|
||||
def initialize(data, **kwargs)
|
||||
@data = data.map(&:round)
|
||||
@min = kwargs.fetch(:min) { 0 }
|
||||
@max = [(kwargs.fetch(:max) { data.max }).to_f, 1.0].max
|
||||
end
|
||||
|
||||
def to_s
|
||||
data.map {|i| TICKS[(TICKS.size - 1) * (i - min) / max] }.join
|
||||
end
|
||||
end
|
@ -0,0 +1,10 @@
|
||||
require 'minitest/autorun'
|
||||
|
||||
require_relative 'spark'
|
||||
|
||||
class TestSpark < Minitest::Test
|
||||
def test_div_by_zero
|
||||
spark = Spark.new([0, 0, 0])
|
||||
assert_equal '▁▁▁', spark.to_s
|
||||
end
|
||||
end
|
@ -0,0 +1,7 @@
|
||||
require 'rbconfig'
|
||||
# ruby 1.8.7 doesn't define RUBY_ENGINE
|
||||
ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
|
||||
ruby_version = RbConfig::CONFIG["ruby_version"]
|
||||
path = File.expand_path('..', __FILE__)
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/builder-3.2.2/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/alphred-1.1.0/lib"
|
@ -0,0 +1,9 @@
|
||||
/.bundle/
|
||||
/.yardoc
|
||||
/Gemfile.lock
|
||||
/_yardoc/
|
||||
/coverage/
|
||||
/doc/
|
||||
/pkg/
|
||||
/spec/reports/
|
||||
/tmp/
|
@ -0,0 +1,4 @@
|
||||
language: ruby
|
||||
rvm:
|
||||
- 2.0.0
|
||||
before_install: gem install bundler -v 1.10.6
|
@ -0,0 +1,12 @@
|
||||
## Unreleased
|
||||
|
||||
## [1.1.0] - 2015-11-01
|
||||
|
||||
### Added
|
||||
- `Config` class for handling persistent configuration across workflow updates.
|
||||
- CHANGELOG
|
||||
|
||||
## [1.0.0] - 2015-10-31
|
||||
|
||||
### Added
|
||||
- Library for making Alfred workflows.
|
@ -0,0 +1,13 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
||||
|
||||
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
@ -0,0 +1,10 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in alphred.gemspec
|
||||
gemspec
|
||||
|
||||
group :development do
|
||||
gem "guard"
|
||||
gem "guard-minitest"
|
||||
gem "pry"
|
||||
end
|
@ -0,0 +1,5 @@
|
||||
guard :minitest do
|
||||
watch(%r{^test/(.*)\/?test_(.*)\.rb$})
|
||||
watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
||||
watch(%r{^test/test_helper\.rb$}) { 'test' }
|
||||
end
|