sferik 0.0.1

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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +5 -0
  3. data/CHANGELOG.md +72 -0
  4. data/LICENSE.md +16 -0
  5. data/README.md +517 -0
  6. data/exe/sferik +6 -0
  7. data/lib/sferik/api/code_endpoints.rb +37 -0
  8. data/lib/sferik/api/profile_endpoints.rb +113 -0
  9. data/lib/sferik/api/resume_endpoints.rb +41 -0
  10. data/lib/sferik/api/site_endpoints.rb +247 -0
  11. data/lib/sferik/api/talk_endpoints.rb +45 -0
  12. data/lib/sferik/api.rb +23 -0
  13. data/lib/sferik/block.rb +25 -0
  14. data/lib/sferik/body.rb +54 -0
  15. data/lib/sferik/cache.rb +361 -0
  16. data/lib/sferik/cli.rb +369 -0
  17. data/lib/sferik/client.rb +453 -0
  18. data/lib/sferik/collection.rb +82 -0
  19. data/lib/sferik/configuration.rb +129 -0
  20. data/lib/sferik/connections.rb +151 -0
  21. data/lib/sferik/contributions.rb +86 -0
  22. data/lib/sferik/day.rb +35 -0
  23. data/lib/sferik/dependency.rb +26 -0
  24. data/lib/sferik/deployment.rb +36 -0
  25. data/lib/sferik/errors.rb +234 -0
  26. data/lib/sferik/figure.rb +83 -0
  27. data/lib/sferik/finger.rb +74 -0
  28. data/lib/sferik/freshness.rb +54 -0
  29. data/lib/sferik/home/pages.rb +27 -0
  30. data/lib/sferik/home/profile.rb +51 -0
  31. data/lib/sferik/home/section.rb +27 -0
  32. data/lib/sferik/home.rb +38 -0
  33. data/lib/sferik/json_parsing.rb +28 -0
  34. data/lib/sferik/name_change.rb +49 -0
  35. data/lib/sferik/place.rb +33 -0
  36. data/lib/sferik/podcast.rb +41 -0
  37. data/lib/sferik/project.rb +49 -0
  38. data/lib/sferik/projects.rb +138 -0
  39. data/lib/sferik/push.rb +33 -0
  40. data/lib/sferik/resource.rb +373 -0
  41. data/lib/sferik/resume/award.rb +43 -0
  42. data/lib/sferik/resume/basics.rb +77 -0
  43. data/lib/sferik/resume/education.rb +59 -0
  44. data/lib/sferik/resume/location.rb +35 -0
  45. data/lib/sferik/resume/meta.rb +35 -0
  46. data/lib/sferik/resume/patent.rb +43 -0
  47. data/lib/sferik/resume/profile.rb +35 -0
  48. data/lib/sferik/resume/project.rb +27 -0
  49. data/lib/sferik/resume/skill.rb +27 -0
  50. data/lib/sferik/resume/speaking.rb +19 -0
  51. data/lib/sferik/resume/volunteer.rb +59 -0
  52. data/lib/sferik/resume/work.rb +51 -0
  53. data/lib/sferik/resume.rb +125 -0
  54. data/lib/sferik/session.rb +41 -0
  55. data/lib/sferik/social_profile.rb +49 -0
  56. data/lib/sferik/status/github.rb +38 -0
  57. data/lib/sferik/status/loaded.rb +48 -0
  58. data/lib/sferik/status.rb +33 -0
  59. data/lib/sferik/talk.rb +73 -0
  60. data/lib/sferik/talks.rb +79 -0
  61. data/lib/sferik/validation.rb +119 -0
  62. data/lib/sferik/version.rb +7 -0
  63. data/lib/sferik/web_finger/link.rb +46 -0
  64. data/lib/sferik/web_finger.rb +43 -0
  65. data/lib/sferik/who.rb +53 -0
  66. data/lib/sferik/whoami.rb +36 -0
  67. data/lib/sferik/wrapping.rb +114 -0
  68. data/lib/sferik.rb +246 -0
  69. data/sig/manifest.yaml +13 -0
  70. data/sig/sferik.rbs +825 -0
  71. metadata +118 -0
@@ -0,0 +1,373 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "json"
5
+ require "time"
6
+ require_relative "errors"
7
+ require_relative "wrapping"
8
+
9
+ module Sferik
10
+ # Parses the dates the API gives to the year, month, or day ("2026", "2015-11", "2014-04-22"), as the first day of
11
+ # the period. ISO 8601 allows all three, but Date.iso8601 doesn't take a year alone, and on JRuby not a month either.
12
+ #
13
+ # @api private
14
+ module PartialDate
15
+ # Parse a date given to the year, month, or day
16
+ #
17
+ # @api private
18
+ # @param value [String] the date
19
+ # @return [Date] the date, or the first day of the year or month
20
+ # @raise [Date::Error] if the value isn't an ISO 8601 date
21
+ # @example
22
+ # Sferik::PartialDate.iso8601("2015-11") # => #<Date: 2015-11-01>
23
+ def self.iso8601(value)
24
+ Date.iso8601(
25
+ case value
26
+ when /\A\d{4}\z/ then "#{value}-01-01"
27
+ when /\A\d{4}-\d{2}\z/ then "#{value}-01"
28
+ else value
29
+ end
30
+ )
31
+ end
32
+ end
33
+ private_constant :PartialDate
34
+
35
+ # Base class for objects that wrap sferik.net API responses
36
+ #
37
+ # A resource is immutable: its attributes are deep-frozen, and two resources with the same attributes are equal.
38
+ # It works with pattern matching, with the names of its readers as keys: a predicate's without its question mark,
39
+ # so that a pattern can bind it. A reader of a list returns an empty one when the response has none.
40
+ #
41
+ # The resources inside one are built when it is, and so are its dates and times, so a response that isn't what the
42
+ # API documents raises {InvalidResponse} from the endpoint that got it, not from a reader later on. A reader returns
43
+ # the same frozen object each time it's called.
44
+ #
45
+ # @api public
46
+ # @example Pattern matching
47
+ # case Sferik.contributions
48
+ # in {total:, longest_streak:, live:} then puts "#{total} contributions, #{longest_streak}-day streak"
49
+ # end
50
+ class Resource
51
+ include Wrapping
52
+
53
+ # What a reader of a list returns when the response has none, and the keys of a resource that reads none
54
+ EMPTY = [].freeze # steep:ignore UnannotatedEmptyCollection
55
+ private_constant :EMPTY
56
+
57
+ # The readers of a resource that declares none, and what a reader of a dictionary returns when the response has none
58
+ NONE = {}.freeze # steep:ignore UnannotatedEmptyCollection
59
+ private_constant :NONE
60
+
61
+ # The raw attributes from the API response
62
+ # @api public
63
+ # @return [Hash{String => Object}] the raw attributes, frozen
64
+ # @example
65
+ # talk.attributes["title"]
66
+ attr_reader :attributes
67
+
68
+ class << self
69
+ # The names of the readers this resource declares
70
+ #
71
+ # They are the keys of {#to_h}, and of pattern matching. A predicate goes by its name without the question mark: `featured?` is `:featured`.
72
+ #
73
+ # @api public
74
+ # @return [Array<Symbol>] the names, frozen
75
+ # @example
76
+ # Sferik::Talk.attribute_names # => [:title, :event, :date, :location, :slides, :video, :link, :featured]
77
+ def attribute_names
78
+ readers.keys.freeze
79
+ end
80
+
81
+ private
82
+
83
+ # The reader each name is read with
84
+ #
85
+ # A resource that declares none has those of the nearest resource it inherits from that does.
86
+ #
87
+ # @api private
88
+ # @return [Hash{Symbol => Symbol}] the readers by name, frozen
89
+ def readers
90
+ ancestors.filter_map { |ancestor| ancestor.instance_variable_get(:@readers) }.first || NONE
91
+ end
92
+
93
+ # The keys of the response this resource's readers read
94
+ #
95
+ # A resource that declares no readers has those of the nearest resource it inherits from that does.
96
+ #
97
+ # @api private
98
+ # @return [Array<String>] the keys, frozen
99
+ def keys
100
+ ancestors.filter_map { |ancestor| ancestor.instance_variable_get(:@keys) }.first || EMPTY
101
+ end
102
+
103
+ # The blocks that make what this resource prepares when it's built
104
+ #
105
+ # A resource that prepares none has those of the nearest resource it inherits from that does.
106
+ #
107
+ # @api private
108
+ # @return [Hash{Symbol => Proc}] the blocks by name, frozen
109
+ def preparations
110
+ ancestors.filter_map { |ancestor| ancestor.instance_variable_get(:@preparations) }.first || NONE
111
+ end
112
+
113
+ # Name a value to prepare when a resource is built
114
+ #
115
+ # The resource then has it as prepared(name).
116
+ #
117
+ # @api private
118
+ # @param name [Symbol] the name of the value
119
+ # @yield what makes the value, run as the resource once it has its attributes
120
+ # @yieldreturn [Object] the value
121
+ # @return [Hash{Symbol => Proc}] the blocks by name, frozen
122
+ def prepare(name, &value)
123
+ @preparations = preparations.merge(name => value).freeze
124
+ end
125
+
126
+ # Define a reader of a value that's prepared when a resource is built
127
+ #
128
+ # @api private
129
+ # @param name [Symbol] the name of the reader
130
+ # @param key [String] the key in the response
131
+ # @yield what makes the value, run as the resource once it has its attributes
132
+ # @yieldreturn [Object] the value
133
+ # @return [Symbol] the name of the reader
134
+ def reader(name, key, &value)
135
+ prepare(name, &value)
136
+ define_method(name) { prepared(name) } # steep:ignore NoMethod
137
+ record(name, key)
138
+ end
139
+
140
+ # Define a reader for an attribute
141
+ #
142
+ # @api private
143
+ # @param name [Symbol] the name of the reader
144
+ # @param key [String] the key in the response (defaults to the name, camelCased)
145
+ # @param type [Class, nil] a resource class to wrap the value in
146
+ # @return [Symbol] the name of the reader
147
+ def attribute(name, key = camelize(name), type: nil)
148
+ reader(name, key) { wrap(attributes[key], type) }
149
+ end
150
+
151
+ # Define a reader for a list, which is empty when the response has none
152
+ #
153
+ # @api private
154
+ # @param name [Symbol] the name of the reader
155
+ # @param key [String] the key in the response (defaults to the name, camelCased)
156
+ # @param type [Class, nil] a resource class to wrap each value of the list in
157
+ # @return [Symbol] the name of the reader
158
+ def list(name, key = camelize(name), type: nil)
159
+ reader(name, key) { wrap_list(name, given(attributes[key], EMPTY), type) }
160
+ end
161
+
162
+ # Define a predicate for a boolean attribute
163
+ #
164
+ # @api private
165
+ # @param name [Symbol] the name of the attribute (the reader is suffixed with a question mark)
166
+ # @param key [String] the key in the response
167
+ # @return [Symbol] the name of the attribute
168
+ def predicate(name, key = camelize(name))
169
+ reader = :"#{name}?"
170
+ define_method(reader) do
171
+ # @type self: Resource
172
+ attributes[key].eql?(true)
173
+ end
174
+ record(name, key, reader:)
175
+ end
176
+
177
+ # Define a reader for a dictionary, which is empty when the response has none
178
+ #
179
+ # A dictionary is a JSON object of resources by name.
180
+ #
181
+ # @api private
182
+ # @param name [Symbol] the name of the reader
183
+ # @param key [String] the key in the response (defaults to the name, camelCased)
184
+ # @param type [Class] a resource class to wrap each value of the dictionary in
185
+ # @return [Symbol] the name of the reader
186
+ def dictionary(name, key = camelize(name), type:)
187
+ reader(name, key) { wrap_dictionary(name, given(attributes[key], NONE), type) }
188
+ end
189
+
190
+ # Define a reader that parses an ISO 8601 date or time
191
+ #
192
+ # @api private
193
+ # @param name [Symbol] the name of the reader
194
+ # @param parser [#iso8601] Time, Date, or {PartialDate} for a date that may be given to the year or month
195
+ # @param key [String] the key in the response
196
+ # @return [Symbol] the name of the reader
197
+ def timestamp(name, parser, key = camelize(name))
198
+ reader(name, key) { wrap_timestamp(name, attributes[key], parser) }
199
+ end
200
+
201
+ # Name the readers that #inspect shows (the first three, unless declared)
202
+ #
203
+ # A resource whose first readers are long lists names shorter ones, to be read at a glance in a console.
204
+ #
205
+ # @api private
206
+ # @param names [Array<Symbol>] the readers, or any other public methods
207
+ # @return [Array<Symbol>] the readers
208
+ def inspect_with(*names)
209
+ @inspected = names
210
+ end
211
+
212
+ # The readers #inspect shows
213
+ #
214
+ # A resource that names none has those of the nearest resource it inherits from that does.
215
+ #
216
+ # @api private
217
+ # @return [Array<Symbol>] the readers
218
+ def inspected
219
+ ancestors.filter_map { |ancestor| ancestor.instance_variable_get(:@inspected) }.first || attribute_names.first(3)
220
+ end
221
+
222
+ # Note a reader and the key it reads
223
+ #
224
+ # @api private
225
+ # @param name [Symbol] the name the reader goes by
226
+ # @param key [String, nil] the key, or nil for a reader that reads nested keys itself
227
+ # @param reader [Symbol] the reader, if it isn't the name: a predicate's has a question mark
228
+ # @return [Symbol] the name
229
+ def record(name, key = nil, reader: name)
230
+ @readers = readers.merge(name => reader).freeze
231
+ @keys = [*keys, key].freeze if key
232
+ name
233
+ end
234
+
235
+ # The key a reader reads by default: its name, camelCased
236
+ #
237
+ # @api private
238
+ # @param name [Symbol] the reader
239
+ # @return [String] the key
240
+ def camelize(name)
241
+ name.to_s.gsub(/_[a-z]/) { |match| match.delete_prefix("_").upcase }
242
+ end
243
+ end
244
+
245
+ # Initialize a new resource
246
+ #
247
+ # The attributes are copied, and the copy frozen: what's given is left as it was. Their keys are the keys of the
248
+ # API's JSON, which are strings, at every depth: "startDate", not the name of the reader that reads it.
249
+ #
250
+ # The resources inside this one are built here, and its dates and times parsed, so what the readers return is
251
+ # checked once, now, rather than each time one is called.
252
+ #
253
+ # @api public
254
+ # @param attributes [Hash{String => Object}] the attributes from the API response
255
+ # @return [Resource] a new resource
256
+ # @raise [ArgumentError] if the attributes aren't a Hash, or a key of theirs isn't a String, at any depth
257
+ # @raise [InvalidResponse] if a value isn't what the API documents, at any depth: a list that isn't a JSON array, a
258
+ # resource that isn't a JSON object, or a date or time that isn't an ISO 8601 one
259
+ # @example
260
+ # Sferik::Talk.new("title" => "Writing Fast Ruby")
261
+ def initialize(attributes)
262
+ @attributes = deep_freeze(check(:attributes, attributes, Hash))
263
+ @prepared = self.class.__send__(:preparations).transform_values { |value| instance_exec(&value) }.freeze
264
+ freeze
265
+ end
266
+
267
+ # The attributes, read through the readers this resource declares
268
+ #
269
+ # @api public
270
+ # @return [Hash{Symbol => Object}] the values of the readers
271
+ # @example
272
+ # talk.to_h # => {title: "Writing Fast Ruby", ...}
273
+ def to_h
274
+ self.class.attribute_names.to_h { |name| [name, read(name)] }
275
+ end
276
+
277
+ # The resource as JSON: the JSON it came from
278
+ #
279
+ # @api public
280
+ # @param args [Array<Object>] what JSON.generate passes on: the state of the generator
281
+ # @return [String] the raw attributes, as JSON
282
+ # @example
283
+ # talk.to_json # => "{\"title\":\"Writing Fast Ruby\",...}"
284
+ def to_json(*args)
285
+ attributes.to_json(*args) # steep:ignore UnexpectedPositionalArgument
286
+ end
287
+
288
+ # The resource as ActiveSupport makes JSON of it: the JSON it came from
289
+ #
290
+ # ActiveSupport asks for this when a resource is inside something else it makes JSON of. Without it, a resource
291
+ # in a Hash that Rails renders as JSON would be its instance variables, and one that's Enumerable would be a list.
292
+ #
293
+ # @api public
294
+ # @param _options [Array<Object>] the options ActiveSupport passes on, which make no difference
295
+ # @return [Hash{String => Object}] the raw attributes, frozen
296
+ # @example
297
+ # talk.as_json # => {"title" => "Writing Fast Ruby", ...}
298
+ def as_json(*_options)
299
+ attributes
300
+ end
301
+
302
+ # The keys pattern matching asks for, from {#to_h}
303
+ #
304
+ # @api public
305
+ # @param keys [Array<Symbol>, nil] the keys asked for, or nil for all of them
306
+ # @return [Hash{Symbol => Object}] the values
307
+ # @example
308
+ # case talk
309
+ # in {title:, event:, featured:} then "#{title} at #{event}#{" (featured)" if featured}"
310
+ # end
311
+ def deconstruct_keys(keys)
312
+ names = keys ? self.class.attribute_names & keys : self.class.attribute_names
313
+ names.to_h { |name| [name, read(name)] }
314
+ end
315
+
316
+ # Whether another object is a resource of the same class with the same attributes
317
+ #
318
+ # @api public
319
+ # @param other [Object] the other object
320
+ # @return [Boolean]
321
+ # @example
322
+ # Sferik.whoami == Sferik.whoami # => true
323
+ def ==(other)
324
+ other.instance_of?(self.class) && other.attributes.eql?(attributes)
325
+ end
326
+ alias_method :eql?, :==
327
+
328
+ # A hash code, so equal resources can be keys of the same Hash entry
329
+ #
330
+ # @api public
331
+ # @return [Integer] the hash code
332
+ # @example
333
+ # {talk => true}
334
+ def hash
335
+ [self.class, attributes].hash
336
+ end
337
+
338
+ # A short description of the resource
339
+ #
340
+ # A date is shown as its day alone, without the Julian day numbers Date#inspect adds.
341
+ #
342
+ # @api public
343
+ # @return [String] the description
344
+ # @example
345
+ # talk.inspect # => "#<Sferik::Talk title=\"Writing Fast Ruby\" ...>"
346
+ def inspect
347
+ shown = self.class.__send__(:inspected).map do |name|
348
+ "#{name}=#{read(name).then { |value| value.instance_of?(Date) ? "#<Date: #{value}>" : value.inspect }}"
349
+ end
350
+ "#<#{[self.class, *shown].join(" ")}>"
351
+ end
352
+
353
+ private
354
+
355
+ # A value that was prepared when the resource was built
356
+ #
357
+ # @api private
358
+ # @param name [Symbol] the name the value was prepared under
359
+ # @return [Object] the value
360
+ def prepared(name)
361
+ @prepared.fetch(name)
362
+ end
363
+
364
+ # Read the value a name goes by
365
+ #
366
+ # @api private
367
+ # @param name [Symbol] the name of a reader, or of any other public method
368
+ # @return [Object] the value
369
+ def read(name)
370
+ public_send(self.class.__send__(:readers).fetch(name, name))
371
+ end
372
+ end
373
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../resource"
4
+
5
+ module Sferik
6
+ class Resume < Resource
7
+ # An honor, as returned in {Resume#awards}
8
+ # @api public
9
+ class Award < Resource
10
+ # @!method title
11
+ # The honor
12
+ # @api public
13
+ # @return [String] the honor
14
+ # @example
15
+ # award.title # => "Ruby Hero Award"
16
+ attribute :title
17
+
18
+ # @!method date
19
+ # When it was given
20
+ # @api public
21
+ # @return [Date] when it was given
22
+ # @example
23
+ # award.date # => #<Date: 2014-04-22>
24
+ timestamp :date, PartialDate
25
+
26
+ # @!method awarder
27
+ # Who gave it
28
+ # @api public
29
+ # @return [String] who gave it
30
+ # @example
31
+ # award.awarder # => "Ruby Heroes"
32
+ attribute :awarder
33
+
34
+ # @!method summary
35
+ # What it was for
36
+ # @api public
37
+ # @return [String] what it was for
38
+ # @example
39
+ # award.summary # => "Presented at RailsConf..."
40
+ attribute :summary
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../resource"
4
+ require_relative "location"
5
+ require_relative "profile"
6
+
7
+ module Sferik
8
+ class Resume < Resource
9
+ # Name, contact details, and summary, as returned by {Resume#basics}
10
+ # @api public
11
+ class Basics < Resource
12
+ # @!method name
13
+ # The name
14
+ # @api public
15
+ # @return [String] the name
16
+ # @example
17
+ # basics.name # => "Erik Berlin"
18
+ attribute :name
19
+
20
+ # @!method label
21
+ # The title
22
+ # @api public
23
+ # @return [String] the title
24
+ # @example
25
+ # basics.label # => "Software Engineer"
26
+ attribute :label
27
+
28
+ # @!method former_name
29
+ # The name before 2017 (an addition to JSON Resume)
30
+ # @api public
31
+ # @return [String] the name before 2017 (an addition to JSON Resume)
32
+ # @example
33
+ # basics.former_name # => "Erik Michaels-Ober"
34
+ attribute :former_name
35
+
36
+ # @!method email
37
+ # The email address
38
+ # @api public
39
+ # @return [String] the email address
40
+ # @example
41
+ # basics.email # => "sferik@gmail.com"
42
+ attribute :email
43
+
44
+ # @!method url
45
+ # The site
46
+ # @api public
47
+ # @return [String] the site
48
+ # @example
49
+ # basics.url # => "https://sferik.net"
50
+ attribute :url
51
+
52
+ # @!method summary
53
+ # Paragraphs separated by blank lines, with **bold** and *italic*
54
+ # @api public
55
+ # @return [String] paragraphs separated by blank lines, with **bold** and *italic*
56
+ # @example
57
+ # basics.summary # => "Entrepreneur, engineer, and manager..."
58
+ attribute :summary
59
+
60
+ # @!method location
61
+ # Where Erik lives
62
+ # @api public
63
+ # @return [Location] where Erik lives
64
+ # @example
65
+ # basics.location # => #<Sferik::Resume::Location ...>
66
+ attribute :location, "location", type: Location
67
+
68
+ # @!method profiles
69
+ # Profiles elsewhere
70
+ # @api public
71
+ # @return [Array<Profile>] profiles elsewhere
72
+ # @example
73
+ # basics.profiles # => [#<Sferik::Resume::Profile ...>, ...]
74
+ list :profiles, "profiles", type: Profile
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../resource"
4
+
5
+ module Sferik
6
+ class Resume < Resource
7
+ # A school, as returned in {Resume#education}
8
+ # @api public
9
+ class Education < Resource
10
+ # @!method institution
11
+ # The school
12
+ # @api public
13
+ # @return [String] the school
14
+ # @example
15
+ # school.institution # => "Carnegie Mellon University"
16
+ attribute :institution
17
+
18
+ # @!method location
19
+ # Where it is
20
+ # @api public
21
+ # @return [String] where it is
22
+ # @example
23
+ # school.location # => "Pittsburgh"
24
+ attribute :location
25
+
26
+ # @!method start_date
27
+ # When Erik started, as the first day of the month or year given
28
+ # @api public
29
+ # @return [Date] when Erik started, as the first day of the month or year given
30
+ # @example
31
+ # school.start_date # => #<Date: 2001-08-01>
32
+ timestamp :start_date, PartialDate
33
+
34
+ # @!method end_date
35
+ # When Erik finished
36
+ # @api public
37
+ # @return [Date, nil] when Erik finished
38
+ # @example
39
+ # school.end_date # => #<Date: 2005-05-01>
40
+ timestamp :end_date, PartialDate
41
+
42
+ # @!method highlights
43
+ # What Erik did there
44
+ # @api public
45
+ # @return [Array<String>] what Erik did there
46
+ # @example
47
+ # school.highlights # => ["Student Body President"]
48
+ list :highlights
49
+
50
+ # @!method courses
51
+ # Courses
52
+ # @api public
53
+ # @return [Array<String>] courses
54
+ # @example
55
+ # school.courses # => []
56
+ list :courses
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../resource"
4
+
5
+ module Sferik
6
+ class Resume < Resource
7
+ # Where Erik lives, as returned in {Basics#location}
8
+ # @api public
9
+ class Location < Resource
10
+ # @!method city
11
+ # The city
12
+ # @api public
13
+ # @return [String] the city
14
+ # @example
15
+ # location.city # => "San Francisco"
16
+ attribute :city
17
+
18
+ # @!method region
19
+ # The state
20
+ # @api public
21
+ # @return [String] the state
22
+ # @example
23
+ # location.region # => "California"
24
+ attribute :region
25
+
26
+ # @!method country_code
27
+ # The ISO 3166 country code
28
+ # @api public
29
+ # @return [String] the ISO 3166 country code
30
+ # @example
31
+ # location.country_code # => "US"
32
+ attribute :country_code
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../resource"
4
+
5
+ module Sferik
6
+ class Resume < Resource
7
+ # What the resume says about itself, as returned by {Resume#meta}
8
+ # @api public
9
+ class Meta < Resource
10
+ # @!method canonical
11
+ # Where the resume lives, if it says
12
+ # @api public
13
+ # @return [String, nil] where the resume lives, if it says
14
+ # @example
15
+ # meta.canonical # => "https://sferik.net/resume"
16
+ attribute :canonical
17
+
18
+ # @!method version
19
+ # The version of the resume, if it has one
20
+ # @api public
21
+ # @return [String, nil] the version of the resume, if it has one
22
+ # @example
23
+ # meta.version # => "v1.0.0"
24
+ attribute :version
25
+
26
+ # @!method last_modified
27
+ # When the resume last changed, if it says
28
+ # @api public
29
+ # @return [Date, nil] when the resume last changed, if it says
30
+ # @example
31
+ # meta.last_modified # => #<Date: 2026-10-01>
32
+ timestamp :last_modified, PartialDate
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../resource"
4
+
5
+ module Sferik
6
+ class Resume < Resource
7
+ # A patent, as returned in {Resume#patents} (an addition to JSON Resume)
8
+ # @api public
9
+ class Patent < Resource
10
+ # @!method title
11
+ # The title
12
+ # @api public
13
+ # @return [String] the title
14
+ # @example
15
+ # patent.title # => "Method and system for dynamic advertising based on user actions"
16
+ attribute :title
17
+
18
+ # @!method number
19
+ # The publication number
20
+ # @api public
21
+ # @return [String] the publication number
22
+ # @example
23
+ # patent.number # => "US20110153414A1"
24
+ attribute :number
25
+
26
+ # @!method date
27
+ # When it was published
28
+ # @api public
29
+ # @return [Date] when it was published
30
+ # @example
31
+ # patent.date # => #<Date: 2011-06-23>
32
+ timestamp :date, PartialDate
33
+
34
+ # @!method url
35
+ # The patent on Google Patents
36
+ # @api public
37
+ # @return [String] the patent on Google Patents
38
+ # @example
39
+ # patent.url # => "https://patents.google.com/patent/US20110153414A1"
40
+ attribute :url
41
+ end
42
+ end
43
+ end