pincerna 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rubocop.yml +77 -0
  4. data/.travis-gemfile +17 -0
  5. data/.travis.yml +7 -0
  6. data/.yardopts +1 -0
  7. data/CHANGELOG.md +3 -0
  8. data/Gemfile +23 -0
  9. data/LICENSE.md +21 -0
  10. data/README.md +211 -0
  11. data/Rakefile +45 -0
  12. data/bin/pincernad +56 -0
  13. data/docs/Pincerna.html +130 -0
  14. data/docs/Pincerna/Base.html +3051 -0
  15. data/docs/Pincerna/Bookmark.html +523 -0
  16. data/docs/Pincerna/Cache.html +767 -0
  17. data/docs/Pincerna/ChromeBookmark.html +308 -0
  18. data/docs/Pincerna/CurrencyConversion.html +589 -0
  19. data/docs/Pincerna/FirefoxBookmark.html +328 -0
  20. data/docs/Pincerna/Ip.html +1017 -0
  21. data/docs/Pincerna/Map.html +399 -0
  22. data/docs/Pincerna/SafariBookmark.html +308 -0
  23. data/docs/Pincerna/Server.html +673 -0
  24. data/docs/Pincerna/Translation.html +517 -0
  25. data/docs/Pincerna/UnitConversion.html +1042 -0
  26. data/docs/Pincerna/Version.html +189 -0
  27. data/docs/Pincerna/Vpn.html +561 -0
  28. data/docs/Pincerna/Weather.html +837 -0
  29. data/docs/_index.html +298 -0
  30. data/docs/class_list.html +54 -0
  31. data/docs/css/common.css +1 -0
  32. data/docs/css/full_list.css +57 -0
  33. data/docs/css/style.css +338 -0
  34. data/docs/file.README.html +327 -0
  35. data/docs/file_list.html +56 -0
  36. data/docs/frames.html +28 -0
  37. data/docs/index.html +327 -0
  38. data/docs/js/app.js +214 -0
  39. data/docs/js/full_list.js +178 -0
  40. data/docs/js/jquery.js +4 -0
  41. data/docs/method_list.html +389 -0
  42. data/docs/top-level-namespace.html +112 -0
  43. data/icon.png +0 -0
  44. data/images/chrome.png +0 -0
  45. data/images/currency.png +0 -0
  46. data/images/firefox.png +0 -0
  47. data/images/map.png +0 -0
  48. data/images/network.png +0 -0
  49. data/images/safari.png +0 -0
  50. data/images/translate.png +0 -0
  51. data/images/unit.png +0 -0
  52. data/images/vpn.png +0 -0
  53. data/images/weather.png +0 -0
  54. data/info.plist +961 -0
  55. data/it.cowtech.pincernad.plist +19 -0
  56. data/lib/pincerna.rb +30 -0
  57. data/lib/pincerna/base.rb +258 -0
  58. data/lib/pincerna/bookmark.rb +80 -0
  59. data/lib/pincerna/cache.rb +61 -0
  60. data/lib/pincerna/chrome_bookmark.rb +40 -0
  61. data/lib/pincerna/currency_conversion.rb +134 -0
  62. data/lib/pincerna/firefox_bookmark.rb +92 -0
  63. data/lib/pincerna/ip.rb +135 -0
  64. data/lib/pincerna/map.rb +30 -0
  65. data/lib/pincerna/safari_bookmark.rb +40 -0
  66. data/lib/pincerna/server.rb +85 -0
  67. data/lib/pincerna/translation.rb +67 -0
  68. data/lib/pincerna/unit_conversion.rb +120 -0
  69. data/lib/pincerna/version.rb +24 -0
  70. data/lib/pincerna/vpn.rb +74 -0
  71. data/lib/pincerna/weather.rb +188 -0
  72. data/pincerna.alfredworkflow +0 -0
  73. data/pincerna.gemspec +36 -0
  74. data/pincerna.sh +9 -0
  75. data/spec/cassettes/Pincerna_CurrencyConversion/_perform_filtering/should_return_valid_values.yml +38 -0
  76. data/spec/cassettes/Pincerna_Ip/_get_local_addresses/should_return_a_list_of_addresses.yml +47 -0
  77. data/spec/cassettes/Pincerna_Ip/_get_public_address/should_return_public_IP_address.yml +44 -0
  78. data/spec/cassettes/Pincerna_Translation/_perform_filtering/should_default_from_English_to_the_given_language_when_only_one_is_present.yml +124 -0
  79. data/spec/cassettes/Pincerna_Translation/_perform_filtering/should_query_Google_Translate_for_sentences_returning_no_alternatives.yml +51 -0
  80. data/spec/cassettes/Pincerna_Translation/_perform_filtering/should_query_Google_Translate_for_single_words.yml +71 -0
  81. data/spec/cassettes/Pincerna_Weather/_get_forecast/should_append_name.yml +177 -0
  82. data/spec/cassettes/Pincerna_Weather/_get_forecast/should_get_correct_forecasts.yml +339 -0
  83. data/spec/cassettes/Pincerna_Weather/_lookup_places/should_return_an_existing_WOEID_without_making_any_request.yml +56 -0
  84. data/spec/cassettes/Pincerna_Weather/_lookup_places/should_search_for_places.yml +189 -0
  85. data/spec/cassettes/Pincerna_Weather/_perform_filtering/should_get_forecast.yml +56 -0
  86. data/spec/coverage_helper.rb +44 -0
  87. data/spec/pincerna/base_spec.rb +166 -0
  88. data/spec/pincerna/bookmark_spec.rb +65 -0
  89. data/spec/pincerna/cache_spec.rb +88 -0
  90. data/spec/pincerna/chrome_bookmark_spec.rb +114 -0
  91. data/spec/pincerna/currency_conversion_spec.rb +46 -0
  92. data/spec/pincerna/firefox_bookmark_spec.rb +46 -0
  93. data/spec/pincerna/ip_spec.rb +194 -0
  94. data/spec/pincerna/map_spec.rb +24 -0
  95. data/spec/pincerna/safari_bookmark_spec.rb +237 -0
  96. data/spec/pincerna/server_spec.rb +108 -0
  97. data/spec/pincerna/translation_spec.rb +55 -0
  98. data/spec/pincerna/unit_conversion_spec.rb +98 -0
  99. data/spec/pincerna/vpn_spec.rb +68 -0
  100. data/spec/pincerna/weather_spec.rb +131 -0
  101. data/spec/spec_helper.rb +48 -0
  102. metadata +283 -0
@@ -0,0 +1,19 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>it.cowtech.pincernad</string>
7
+ <key>KeepAlive</key>
8
+ <true/>
9
+ <key>RunAtLoad</key>
10
+ <true/>
11
+ <key>EnableGlobbing</key>
12
+ <true/>
13
+ <key>ProgramArguments</key>
14
+ <array>
15
+ <string>~/.rvm/bin/pincernad</string>
16
+ <string>-e production</string>
17
+ </array>
18
+ </dict>
19
+ </plist>
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the pincerna gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
4
+ # Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
5
+ #
6
+
7
+ require "open-uri"
8
+ require "strscan"
9
+ require "cgi"
10
+ require "fileutils"
11
+ require "nokogiri"
12
+ require "oj"
13
+ require "yahoo_weatherman"
14
+ require "ruby-units"
15
+ require "plist"
16
+ require "daybreak"
17
+ require "em-synchrony"
18
+ require "em-synchrony/em-http"
19
+
20
+ require "pincerna/version" if !defined?(Pincerna::Version)
21
+ require "pincerna/base"
22
+ require "pincerna/cache"
23
+ require "pincerna/currency_conversion"
24
+ require "pincerna/ip"
25
+ require "pincerna/map"
26
+ require "pincerna/translation"
27
+ require "pincerna/unit_conversion"
28
+ require "pincerna/vpn"
29
+ require "pincerna/weather"
30
+ require "pincerna/bookmark"
@@ -0,0 +1,258 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the pincerna gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
4
+ # Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
5
+ #
6
+
7
+ # A bunch of useful Alfred 2 workflows.
8
+ module Pincerna
9
+ # Base class for all filter.
10
+ #
11
+ # @attribute [r] output
12
+ # @return [String] The output of filtering.
13
+ # @attribute [r] format
14
+ # @return [Symbol] The format of output. Can be `:xml` (default) or `:yml`.
15
+ # @attribute [r] format_content_type
16
+ # @return [String] The content type of the format. Can be `text/xml` (default) or `text/x-yaml`.
17
+ class Base
18
+ # The expression to match.
19
+ MATCHER = /^(?<all>.*)$/i
20
+
21
+ # Relevant groups in the match.
22
+ RELEVANT_MATCHES = {
23
+ "all" => ->(_, value) { value }
24
+ }
25
+
26
+ # Recognized types of filtering
27
+ TYPES = {
28
+ "unit_conversion" => /^(convert|unit|c)$/,
29
+ "currency_conversion" => /^(currency|cc)$/,
30
+ "translation" => /^(translate|t)$/,
31
+ "map" => /^(map|m)$/,
32
+ "weather" => /^(forecast|weather)$/,
33
+ "ip" => /^ip$/,
34
+ "vpn" => /^vpn$/,
35
+ "chrome_bookmark" => /^(chrome-bookmark|bc)$/,
36
+ "safari_bookmark" => /^(safari-bookmark|bs)$/,
37
+ "firefox_bookmark" => /^(firefox-bookmark|bf)$/
38
+ }
39
+
40
+ # The full name of the gem
41
+ FULL_NAME = "it.cowtech.pincerna"
42
+
43
+ # The root of the pincerna gem
44
+ ROOT = File.expand_path(File.dirname(__FILE__) + "/../../")
45
+
46
+ # The root of the cache
47
+ CACHE_ROOT = File.expand_path("~/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/#{FULL_NAME}")
48
+
49
+ # The root of alfred workflows
50
+ WORKFLOW_ROOT = File.expand_path("~/Library/Application Support/Alfred 2/Alfred.alfredpreferences/workflows/#{FULL_NAME}")
51
+
52
+ attr_reader :output, :format, :format_content_type
53
+
54
+ # Executes a filtering query.
55
+ #
56
+ # @param type [Symbol] The type of the query.
57
+ # @param query [String] The argument of the query.
58
+ # @param format [String] The format to use. Valid values are `:xml` (default) and `:yml`.
59
+ # @param debug [String] The debug mode.
60
+ # @return [String] The result of the query.
61
+ def self.execute!(type, query, format = :xml, debug = nil)
62
+ instance = nil
63
+
64
+ type = catch(:type) do
65
+ TYPES.each do |file, matcher|
66
+ throw(:type, file) if type =~ matcher
67
+ end
68
+
69
+ nil
70
+ end
71
+
72
+ if type
73
+ instance = find_class(type).new(query, format, debug)
74
+ instance.filter
75
+ end
76
+
77
+ instance
78
+ end
79
+
80
+ # Creates a new query.
81
+ #
82
+ # @param query [String] The argument of the query.
83
+ # @param requested_format [String] The format to use. Valid values are `xml` (default), `yaml` or `yml`.
84
+ # @param debug [String] The debug mode.
85
+ def initialize(query, requested_format = "xml", debug = nil)
86
+ @query = query.strip.gsub("\\ ", " ")
87
+ @cache_dir = CACHE_ROOT
88
+
89
+ if requested_format =~ /^y(a?)ml$/i then
90
+ @format = :yml
91
+ @format_content_type = "text/x-yaml"
92
+ else
93
+ @format = :xml
94
+ @format_content_type = "text/xml"
95
+ end
96
+
97
+ @debug = debug
98
+ @feedback_items = []
99
+ end
100
+
101
+ # Filters a query.
102
+ #
103
+ # @return [String] The feedback items of the query, formatted as XML.
104
+ def filter
105
+ # Match the query
106
+ matches = self.class::MATCHER.match(@query)
107
+
108
+ if matches then
109
+ # Execute the filtering
110
+ results = execute_filtering(matches)
111
+
112
+ # Show results if appropriate
113
+ process_results(results).each {|r| add_feedback_item(r) } if !results.empty?
114
+ end
115
+
116
+ output_feedback
117
+ end
118
+
119
+ # Filters a query.
120
+ #
121
+ # @param args [Array] The arguments of the query.
122
+ # @return [Array] A list of items to process.
123
+ def perform_filtering(*args)
124
+ raise ArgumentError.new("Must be overriden by subclasses.")
125
+ end
126
+
127
+ # Processes items to obtain feedback items.
128
+ #
129
+ # @param results [Array] The items to process.
130
+ # @return [Array] The feedback items.
131
+ def process_results(results)
132
+ raise ArgumentError.new("Must be overriden by subclasses.")
133
+ end
134
+
135
+ # Adds a feedback items.
136
+ #
137
+ # @param item [Array] The items to add.
138
+ def add_feedback_item(item)
139
+ @feedback_items << item
140
+ end
141
+
142
+ # Prepares the feedback for output.
143
+ def output_feedback
144
+ if format == :xml then
145
+ @output = Nokogiri::XML::Builder.new { |xml|
146
+ xml.items do
147
+ @feedback_items.each do |item|
148
+ childs, attributes = split_output_item(item)
149
+
150
+ xml.item(attributes) do
151
+ childs.each { |name, value| xml.send(name, value) }
152
+ end
153
+ end
154
+ end
155
+ }.to_xml
156
+ else
157
+ @output = @feedback_items.to_yaml
158
+ end
159
+ end
160
+
161
+ # Rounds a float to a certain precision.
162
+ #
163
+ # @param value [Float] The value to convert.
164
+ # @param precision [Fixnum] The precision to use.
165
+ # @return [Float] The rounded value.
166
+ def round_float(value, precision = 3)
167
+ factor = 10**precision
168
+ (value * factor).round.to_f / factor
169
+ end
170
+
171
+ # Rounds a float to a certain precision and prints it as a string. Unneeded leading zero are remove.
172
+ #
173
+ # @param value [Float] The value to print.
174
+ # @param precision [Fixnum] The precision to use.
175
+ # @return [String] The formatted value.
176
+ def format_float(value, precision = 3)
177
+ value = round_float(value, precision)
178
+ value = "%0.#{precision}f" % value
179
+ value.gsub(/\.?0+$/, "")
180
+ end
181
+
182
+ protected
183
+ # Instantiates the new class.
184
+ #
185
+ # @param file [String] The file name.
186
+ def self.find_class(file)
187
+ Pincerna.const_get(file.capitalize.gsub(/_(.)/) { $1.upcase})
188
+ end
189
+
190
+ # Executes filtering on matched data.
191
+ #
192
+ # @param matches [MatchData] The matched data.
193
+ # @return [Array] The results of filtering.
194
+ def execute_filtering(matches)
195
+ # Get relevant matches and arguments.
196
+ relevant = self.class::RELEVANT_MATCHES
197
+ args = relevant.map {|key, value| value.call(self, matches[key]) }
198
+
199
+ # Now perform the operation
200
+ begin
201
+ perform_filtering(*args)
202
+ rescue => e
203
+ raise e if debug_mode == :error
204
+ []
205
+ end
206
+ end
207
+
208
+ # Converts an array of key-value pairs to an hash.
209
+ #
210
+ # @param array [Array] The array to convert.
211
+ # @return [Hash] The converted hash.
212
+ def array_to_hash(array)
213
+ array.reduce({}){ |rv, entry|
214
+ rv[entry[0]] = entry[1]
215
+ rv
216
+ }
217
+ end
218
+
219
+ # Gets attributes and children for output.
220
+ #
221
+ # @param item [Hash] The output item.
222
+ # @return [Array] An array with children and attributes.
223
+ def split_output_item(item)
224
+ item.partition {|k, _| [:title, :subtitle, :icon].include?(k) }.map {|a| array_to_hash(a) }
225
+ end
226
+
227
+ # Fetches remote JSON resource.
228
+ #
229
+ # @param url [String] The URL to get.
230
+ # @param params [Hash] The parameters to pass to the server.
231
+ # @param json [Boolean] If the response is a JSON object.
232
+ # @return [Hash] The server's response.
233
+ def fetch_remote_resource(url, params, json = true)
234
+ args = {query: params}
235
+ args[:head] = {"accept" => "application/json"} if json
236
+ response = EM::HttpRequest.new(url, {connect_timeout: 5}).get(args).response
237
+ json ? Oj.load(response) : response
238
+ end
239
+
240
+ # Executes a command and returns its output.
241
+ #
242
+ # @param args [Array] The command to execute, with its arguments.
243
+ # @return [String] The output of the command
244
+ def execute_command(*args)
245
+ rv = ""
246
+ IO.popen(args) {|f| rv = f.read }
247
+ rv
248
+ end
249
+
250
+ # Returns the current debug mode.
251
+ #
252
+ # @return [Boolean|NilClass] The current debug mode.
253
+ def debug_mode
254
+ mode = ENV["PINCERNA_DEBUG"] || @debug
255
+ !mode.nil? ? mode.to_sym : nil
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the pincerna gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
4
+ # Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
5
+ #
6
+
7
+ module Pincerna
8
+ # Show the list of Safari bookmarks.
9
+ class Bookmark < Base
10
+ # The expression to match.
11
+ MATCHER = /^(?<all>.*)$/i
12
+
13
+ # The icon to show for each feedback item.
14
+ ICON = Pincerna::Base::ROOT + "/images/safari.png"
15
+
16
+ # The separator for paths.
17
+ SEPARATOR = "\u2192"
18
+
19
+ # Reads the list of Chrome Bookmarks.
20
+ #
21
+ # @param query [Array] A query to match against bookmarks names.
22
+ # @return [Array] A list of boomarks.
23
+ def perform_filtering(query)
24
+ Pincerna::Cache.instance.use("bookmarks:#{self.class.to_s}", Pincerna::Cache::EXPIRATIONS[:short]) do
25
+ # Get bookmarks and then only keep valid ones
26
+ @bookmarks = []
27
+ read_bookmarks
28
+ filter_and_sort(@bookmarks, query)
29
+ end
30
+ end
31
+
32
+ # Processes items to obtain feedback items.
33
+ #
34
+ # @param results [Array] The items to process.
35
+ # @return [Array] The feedback items.
36
+ def process_results(results)
37
+ results.map do |result|
38
+ subtitle = result[:path] ? result[:path].gsub(/^\s\u2192 /, "") : "Action this item to open the URL in the browser ..."
39
+ {title: result[:name], arg: result[:url], subtitle: subtitle, icon: ICON}
40
+ end
41
+ end
42
+
43
+ # Reads the list of bookmarks.
44
+ def read_bookmarks
45
+ raise ArgumentError.new("Must be overriden by subclasses.")
46
+ end
47
+
48
+ private
49
+ # Filters and sorts bookmarks.
50
+ #
51
+ # @param bookmarks [Array] The bookmarks list.
52
+ # @param query [String] The query to filter.
53
+ def filter_and_sort(bookmarks, query)
54
+ # Filtering
55
+ matcher = !query.empty? ? /^#{Regexp.escape(query.downcase)}/i : nil
56
+ bookmarks = bookmarks.select {|bookmark| bookmark[:name] =~ matcher } if matcher
57
+
58
+ # Sorting
59
+ bookmarks.sort {|first, second|
60
+ cmp = first[:name] <=> second[:name]
61
+ cmp = first[:path] <=> second[:path] if cmp == 0
62
+ cmp
63
+ }
64
+ end
65
+
66
+ protected
67
+ # Adds a bookmarks to the list.
68
+ #
69
+ # @param title [String] The title of the bookmark.
70
+ # @param url [String] The URL of the bookmark.
71
+ # @param path [String] The id of the parent folder.
72
+ def add_bookmark(title, url, path)
73
+ @bookmarks << {name: title, url: url, path: path} if title && !title.empty? && url && !url.empty? && url !~ /^javascript:/
74
+ end
75
+ end
76
+ end
77
+
78
+ require "pincerna/safari_bookmark"
79
+ require "pincerna/firefox_bookmark"
80
+ require "pincerna/chrome_bookmark"
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the pincerna gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
4
+ # Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
5
+ #
6
+
7
+ module Pincerna
8
+ # A utility class to handle caching.
9
+ #
10
+ # @attribute [r] data
11
+ # @return [Daybreak::DB] The cache store.
12
+ class Cache
13
+ # Expiration of keys.
14
+ EXPIRATIONS = {short: 1800, long: 2592000} # 30 min, 1 month
15
+
16
+ # Location of the cache file
17
+ FILE = Pincerna::Base::CACHE_ROOT + "/cache.db"
18
+
19
+ attr_reader :data
20
+
21
+ # Returns the instance of the cache.
22
+ def self.instance
23
+ @instance ||= Pincerna::Cache.new
24
+ end
25
+
26
+ # Creates a new cache object.
27
+ def initialize
28
+ FileUtils.mkdir_p(File.dirname(FILE))
29
+ @data = Daybreak::DB.new(FILE)
30
+ @flusher = EM.add_periodic_timer(5) { Pincerna::Cache.instance.flush }
31
+ end
32
+
33
+ # Closes the cache data.
34
+ def destroy
35
+ @flusher.cancel
36
+ @data.close rescue nil
37
+ end
38
+
39
+ # Flush data into disk.
40
+ def flush
41
+ @data.flush
42
+ @data.compact
43
+ end
44
+
45
+ # Use data from cache or fetches new data.
46
+ #
47
+ # @param key [String] The key of the data.
48
+ # @param expiration [Fixnum] Expiration of new data, in seconds.
49
+ def use(key, expiration)
50
+ value = @data[key]
51
+
52
+ if !value || Time.now.to_f > value[:expiration] then
53
+ data = yield
54
+ @data[key] = {data: data, expiration: Time.now.to_f + expiration}
55
+ data
56
+ else
57
+ value[:data]
58
+ end
59
+ end
60
+ end
61
+ end