Before Width: | Height: | Size: 5.9 KiB |
@ -1,28 +0,0 @@
|
|||||||
## 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
|
|
@ -1,42 +0,0 @@
|
|||||||
# 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.
|
|
||||||
If you never need to search for a location, this can be omitted by using
|
|
||||||
`DEFAULT_LAT_LONG`.)
|
|
||||||
- `DEFAULT_LOCATION`: Ex. "Seattle, WA".
|
|
||||||
- `DEFAULT_LAT_LONG`: Only required if `GOOGLE_API_KEY` is unavailable, since
|
|
||||||
`DEFAULT_LOCATION` can't be geocoded. Format: `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/)
|
|
@ -1,89 +0,0 @@
|
|||||||
require 'delegate'
|
|
||||||
require 'erb'
|
|
||||||
require 'yaml'
|
|
||||||
|
|
||||||
class Items < DelegateClass(Array)
|
|
||||||
attr_reader :items
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@items = []
|
|
||||||
super(@items)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
ERB.new(<<-XML).result(binding)
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<items>
|
|
||||||
<%= items.map {|item| item.to_s.split("\n").map {|line| ' ' << line }}.join("\n").strip %>
|
|
||||||
</items>
|
|
||||||
XML
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Item
|
|
||||||
attr_reader *%i[ uid arg valid
|
|
||||||
title subtitle icon ]
|
|
||||||
def initialize(**kwargs)
|
|
||||||
@uid = kwargs.fetch(:uid).to_s.encode(xml: :attr)
|
|
||||||
@arg = kwargs[:arg].to_s.encode(xml: :attr)
|
|
||||||
@valid = kwargs.fetch(:valid, false) ? 'yes' : 'no'
|
|
||||||
@title = kwargs.fetch(:title).encode(xml: :text)
|
|
||||||
@subtitle = kwargs[:subtitle] && kwargs[:subtitle].encode(xml: :text)
|
|
||||||
@icon = kwargs[:icon] && kwargs[:icon].encode(xml: :text)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
ERB.new(<<-XML, nil, '%>').result(binding)
|
|
||||||
<item arg=<%= arg %> uid=<%= uid %> valid="<%= valid %>">
|
|
||||||
<title><%= title %></title>
|
|
||||||
% if subtitle
|
|
||||||
<subtitle><%= subtitle %></subtitle>
|
|
||||||
% end
|
|
||||||
% if icon
|
|
||||||
<icon><%= icon %></icon>
|
|
||||||
% end
|
|
||||||
</item>
|
|
||||||
XML
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module Alfred
|
|
||||||
class Config
|
|
||||||
def self.[](key)
|
|
||||||
config[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.[]=(key, value)
|
|
||||||
config[key] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.config
|
|
||||||
return @config if defined?(@config)
|
|
||||||
|
|
||||||
bundle_id = `/usr/libexec/PlistBuddy info.plist -c 'print :bundleid'`.strip
|
|
||||||
@config = self.new(bundle_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
WORKFLOW_DATA = '~/Library/Application Support/Alfred 2/Workflow Data/'
|
|
||||||
|
|
||||||
attr_reader :path
|
|
||||||
attr_accessor :config
|
|
||||||
|
|
||||||
def initialize(bundle_id)
|
|
||||||
dir = File.expand_path(File.join(WORKFLOW_DATA, bundle_id))
|
|
||||||
Dir.mkdir(dir) unless Dir.exist?(dir)
|
|
||||||
|
|
||||||
@path = File.join(dir, 'config.yml')
|
|
||||||
@config = File.exist?(@path) ? YAML.load_file(@path) : {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def [](key)
|
|
||||||
config.fetch(key) { '' }
|
|
||||||
end
|
|
||||||
|
|
||||||
def []=(key, value)
|
|
||||||
config[key] = value
|
|
||||||
File.write(path, YAML.dump(config))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,26 +0,0 @@
|
|||||||
require_relative 'alfred'
|
|
||||||
|
|
||||||
OPTIONS = %w[ FORECAST_API_KEY
|
|
||||||
GOOGLE_API_KEY
|
|
||||||
DEFAULT_LOCATION
|
|
||||||
DEFAULT_LAT_LONG ]
|
|
||||||
|
|
||||||
input = ARGV.shift || ''
|
|
||||||
|
|
||||||
items = Items.new
|
|
||||||
OPTIONS.each do |option|
|
|
||||||
title = if input.empty?
|
|
||||||
"Unset #{option}"
|
|
||||||
else
|
|
||||||
"Set #{option} to #{input}"
|
|
||||||
end
|
|
||||||
items << Item.new(
|
|
||||||
uid: option,
|
|
||||||
arg: "Alfred::Config['#{option}'] = '#{input}'",
|
|
||||||
valid: true,
|
|
||||||
title: title,
|
|
||||||
subtitle: Alfred::Config[option],
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
puts items.to_s
|
|
@ -1,138 +0,0 @@
|
|||||||
require 'delegate'
|
|
||||||
require 'erb'
|
|
||||||
require 'yaml'
|
|
||||||
|
|
||||||
require_relative 'alfred'
|
|
||||||
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?
|
|
||||||
lat, long = Alfred::Config['DEFAULT_LAT_LONG'].split(?,).map(&:to_f)
|
|
||||||
Location.new(Alfred::Config['DEFAULT_LOCATION'], lat, long)
|
|
||||||
else
|
|
||||||
Location.new(query)
|
|
||||||
end
|
|
||||||
forecast = Forecaster.forecast(location)
|
|
||||||
|
|
||||||
items = Items.new
|
|
||||||
|
|
||||||
items << Item.new(
|
|
||||||
uid: :location,
|
|
||||||
arg: "#{location.lat.round(4)},#{location.long.round(4)}",
|
|
||||||
valid: true,
|
|
||||||
title: location.name,
|
|
||||||
icon: 'icons/forecast.ico',
|
|
||||||
)
|
|
||||||
|
|
||||||
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 << 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| 1000 * m['precipIntensity'] }
|
|
||||||
intensity = intensity.select.with_index {|_,i| i % 5 == 0 }
|
|
||||||
min, max = intensity.minmax
|
|
||||||
|
|
||||||
subtitle = ["#{min.round}\" #{Spark.new(intensity)} #{max.round}\""]
|
|
||||||
|
|
||||||
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 << 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| 1000 * m['precipIntensity'] }
|
|
||||||
intensity = intensity.select.with_index {|_,i| i % 4 == 0 }
|
|
||||||
min, max = intensity.minmax
|
|
||||||
|
|
||||||
subtitle = ["#{min.round}\" #{Spark.new(intensity)} #{max.round}\""]
|
|
||||||
|
|
||||||
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 << 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 << Item.new(
|
|
||||||
uid: wday,
|
|
||||||
title: "#{wday} - #{data['summary']}",
|
|
||||||
subtitle: subtitle.join(' · '),
|
|
||||||
icon: "icons/#{ICONS[data['icon']]}.png",
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
puts items.to_s
|
|
@ -1,23 +0,0 @@
|
|||||||
require 'json'
|
|
||||||
require 'open-uri'
|
|
||||||
|
|
||||||
require_relative 'alfred'
|
|
||||||
|
|
||||||
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(Alfred::Config['FORECAST_API_KEY'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def forecast(location)
|
|
||||||
lat, long = location.lat, location.long
|
|
||||||
url = "https://api.forecast.io/forecast/#{api_key}/#{lat},#{long}?units=auto"
|
|
||||||
response = JSON.load(open(url))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
|||||||
require 'json'
|
|
||||||
require 'open-uri'
|
|
||||||
require 'uri'
|
|
||||||
|
|
||||||
require_relative 'alfred'
|
|
||||||
|
|
||||||
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(Alfred::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
|
|
Before Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 976 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 609 B |
Before Width: | Height: | Size: 818 B |
Before Width: | Height: | Size: 879 B |
Before Width: | Height: | Size: 968 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 695 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1008 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 979 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 951 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 963 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 879 B |
Before Width: | Height: | Size: 835 B |
Before Width: | Height: | Size: 783 B |
Before Width: | Height: | Size: 846 B |
Before Width: | Height: | Size: 849 B |
Before Width: | Height: | Size: 785 B |
Before Width: | Height: | Size: 655 B |
Before Width: | Height: | Size: 581 B |
Before Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 635 B |
Before Width: | Height: | Size: 598 B |
Before Width: | Height: | Size: 518 B |
Before Width: | Height: | Size: 640 B |
Before Width: | Height: | Size: 635 B |
Before Width: | Height: | Size: 630 B |
Before Width: | Height: | Size: 631 B |
Before Width: | Height: | Size: 626 B |
Before Width: | Height: | Size: 446 B |
Before Width: | Height: | Size: 589 B |
Before Width: | Height: | Size: 738 B |
Before Width: | Height: | Size: 637 B |
Before Width: | Height: | Size: 878 B |
Before Width: | Height: | Size: 796 B |
Before Width: | Height: | Size: 800 B |
Before Width: | Height: | Size: 597 B |
Before Width: | Height: | Size: 608 B |
Before Width: | Height: | Size: 616 B |
Before Width: | Height: | Size: 613 B |
Before Width: | Height: | Size: 576 B |
Before Width: | Height: | Size: 552 B |
Before Width: | Height: | Size: 522 B |
Before Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 560 B |
Before Width: | Height: | Size: 8.2 KiB |
@ -1,159 +0,0 @@
|
|||||||
<?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>2A5C0A87-204E-49EA-94A7-8E62BB4EFD8A</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>destinationuid</key>
|
|
||||||
<string>5E2D96BA-31B2-4800-9A2B-B999285680A0</string>
|
|
||||||
<key>modifiers</key>
|
|
||||||
<integer>0</integer>
|
|
||||||
<key>modifiersubtext</key>
|
|
||||||
<string></string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>450E8285-E286-4D33-AADF-1ACF99F41031</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>destinationuid</key>
|
|
||||||
<string>373F7A50-59E2-4225-B665-E63FBBBAF7E5</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>plusspaces</key>
|
|
||||||
<false/>
|
|
||||||
<key>url</key>
|
|
||||||
<string>http://forecast.io/#/f/{query}</string>
|
|
||||||
<key>utf8</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<key>type</key>
|
|
||||||
<string>alfred.workflow.action.openurl</string>
|
|
||||||
<key>uid</key>
|
|
||||||
<string>5E2D96BA-31B2-4800-9A2B-B999285680A0</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>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>escaping</key>
|
|
||||||
<integer>0</integer>
|
|
||||||
<key>script</key>
|
|
||||||
<string>ruby -r./alfred -e "{query}"</string>
|
|
||||||
<key>type</key>
|
|
||||||
<integer>0</integer>
|
|
||||||
</dict>
|
|
||||||
<key>type</key>
|
|
||||||
<string>alfred.workflow.action.script</string>
|
|
||||||
<key>uid</key>
|
|
||||||
<string>373F7A50-59E2-4225-B665-E63FBBBAF7E5</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-config</string>
|
|
||||||
<key>script</key>
|
|
||||||
<string>ruby forecast-config.rb {query}</string>
|
|
||||||
<key>title</key>
|
|
||||||
<string>Configure the Forecast workflow</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>450E8285-E286-4D33-AADF-1ACF99F41031</string>
|
|
||||||
<key>version</key>
|
|
||||||
<integer>0</integer>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>readme</key>
|
|
||||||
<string></string>
|
|
||||||
<key>uidata</key>
|
|
||||||
<dict>
|
|
||||||
<key>2A5C0A87-204E-49EA-94A7-8E62BB4EFD8A</key>
|
|
||||||
<dict>
|
|
||||||
<key>ypos</key>
|
|
||||||
<real>10</real>
|
|
||||||
</dict>
|
|
||||||
<key>373F7A50-59E2-4225-B665-E63FBBBAF7E5</key>
|
|
||||||
<dict>
|
|
||||||
<key>ypos</key>
|
|
||||||
<real>130</real>
|
|
||||||
</dict>
|
|
||||||
<key>450E8285-E286-4D33-AADF-1ACF99F41031</key>
|
|
||||||
<dict>
|
|
||||||
<key>ypos</key>
|
|
||||||
<real>130</real>
|
|
||||||
</dict>
|
|
||||||
<key>5E2D96BA-31B2-4800-9A2B-B999285680A0</key>
|
|
||||||
<dict>
|
|
||||||
<key>ypos</key>
|
|
||||||
<real>10</real>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>webaddress</key>
|
|
||||||
<string>http://github.com/kejadlen/forecast.alfredworkflow</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,16 +0,0 @@
|
|||||||
require_relative 'geocoder'
|
|
||||||
|
|
||||||
class Location
|
|
||||||
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
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
|||||||
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
|
|
@ -1,22 +0,0 @@
|
|||||||
require 'minitest/autorun'
|
|
||||||
|
|
||||||
require_relative 'alfred'
|
|
||||||
|
|
||||||
class TestConfig < Minitest::Test
|
|
||||||
def setup
|
|
||||||
@config = Alfred::Config.new('com.kejadlen.test')
|
|
||||||
end
|
|
||||||
|
|
||||||
def teardown
|
|
||||||
File.delete(@config.path) if File.exist?(@config.path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_config
|
|
||||||
assert_nil @config[:foo]
|
|
||||||
|
|
||||||
@config[:foo] = 123
|
|
||||||
assert_equal 123, @config[:foo]
|
|
||||||
assert File.exist?(@config.path)
|
|
||||||
assert_equal '{:foo=>123}', File.read(@config.path)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,10 +0,0 @@
|
|||||||
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
|
|