rescuetime 0.3.2 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rescuetime/formatters/base_formatter'
4
+
5
+ module Rescuetime
6
+ # Contains all Rescuetime formatters and formatter-loading behavior
7
+ # @since v0.4.0
8
+ module Formatters
9
+ # The superclass for all valid Rescuetime formatters
10
+ BASE_FORMATTER = Rescuetime::Formatters::BaseFormatter
11
+ # The path where local in-gem formatters are stored. Expands to
12
+ # lib/rescuetime/formatters/*_formatter.rb
13
+ LOCAL_FORMATTER_PATH = '../formatters/*_formatter.rb'.freeze
14
+
15
+ # Returns a list of known formatters. A known formatter is either in the
16
+ # local gem folder lib/rescuetime/formatters/ and ends in '_formatter.rb',
17
+ # or it follows the path-matching options provided by the user using
18
+ # Rescuetime.configure.
19
+ #
20
+ # @param [Class#descendents] base_formatter the formatter superclass -
21
+ # #load_formatters returns all
22
+ # classes that inherit from this
23
+ # class
24
+ # @return [Array<Class>] known report formatters
25
+ #
26
+ # @see Rescuetime::Configuration.formatter_paths
27
+ def load_formatters(base_formatter: BASE_FORMATTER)
28
+ load_formatter_files
29
+ base_formatter.descendents
30
+ end
31
+
32
+ private
33
+
34
+ # Requires all formatter files, determined by the local path for formatters
35
+ # plus any additional paths set in the Rescuetime configuration.
36
+ #
37
+ # @param [String] local_path the location of the local in-gem formatters
38
+ #
39
+ # @see Rescuetime::Configuration.formatter_paths
40
+ def load_formatter_files(local_path: LOCAL_FORMATTER_PATH)
41
+ # require all formatters, local and configured
42
+ paths = Rescuetime.configuration.formatter_paths << local_path
43
+ paths.each do |path|
44
+ Dir[File.expand_path(path, __FILE__)].each { |file| require file }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rescuetime::Formatters
4
+ # Formats a rescuetime report as an array of hashes.
5
+ #
6
+ # @since v0.4.0
7
+ class ArrayFormatter < BaseFormatter
8
+ # Returns the name of your formatter
9
+ #
10
+ # @return [String] a name for your report formatter ("array")
11
+ def self.name
12
+ 'array'
13
+ end
14
+
15
+ # Formats the rescuetime report from CSV to a user-defined format
16
+ #
17
+ # @param [#to_a] report a csv-formatted report
18
+ # @return [Array<Hash>] a report formatted as an array of hashes
19
+ def self.format(report)
20
+ report.to_a.map(&:to_hash)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rescuetime::Formatters
4
+ # Base class for report formatters
5
+ #
6
+ # @abstract Subclass and override {.name} and {.format} to implement a custom
7
+ # formatter
8
+ # @see Rescuetime::Formatters::CSVFormatter
9
+ # @see Rescuetime::Formatters::ArrayFormatter
10
+ # @since v0.4.0
11
+ class BaseFormatter
12
+ # Returns the name of your formatter
13
+ #
14
+ # @return [String] a name for your report formatter
15
+ #
16
+ # @raise [NotImplementedError] this method is not yet implemented
17
+ def self.name
18
+ raise NotImplementedError, 'you have not defined a report name'
19
+ end
20
+
21
+ # Formats the rescuetime report from CSV to a user-defined format
22
+ #
23
+ # @param [CSV] _report a csv-formatted report
24
+ # @return a report formatted to your specifications
25
+ #
26
+ # @raise [NotImplementedError] this method is not yet implemented
27
+ def self.format(_report)
28
+ raise NotImplementedError,
29
+ 'you have not defined report formatting instructions'
30
+ end
31
+
32
+ # Returns all classes descended from the current class
33
+ #
34
+ # @example
35
+ # require 'rescuetime/formatters/array_formatter'
36
+ # base_formatter = Rescuetime::Formatters::BaseFormatter
37
+ #
38
+ # base_formatter.descendents
39
+ # #=> [Rescuetime::Formatters::ArrayFormatter]
40
+ #
41
+ # @return [Array<Class>] all loaded descendents of the current class
42
+ def self.descendents
43
+ ObjectSpace.each_object(Class).select { |klass| klass < self }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rescuetime::Formatters
4
+ # Formats a rescuetime report as a CSV object
5
+ #
6
+ # @since v0.4.0
7
+ class CSVFormatter < BaseFormatter
8
+ # Returns the name of your formatter
9
+ #
10
+ # @return [String] a name for your report formatter ("csv")
11
+ def self.name
12
+ 'csv'
13
+ end
14
+
15
+ # Returns the rescuetime report in raw form
16
+ #
17
+ # @param [CSV] report a csv-formatted report
18
+ # @return [CSV] a csv-formatted report
19
+ def self.format(report)
20
+ report
21
+ end
22
+ end
23
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rescuetime/date_parser'
2
4
 
3
5
  module Rescuetime
@@ -10,14 +12,14 @@ module Rescuetime
10
12
  # Parameters that are included by default in every query
11
13
  BASE_PARAMS = { format: 'csv',
12
14
  operation: 'select',
13
- version: 0 }
15
+ version: 0 }.freeze
14
16
 
15
17
  # Valid values for the :order and :interval parameters
16
18
  # @see #order_by
17
19
  VALID = {
18
20
  order_by: %w(time rank member),
19
21
  interval: %w(minute hour day week month)
20
- }
22
+ }.freeze
21
23
 
22
24
  # Returns a Rescuetime overview report, grouping activities into their
23
25
  # top-level categories
@@ -167,10 +169,10 @@ module Rescuetime
167
169
  # @example Invalid Values Raise Errors
168
170
  # client = Rescuetime::Client.new
169
171
  # client.order_by 'invalid'
170
- # # => Rescuetime::InvalidQueryError: invalid is not a valid order
172
+ # # => Rescuetime::Errors::InvalidQueryError: invalid is not a valid order
171
173
  #
172
174
  # client.order_by 'time', interval: 'invalid'
173
- # # => Rescuetime::InvalidQueryError: invalid is not a valid interval
175
+ # # => Rescuetime::Errors::InvalidQueryError: invalid is not a valid interval
174
176
  #
175
177
  # @param [#to_s] order an order for the results (ex. 'time')
176
178
  # @param [#intern, nil] interval a chunking interval for results
@@ -179,7 +181,7 @@ module Rescuetime
179
181
  # @return [Rescuetime::Collection] a Rescuetime Collection specifying order
180
182
  # and interval (if set)
181
183
  #
182
- # @raise [Rescuetime::InvalidQueryError] if either order or interval are
184
+ # @raise [Rescuetime::Errors::InvalidQueryError] if either order or interval are
183
185
  # invalid
184
186
  #
185
187
  # @see https://www.rescuetime.com/apidoc#paramlist Rescuetime API docs
@@ -193,11 +195,11 @@ module Rescuetime
193
195
 
194
196
  # guards against invalid order or interval
195
197
  unless valid_order? order
196
- fail InvalidQueryError, "#{order} is not a valid order"
198
+ raise Errors::InvalidQueryError, "#{order} is not a valid order"
197
199
  end
198
200
 
199
201
  unless valid_interval? interval
200
- fail InvalidQueryError, "#{interval} is not a valid interval"
202
+ raise Errors::InvalidQueryError, "#{interval} is not a valid interval"
201
203
  end
202
204
 
203
205
  add_to_query perspective: (order == 'time' ? 'interval' : order),
@@ -225,7 +227,7 @@ module Rescuetime
225
227
  # @return [Rescuetime::Collection] a Rescuetime Collection specifying
226
228
  # report date
227
229
  #
228
- # @raise [Rescuetime::InvalidQueryError] if the date format is invalid
230
+ # @raise [Rescuetime::Errors::InvalidQueryError] if the date format is invalid
229
231
  #
230
232
  # @see #from
231
233
  # @see #to
@@ -256,7 +258,7 @@ module Rescuetime
256
258
  # @return [Rescuetime::Collection] a Rescuetime Collection specifying
257
259
  # report date
258
260
  #
259
- # @raise [Rescuetime::InvalidQueryError] if the date format is invalid
261
+ # @raise [Rescuetime::Errors::InvalidQueryError] if the date format is invalid
260
262
  #
261
263
  # @see #to
262
264
  # @see #date
@@ -286,13 +288,13 @@ module Rescuetime
286
288
  # #=> [ ... ]
287
289
  #
288
290
  # client.to(1.week.ago).all # invalid!
289
- # #=> Rescuetime::InvalidQueryError
291
+ # #=> Rescuetime::Errors::InvalidQueryError
290
292
  #
291
293
  # @param [#strftime, String] date a valid date-like object
292
294
  # @return [Rescuetime::Collection] a Rescuetime Collection specifying
293
295
  # report date
294
296
  #
295
- # @raise [Rescuetime::InvalidQueryError] if the date format is invalid
297
+ # @raise [Rescuetime::Errors::InvalidQueryError] if the date format is invalid
296
298
  #
297
299
  # @see #from
298
300
  # @see #date
@@ -302,7 +304,7 @@ module Rescuetime
302
304
  # (see: restrict_end)
303
305
  # @since v0.3.0
304
306
  def to(date)
305
- add_to_query restrict_end: to_formatted_s(date)
307
+ add_to_query restrict_end: to_formatted_s(date)
306
308
  end
307
309
 
308
310
  # Limits the Rescuetime report to specific activities and documents.
@@ -348,7 +350,7 @@ module Rescuetime
348
350
  #
349
351
  # @return [Rescuetime::Collection] a Rescuetime Collection specifying
350
352
  # category name and (optionally) document
351
- # @raises [ArgumentError] if name is not set
353
+ # @raise [ArgumentError] if name is not set
352
354
  #
353
355
  # @see #overview
354
356
  # @see #activities
@@ -359,7 +361,7 @@ module Rescuetime
359
361
  # @since v0.3.0
360
362
  def where(name: nil, document: nil)
361
363
  # Stand-in for required keyword arguments
362
- name or fail ArgumentError, 'missing keyword: name'
364
+ name || raise(ArgumentError, 'missing keyword: name')
363
365
 
364
366
  add_to_query restrict_thing: name,
365
367
  restrict_thingy: document
@@ -372,7 +374,7 @@ module Rescuetime
372
374
  # @param [Hash] terms a set of terms to add to the query
373
375
  # @return [Rescuetime::Collection]
374
376
  def add_to_query(**terms)
375
- if self.is_a? Rescuetime::Collection
377
+ if is_a? Rescuetime::Collection
376
378
  self << terms
377
379
  self
378
380
  else
@@ -422,7 +424,7 @@ module Rescuetime
422
424
  # Rescuetime::DateParser
423
425
  # @return [String] a string in 'YYYY-MM-DD' format
424
426
  #
425
- # @raise [Rescuetime::InvalidQueryError] if the date format is invalid
427
+ # @raise [Rescuetime::Errors::InvalidQueryError] if the date format is invalid
426
428
  #
427
429
  # @see Rescuetime::DateParser
428
430
  # @see Rescuetime::DateParser::DATE_FORMATS Valid date formats
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rescuetime/formatters'
4
+
5
+ module Rescuetime
6
+ # Represents the collection of report formatters. Autoloads all formatters
7
+ # based on filename and file location.
8
+ # @since v0.4.0
9
+ class ReportFormatters
10
+ include Rescuetime::Formatters
11
+
12
+ # Returns or loads a memoized list of formatters
13
+ #
14
+ # @example
15
+ # report_formatters = Rescuetime::ReportFormatters.new.formatters
16
+ # #=> [Rescuetime::Formatters::ArrayFormatter,
17
+ # # Rescuetime::Formatters::CSVFormatter]
18
+ #
19
+ # @return [Array<Class>] list of available report formatters
20
+ def formatters
21
+ @formatters ||= load_formatters
22
+ end
23
+
24
+ # Force a reload of report formatters
25
+ #
26
+ # @example
27
+ # report_formatters = Rescuetime::ReportFormatters.new
28
+ # report_formatters.formatters
29
+ # #=> [Rescuetime::Formatters::ArrayFormatter,
30
+ # # Rescuetime::Formatters::CSVFormatter]
31
+ #
32
+ # Rescuetime.configure do |config|
33
+ # config.formatter_paths << 'local/formatters/html_formatter.rb'
34
+ # end
35
+ #
36
+ # report_formatters.reload
37
+ # #=> [Rescuetime::Formatters::ArrayFormatter,
38
+ # # Rescuetime::Formatters::CSVFormatter,
39
+ # # Rescuetime::Formatters::HTMLFormatter]
40
+ #
41
+ # report_formatters.formatters
42
+ # #=> [Rescuetime::Formatters::ArrayFormatter,
43
+ # # Rescuetime::Formatters::CSVFormatter,
44
+ # # Rescuetime::Formatters::HTMLFormatter]
45
+ #
46
+ # @return [Array<Class>] list of available report formatters
47
+ def reload
48
+ @formatters = load_formatters
49
+ end
50
+
51
+ # Returns a list of available formatter names
52
+ #
53
+ # @example
54
+ # report_formatters = Rescuetime::ReportFormatters.new
55
+ # report_formatters.all
56
+ # #=> ["array", "csv"]
57
+ #
58
+ # @return [Array<String>] a list of formatter names
59
+ def all
60
+ formatters.map(&:name)
61
+ end
62
+
63
+ # Returns the formatter with the specified name or, if not found, raises
64
+ # an exception
65
+ #
66
+ # @param [String] name the name of the desired formatter
67
+ # @return [Class] the specified formatter
68
+ #
69
+ # @raise [Rescuetime::Errors::InvalidFormatError]
70
+ def find(name)
71
+ formatter = formatters.find do |f|
72
+ standardize(f.name) == standardize(name)
73
+ end
74
+ formatter || raise(Rescuetime::Errors::InvalidFormatError)
75
+ end
76
+
77
+ private
78
+
79
+ def standardize(str)
80
+ str.to_s.downcase
81
+ end
82
+ end
83
+ end
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rescuetime/core_extensions/string'
4
+
1
5
  module Rescuetime
2
6
  # The Rescuetime::Requestable module contains client methods relating to
3
7
  # sending HTTP requests
@@ -10,7 +14,7 @@ module Rescuetime
10
14
  key_not_found: '"error":"# key not found","messages":"key not found"',
11
15
  query: '"error": "# query error",'\
12
16
  '"messages": "Error: Likely a badly formatted or missing parameter"'
13
- }
17
+ }.freeze
14
18
 
15
19
  class << self
16
20
  # Performs the GET request to the specified host. Before making the
@@ -19,52 +23,77 @@ module Rescuetime
19
23
  #
20
24
  # @param [String] host request host
21
25
  # @param [Hash] params request parameters
22
- # @param [#get] http HTTP requester
23
- # @return [Faraday::Response]
26
+ # @return [String]
24
27
  #
25
- # @raise [Rescuetime::MissingCredentialsError] if no api key is present
26
- # @raise [Rescuetime::InvalidCredentialsError] if api key is incorrect
27
- # @raise [Rescuetime::InvalidQueryError] if query is badly formed
28
- # @raise [Rescuetime::Error] if response HTTP status is
28
+ # @raise [Rescuetime::Errors::MissingCredentialsError] if no api key is present
29
+ # @raise [Rescuetime::Errors::InvalidCredentialsError] if api key is incorrect
30
+ # @raise [Rescuetime::Errors::InvalidQueryError] if query is badly formed
31
+ # @raise [Rescuetime::Errors::Error] if response HTTP status is
29
32
  # not 200
30
33
  #
34
+ # @see http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/Net/HTTP.html#method-c-get_response
35
+ # Net::HTTP.get_response
31
36
  # @see Rescuetime::Client#api_key=
32
- def get(host, params, http: Faraday)
33
- # Fail if no api key is provided
34
- unless params[:key] && !params[:key].to_s.empty?
35
- fail(Rescuetime::MissingCredentialsError)
36
- end
37
+ def get(host, params)
38
+ # guard clause: fail if no API key is present
39
+ key = CoreExtensions::String.new params[:key].to_s # adds #present?
40
+ key.present? || raise(Errors::MissingCredentialsError)
37
41
 
38
- req_params = params.delete_if { |_, v| !v || v.to_s.empty? }
39
- response = http.get host, req_params
42
+ uri = set_uri host, params
43
+ response = Net::HTTP.get_response uri
40
44
 
41
- fail_or_return response
45
+ fail_or_return_body response
42
46
  end
43
47
 
44
48
  private
45
49
 
50
+ # Takes a host and collection of parameters and returns the associated
51
+ # URI object. Required for the Net::HTTP.get_response method.
52
+ #
53
+ # @param [String] host target API endpoint
54
+ # @param [Hash] params collection of query parameters
55
+ # @return [URI] URI object with host and query parameters
56
+ #
57
+ # @see Requester#get
58
+ # @see http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/Net/HTTP.html#method-c-get_response
59
+ # Net::HTTP.get_response
60
+ # @since v0.3.3
61
+ def set_uri(host, params)
62
+ # delete params with empty values
63
+ req_params = params.delete_if do |_key, value|
64
+ # type conversion required because symbols/ints can't be extended
65
+ value = CoreExtensions::String.new value.to_s # add #blank?
66
+ value.blank?
67
+ end
68
+
69
+ uri = URI(host)
70
+ uri.query = URI.encode_www_form(req_params)
71
+ uri
72
+ end
73
+
46
74
  # Checks for an error in the Rescuetime response. If an error was recieved
47
- # raise the appropriate Rescuetime::Error. Otherwise, return the response.
75
+ # raise the appropriate Rescuetime::Errors::Error. Otherwise, return the response.
48
76
  #
49
- # @param [Faraday::Response] response HTTP response from rescuetime.com
50
- # @return [Faraday::Response] 200 HTTP response from rescuetime.com
77
+ # @param [Net::HTTPResponse] response HTTP response from rescuetime.com
78
+ # @return [String] valid response body
51
79
  #
52
- # @raise [Rescuetime::InvalidCredentialsError] if api key is incorrect
53
- # @raise [Rescuetime::InvalidQueryError] if query is badly formed
54
- # @raise [Rescuetime::Error] an error that varies based on the
80
+ # @raise [Rescuetime::Errors::InvalidCredentialsError] if api key is incorrect
81
+ # @raise [Rescuetime::Errors::InvalidQueryError] if query is badly formed
82
+ # @raise [Rescuetime::Errors::Error] an error that varies based on the
55
83
  # response status
56
- def fail_or_return(response)
84
+ def fail_or_return_body(response)
57
85
  # match the response body to known error messages with 200 status
58
86
  case response.body
59
- when key_not_found? then fail Rescuetime::InvalidCredentialsError
60
- when invalid_query? then fail Rescuetime::InvalidQueryError
87
+ when key_not_found? then raise Rescuetime::Errors::InvalidCredentialsError
88
+ when invalid_query? then raise Rescuetime::Errors::InvalidQueryError
61
89
  end
62
90
 
63
91
  # check the response status for errors
64
- error = Rescuetime::Error::CODES[response.status.to_i]
65
- fail(error) if error
92
+ status = response.code.to_i
93
+ error = Rescuetime::Errors::Error::CODES[status]
94
+ raise(error) if error
66
95
 
67
- response
96
+ response.body
68
97
  end
69
98
 
70
99
  # Returns lambda that returns true if the response body states that the