rescuetime 0.3.3 → 0.4.0

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.
@@ -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,58 +23,75 @@ 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
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: Net::HTTP)
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
42
  uri = set_uri host, params
39
- response = http.get_response uri
43
+ response = Net::HTTP.get_response uri
40
44
 
41
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
46
61
  def set_uri(host, params)
47
- req_params = params.delete_if { |_, v| !v || v.to_s.empty? }
48
- uri = URI(host)
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)
49
70
  uri.query = URI.encode_www_form(req_params)
50
71
  uri
51
72
  end
52
73
 
53
74
  # Checks for an error in the Rescuetime response. If an error was recieved
54
- # raise the appropriate Rescuetime::Error. Otherwise, return the response.
75
+ # raise the appropriate Rescuetime::Errors::Error. Otherwise, return the response.
55
76
  #
56
77
  # @param [Net::HTTPResponse] response HTTP response from rescuetime.com
57
78
  # @return [String] valid response body
58
79
  #
59
- # @raise [Rescuetime::InvalidCredentialsError] if api key is incorrect
60
- # @raise [Rescuetime::InvalidQueryError] if query is badly formed
61
- # @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
62
83
  # response status
63
84
  def fail_or_return_body(response)
64
85
  # match the response body to known error messages with 200 status
65
86
  case response.body
66
- when key_not_found? then fail Rescuetime::InvalidCredentialsError
67
- 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
68
89
  end
69
90
 
70
91
  # check the response status for errors
71
92
  status = response.code.to_i
72
- error = Rescuetime::Error::CODES[status]
73
- fail(error) if error
93
+ error = Rescuetime::Errors::Error::CODES[status]
94
+ raise(error) if error
74
95
 
75
96
  response.body
76
97
  end