[alfred] Update workflows

pull/28/head
Alpha Chen 9 years ago
parent bcc09fcd40
commit e7f387b720

@ -1 +0,0 @@
Subproject commit 24666fa0572ff0152f827f83817f23cf0e5055d7

@ -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,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

@ -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,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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save