wikidatum 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5aa9e8eb9fccc24e9c423dcba185ef7550ad465426f90c57128968e06e99d51
4
- data.tar.gz: 19fc905557859d97d1e604155d08d56d416eb332d6f285246c5354bdc1f024a9
3
+ metadata.gz: c8f1ec02cd83bf3ea3e432868a8283474f810bfd55740b5759c820d880f95e31
4
+ data.tar.gz: a5af62f917570a29ceceb6e420377154775d9820dec8abb02eb84e037f0bab89
5
5
  SHA512:
6
- metadata.gz: 6ee9f49ee6689d953f308a9e2f4deaad71226080f70bf6fe3d7f375ac86bbd887e3494d50eed06b96308e6965165c86596fc7dae6abbdf2367e5f4302e49d73a
7
- data.tar.gz: 4147886d03d2f17e2743d0b9309a22e278cbe2e0febb95acc77dbdebeb30319058b1cb33f2b142d0f1f0c84858fa3d59b40039492ff393b89fa19f04e516f54b
6
+ metadata.gz: a6c46eacdb7d7455ee5e96d3cf7c0970bd85ffc3abd1dc09c856b0f92062beaede5d451c9587ab72afc14d3b3c3a4e800c9685b435fbb4074b7d5e0b77cda463
7
+ data.tar.gz: bf4810fc7384b5b5cecf2db956758a1125a1664b62b8f7275d77347c7e0b78d64fbd76d011375914162c7a3a687bb4b679fd2ace7e8addcda2ad84775b88503f
data/CHANGELOG.md CHANGED
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 0.2.0 - 2022-08-13
10
+ ### Added
11
+
12
+ - Add ability to serialize most of an item response from the Beta REST API into a usable `Wikidatum::Item` instance.
13
+ - This enables basic functionality like `client.item(id: 'Q123')` and `client.item(id: 'Q123').statements(properties: ['P123'])`.
14
+ - Add `Client#add_statement` method for creating new statements on an item.
15
+ - Add `Client#delete_statement` method for deleting statements from an item.
16
+ - Add support for reading and writing all statement types: `novalue`, `somevalue`, `string`, `time`, `quantity`, `globecoordinate`, `monolingualtext`, and `wikibase-entityid`.
17
+
18
+ ### Internal
19
+
20
+ - Add YARD docs and auto-deploy them with GitHub Pages.
21
+ - Add comprehensive unit tests for the gem.
22
+
9
23
  ## 0.1.0 - 2022-06-20
10
24
  ### Added
11
25
 
data/Gemfile CHANGED
@@ -7,5 +7,7 @@ gemspec
7
7
 
8
8
  gem 'rake', '~> 13.0'
9
9
  gem 'minitest', '~> 5.16'
10
+ gem 'simplecov', '~> 0.21', require: false
10
11
  gem 'rubocop', '~> 1.30'
11
12
  gem 'yard', '~> 0.9'
13
+ gem 'webmock', '~> 3.17', require: false
data/Gemfile.lock CHANGED
@@ -1,16 +1,28 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- wikidatum (0.1.0)
4
+ wikidatum (0.2.0)
5
+ faraday (~> 2.4)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
10
+ addressable (2.8.0)
11
+ public_suffix (>= 2.0.2, < 5.0)
9
12
  ast (2.4.2)
13
+ crack (0.4.5)
14
+ rexml
15
+ docile (1.4.0)
16
+ faraday (2.4.0)
17
+ faraday-net_http (~> 2.0)
18
+ ruby2_keywords (>= 0.0.4)
19
+ faraday-net_http (2.1.0)
20
+ hashdiff (1.0.1)
10
21
  minitest (5.16.0)
11
22
  parallel (1.22.1)
12
23
  parser (3.1.2.0)
13
24
  ast (~> 2.4.1)
25
+ public_suffix (4.0.7)
14
26
  rainbow (3.1.1)
15
27
  rake (13.0.6)
16
28
  regexp_parser (2.5.0)
@@ -27,18 +39,33 @@ GEM
27
39
  rubocop-ast (1.18.0)
28
40
  parser (>= 3.1.1.0)
29
41
  ruby-progressbar (1.11.0)
42
+ ruby2_keywords (0.0.5)
43
+ simplecov (0.21.2)
44
+ docile (~> 1.1)
45
+ simplecov-html (~> 0.11)
46
+ simplecov_json_formatter (~> 0.1)
47
+ simplecov-html (0.12.3)
48
+ simplecov_json_formatter (0.1.4)
30
49
  unicode-display_width (2.1.0)
50
+ webmock (3.17.1)
51
+ addressable (>= 2.8.0)
52
+ crack (>= 0.3.2)
53
+ hashdiff (>= 0.4.0, < 2.0.0)
31
54
  webrick (1.7.0)
32
55
  yard (0.9.28)
33
56
  webrick (~> 1.7.0)
34
57
 
35
58
  PLATFORMS
59
+ arm64-darwin-21
36
60
  x86_64-darwin-21
61
+ x86_64-linux
37
62
 
38
63
  DEPENDENCIES
39
64
  minitest (~> 5.16)
40
65
  rake (~> 13.0)
41
66
  rubocop (~> 1.30)
67
+ simplecov (~> 0.21)
68
+ webmock (~> 3.17)
42
69
  wikidatum!
43
70
  yard (~> 0.9)
44
71
 
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Wikidatum
2
2
 
3
- This gem will support making requests with the [new Wikidata/Wikibase REST API](https://doc.wikimedia.org/Wikibase/master/js/rest-api/).
3
+ This gem supports making requests to the [new Wikidata/Wikibase REST API](https://doc.wikimedia.org/Wikibase/master/js/rest-api/).
4
4
 
5
- **Currently it is in very early development and is not ready for actual usage**.
5
+ **The gem is currently in very early development and is not ready for production usage**.
6
6
 
7
7
  ## Installation
8
8
 
@@ -22,7 +22,42 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- TODO: Write usage instructions here
25
+ You can view the YARD docs on GitHub Pages [here](https://connorshea.github.io/wikidatum/index.html).
26
+
27
+ Currently, the gem is able to hit a few GET endpoints, and currently has no way to provide authentication and perform POST/PUT/DELETE requests. The additional features will be added later.
28
+
29
+ ```ruby
30
+ require 'wikidatum'
31
+
32
+ wikidatum_client = Wikidatum::Client.new(
33
+ user_agent: 'REPLACE ME WITH THE NAME OF YOUR BOT!',
34
+ # Currently only the beta site has the API available, you'll
35
+ # likely want to use wikidata.org once it's stable.
36
+ wikibase_url: 'https://wikidata.beta.wmflabs.org',
37
+ bot: true
38
+ )
39
+
40
+ # Get an item from the Wikibase instance.
41
+ item = wikidatum_client.item(id: 'Q2') #=> Wikidatum::Item
42
+
43
+ # Get the statements from the item.
44
+ item.statements #=> Array<Wikidatum::Statement>
45
+
46
+ # Get the statments for property P123 on the item.
47
+ item.statements(properties: ['P123']) #=> Array<Wikidatum::Statement>
48
+
49
+ # Get all the labels for the item.
50
+ item.labels #=> Array<Wikidatum::Term>
51
+
52
+ # Get the English label for the item.
53
+ item.label(lang: :en) #=> Wikidatum::Term
54
+
55
+ # Get the actual value for the label.
56
+ item.label(lang: :en).value #=> "Earth"
57
+
58
+ # Get the values for all English aliases on this item.
59
+ item.aliases(langs: [:en]).map(&:value) #=> ["Planet Earth", "Pale Blue Dot"]
60
+ ```
26
61
 
27
62
  ## Development
28
63
 
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require "rake/testtask"
6
6
  Rake::TestTask.new(:test) do |t|
7
7
  t.libs << "test"
8
8
  t.libs << "lib"
9
- t.test_files = FileList["test/**/test_*.rb"]
9
+ t.test_files = FileList["test/**/*_spec.rb"]
10
10
  end
11
11
 
12
12
  require "rubocop/rake_task"
@@ -1,22 +1,370 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'faraday'
4
+ require 'faraday/net_http'
5
+
3
6
  class Wikidatum::Client
4
- # @return [String] The root URL of the Wikibase instance we want to interact
5
- # with. If not provided, will default to Wikidata.
7
+ ITEM_REGEX = /^Q?\d+$/.freeze
8
+ STATEMENT_REGEX = /^Q?\d+\$[\w-]+$/.freeze
9
+
10
+ # @return [String] the root URL of the Wikibase instance we want to interact
11
+ # with. If not provided, will default to Wikidata.
6
12
  attr_reader :wikibase_url
7
13
 
8
- # @return [Boolean] Whether this client instance should identify itself
9
- # as a bot when making requests.
14
+ # @return [Boolean] whether this client instance should identify itself
15
+ # as a bot when making requests.
10
16
  attr_reader :bot
11
17
 
18
+ # @return [String] the UserAgent header to send with all requests to the
19
+ # Wikibase API.
20
+ attr_reader :user_agent
21
+
22
+ # Create a new Wikidatum::Client to interact with the Wikibase REST API.
23
+ #
24
+ # @example
25
+ # wikidatum_client = Wikidatum::Client.new(
26
+ # user_agent: 'REPLACE ME WITH THE NAME OF YOUR BOT!',
27
+ # wikibase_url: 'https://www.wikidata.org',
28
+ # bot: true
29
+ # )
30
+ #
31
+ # @param user_agent [String] The UserAgent header to send with all requests
32
+ # to the Wikibase API.
12
33
  # @param wikibase_url [String] The root URL of the Wikibase instance we want
13
- # to interact with. If not provided, will
14
- # default to Wikidata.
34
+ # to interact with. If not provided, will default to
35
+ # `https://www.wikidata.org`. Do not include a `/` at the end of the URL.
15
36
  # @param bot [Boolean] Whether requests sent by this client instance should
16
- # be registered as bot requests.
37
+ # be registered as bot requests. Defaults to `true`.
17
38
  # @return [Wikidatum::Client]
18
- def initialize(wikibase_url: 'https://www.wikidata.org', bot: true)
39
+ def initialize(user_agent:, wikibase_url: 'https://www.wikidata.org', bot: true)
40
+ raise ArgumentError, "Wikibase URL must not end with a `/`, got #{wikibase_url.inspect}." if wikibase_url.end_with?('/')
41
+
42
+ # TODO: Add the Ruby gem version to the UserAgent automatically, and
43
+ # restrict the ability for end-users to actually set the UserAgent?
44
+ @user_agent = user_agent
19
45
  @wikibase_url = wikibase_url
20
46
  @bot = bot
47
+
48
+ Faraday.default_adapter = :net_http
49
+ end
50
+
51
+ # Get an item from the Wikibase API based on its QID.
52
+ #
53
+ # @example
54
+ # wikidatum_client.item(id: 'Q123')
55
+ # wikidatum_client.item(id: 123)
56
+ # wikidatum_client.item(id: '123')
57
+ #
58
+ # @param id [String, Integer] Either a string or integer representation of
59
+ # the item's QID, e.g. `"Q123"`, `"123"`, or `123`.
60
+ # @return [Wikidatum::Item]
61
+ def item(id:)
62
+ raise ArgumentError, "#{id.inspect} is an invalid Wikibase QID. Must be an integer, a string representation of an integer, or in the format 'Q123'." unless id.is_a?(Integer) || id.match?(ITEM_REGEX)
63
+
64
+ id = coerce_item_id(id)
65
+
66
+ response = get_request("/entities/items/#{id}")
67
+
68
+ puts JSON.pretty_generate(response) if ENV['DEBUG']
69
+
70
+ Wikidatum::Item.marshal_load(response)
71
+ end
72
+
73
+ # Get a statement from the Wikibase API based on its ID.
74
+ #
75
+ # @example
76
+ # wikidatum_client.statement(id: 'Q123$f004ec2b-4857-3b69-b370-e8124f5bd3ac')
77
+ #
78
+ # @param id [String] A string representation of the statement's ID.
79
+ # @return [Wikidatum::Statement]
80
+ def statement(id:)
81
+ raise ArgumentError, "#{id.inspect} is an invalid Wikibase Statement ID. Must be a string in the format 'Q123$f004ec2b-4857-3b69-b370-e8124f5bd3ac'." unless id.match?(STATEMENT_REGEX)
82
+
83
+ response = get_request("/statements/#{id}")
84
+
85
+ puts JSON.pretty_generate(response) if ENV['DEBUG']
86
+
87
+ Wikidatum::Statement.marshal_load(response)
88
+ end
89
+
90
+ # Add a statement to an item.
91
+ #
92
+ # NOTE: Adding references/qualifiers with `add_statement` is untested and
93
+ # effectively unsupported for now.
94
+ #
95
+ # @example Add a string statement.
96
+ # wikidatum_client.add_statement(
97
+ # id: 'Q123',
98
+ # property: 'P23',
99
+ # datavalue: Wikidatum::DataValueType::WikibaseString.new(string: 'Foo'),
100
+ # comment: 'Adding something or another.'
101
+ # )
102
+ #
103
+ # @example Add a 'no value' statement.
104
+ # wikidatum_client.add_statement(
105
+ # id: 'Q123',
106
+ # property: 'P124',
107
+ # datavalue: Wikidatum::DataValueType::NoValue.new(
108
+ # type: :no_value,
109
+ # value: nil
110
+ # )
111
+ # )
112
+ #
113
+ # @example Add an 'unknown value' statement.
114
+ # wikidatum_client.add_statement(
115
+ # id: 'Q123',
116
+ # property: 'P124',
117
+ # datavalue: Wikidatum::DataValueType::SomeValue.new(
118
+ # type: :some_value,
119
+ # value: nil
120
+ # )
121
+ # )
122
+ #
123
+ # @example Add a globe coordinate statement.
124
+ # wikidatum_client.add_statement(
125
+ # id: 'Q123',
126
+ # property: 'P124',
127
+ # datavalue: Wikidatum::DataValueType::GlobeCoordinate.new(
128
+ # latitude: 52.51666,
129
+ # longitude: 13.3833,
130
+ # precision: 0.01666,
131
+ # globe: 'https://wikidata.org/entity/Q2'
132
+ # )
133
+ # )
134
+ #
135
+ # @example Add a monolingual text statement.
136
+ # wikidatum_client.add_statement(
137
+ # id: 'Q123',
138
+ # property: 'P124',
139
+ # datavalue: Wikidatum::DataValueType::MonolingualText.new(
140
+ # language: 'en',
141
+ # text: 'Foobar'
142
+ # )
143
+ # )
144
+ #
145
+ # @example Add a quantity statement.
146
+ # wikidatum_client.add_statement(
147
+ # id: 'Q123',
148
+ # property: 'P124',
149
+ # datavalue: Wikidatum::DataValueType::Quantity.new(
150
+ # amount: '+12',
151
+ # upper_bound: nil,
152
+ # lower_bound: nil,
153
+ # unit: 'https://wikidata.org/entity/Q1234'
154
+ # )
155
+ # )
156
+ #
157
+ # @example Add a time statement.
158
+ # wikidatum_client.add_statement(
159
+ # id: 'Q123',
160
+ # property: 'P124',
161
+ # datavalue: Wikidatum::DataValueType::Time.new(
162
+ # time: '+2022-08-12T00:00:00Z',
163
+ # time_zone: 0,
164
+ # precision: 11,
165
+ # calendar_model: 'https://wikidata.org/entity/Q1234'
166
+ # )
167
+ # )
168
+ #
169
+ # @example Add a Wikibase item statement.
170
+ # wikidatum_client.add_statement(
171
+ # id: 'Q123',
172
+ # property: 'P124',
173
+ # datavalue: Wikidatum::DataValueType::WikibaseEntityId.new(
174
+ # entity_type: 'item',
175
+ # numeric_id: 1234,
176
+ # id: 'Q1234'
177
+ # )
178
+ # )
179
+ #
180
+ # @param id [String] the ID of the item on which the statement will be added.
181
+ # @param property [String] property ID in the format 'P123'.
182
+ # @param datavalue [Wikidatum::DataValueType::GlobeCoordinate, Wikidatum::DataValueType::MonolingualText, Wikidatum::DataValueType::Quantity, Wikidatum::DataValueType::WikibaseString, Wikidatum::DataValueType::Time, Wikidatum::DataValueType::WikibaseEntityId, Wikidatum::DataValueType::NoValue, Wikidatum::DataValueType::SomeValue] the datavalue of the statement being created.
183
+ # @param datatype [String, nil] if nil, it'll determine the type based on what was passed for the statement argument. This may differ from the type of the Statement's datavalue (for example with the 'url' type).
184
+ # @param qualifiers [Hash<String, Array<Wikidatum::Snak>>]
185
+ # @param references [Array<Wikidatum::Reference>]
186
+ # @param rank [String]
187
+ # @param tags [Array<String>]
188
+ # @param comment [String, nil]
189
+ # @return [Boolean] True if the request succeeded.
190
+ def add_statement(id:, property:, datavalue:, datatype: nil, qualifiers: {}, references: [], rank: 'normal', tags: [], comment: nil)
191
+ raise ArgumentError, "#{id.inspect} is an invalid Wikibase QID. Must be an integer, a string representation of an integer, or in the format 'Q123'." unless id.is_a?(Integer) || id.match?(ITEM_REGEX)
192
+
193
+ id = coerce_item_id(id)
194
+
195
+ # Unless datatype is set explicitly by the caller, just assume we can pull the
196
+ # default from the datavalue class.
197
+ datatype ||= datavalue.wikibase_datatype
198
+
199
+ case datavalue.class.to_s
200
+ when 'Wikidatum::DataValueType::NoValue'
201
+ statement_hash = {
202
+ mainsnak: {
203
+ snaktype: 'novalue',
204
+ property: property,
205
+ datatype: datatype
206
+ }
207
+ }
208
+ when 'Wikidatum::DataValueType::SomeValue'
209
+ statement_hash = {
210
+ mainsnak: {
211
+ snaktype: 'somevalue',
212
+ property: property,
213
+ datatype: datatype
214
+ }
215
+ }
216
+ when 'Wikidatum::DataValueType::GlobeCoordinate', 'Wikidatum::DataValueType::MonolingualText', 'Wikidatum::DataValueType::Quantity', 'Wikidatum::DataValueType::WikibaseString', 'Wikidatum::DataValueType::Time', 'Wikidatum::DataValueType::WikibaseEntityId'
217
+ statement_hash = {
218
+ mainsnak: {
219
+ snaktype: 'value',
220
+ property: property,
221
+ datatype: datatype,
222
+ datavalue: {
223
+ type: datavalue.wikibase_type,
224
+ value: datavalue.marshal_dump
225
+ }
226
+ }
227
+ }
228
+ else
229
+ raise ArgumentError, "Expected an instance of one of Wikidatum::DataValueType's subclasses for datavalue, but got #{datavalue.inspect}."
230
+ end
231
+
232
+ body = { statement: statement_hash.merge({ qualifiers: qualifiers, references: references, rank: rank, type: "statement" }) }
233
+
234
+ response = post_request("/entities/items/#{id}/statements", body, tags: tags, comment: comment)
235
+
236
+ puts JSON.pretty_generate(response) if ENV['DEBUG']
237
+
238
+ response.success?
239
+ end
240
+
241
+ # Delete a statement from an item.
242
+ #
243
+ # @example
244
+ # wikidatum_client.delete_statement(
245
+ # id: 'Q123$4543523c-1d1d-1111-1e1e-11b11111b1f1',
246
+ # comment: "Deleting this statement because it's bad."
247
+ # )
248
+ #
249
+ # @param id [String] the ID of the statemnt being deleted.
250
+ # @param tags [Array<String>]
251
+ # @param comment [String, nil]
252
+ # @return [Boolean] True if the request succeeded.
253
+ def delete_statement(id:, tags: [], comment: nil)
254
+ raise ArgumentError, "#{id.inspect} is an invalid Wikibase Statement ID. Must be a string in the format 'Q123$f004ec2b-4857-3b69-b370-e8124f5bd3ac'." unless id.match?(STATEMENT_REGEX)
255
+
256
+ response = delete_request("/statements/#{id}", tags: tags, comment: comment)
257
+
258
+ puts JSON.pretty_generate(response) if ENV['DEBUG']
259
+
260
+ response.success?
261
+ end
262
+
263
+ private
264
+
265
+ # For now this just returns the `@wikibase_url`, but in the future the API
266
+ # routes will presumably be nested further, so this is just future-proofing
267
+ # to allow that to be easily changed later.
268
+ #
269
+ # @return [String] URL for the Wikibase API endpoint.
270
+ def api_url
271
+ @api_url ||= "#{@wikibase_url}/w/rest.php/wikibase/v0"
272
+ end
273
+
274
+ # Default headers to be sent with every request.
275
+ #
276
+ # @return [Hash] A hash of some headers that should be used when sending a request.
277
+ def universal_headers
278
+ @universal_headers ||= {
279
+ 'User-Agent' => @user_agent,
280
+ 'Content-Type' => 'application/json'
281
+ }
282
+ end
283
+
284
+ # Make a GET request to a given Wikibase endpoint.
285
+ #
286
+ # @param path [String] The relative path for the API endpoint.
287
+ # @param params [Hash] Query parameters to send with the request, if any.
288
+ # @return [Hash] JSON response, parsed into a hash.
289
+ def get_request(path, params = nil)
290
+ url = "#{api_url}#{path}"
291
+
292
+ response = Faraday.get(url, params, universal_headers)
293
+
294
+ # Error handling if it doesn't return a 200
295
+ unless response.success?
296
+ puts 'Something went wrong with this request!'
297
+ puts "Status Code: #{response.status}"
298
+ puts response.body.inspect
299
+ end
300
+
301
+ JSON.parse(response.body)
302
+ end
303
+
304
+ # Make a POST request to a given Wikibase endpoint.
305
+ #
306
+ # @param path [String] The relative path for the API endpoint.
307
+ # @param body [Hash] The body to post to the endpoint.
308
+ # @param tags [Array<String>] The tags to apply to the edit being made by this request, for PUT/POST/DELETE requests.
309
+ # @param comment [String] The edit description, for PUT/POST/DELETE requests.
310
+ # @return [Hash] JSON response, parsed into a hash.
311
+ def post_request(path, body = {}, tags: nil, comment: nil)
312
+ url = "#{api_url}#{path}"
313
+
314
+ body[:bot] = @bot
315
+ body[:tags] = tags unless tags.empty?
316
+ body[:comment] = comment unless comment.nil?
317
+
318
+ response = Faraday.post(url) do |req|
319
+ req.body = JSON.generate(body)
320
+ req.headers = universal_headers
321
+ end
322
+
323
+ puts response.body.inspect if ENV['DEBUG']
324
+
325
+ response
326
+ end
327
+
328
+ # Make a DELETE request to a given Wikibase endpoint.
329
+ #
330
+ # @param path [String] The relative path for the API endpoint.
331
+ # @param tags [Array<String>] The tags to apply to the edit being made by this request, for PUT/POST/DELETE requests.
332
+ # @param comment [String] The edit description, for PUT/POST/DELETE requests.
333
+ # @return [Hash] JSON response, parsed into a hash.
334
+ def delete_request(path, tags: [], comment: nil)
335
+ url = "#{api_url}#{path}"
336
+
337
+ body = {}
338
+ body[:bot] = @bot
339
+ body[:tags] = tags unless tags.empty?
340
+ body[:comment] = comment unless comment.nil?
341
+
342
+ response = Faraday.delete(url) do |req|
343
+ req.body = JSON.generate(body)
344
+ req.headers = universal_headers
345
+ end
346
+
347
+ puts response.body.inspect if ENV['DEBUG']
348
+
349
+ # Error handling if it doesn't return a 200
350
+ unless response.success?
351
+ puts 'Something went wrong with this request!'
352
+ puts "Status Code: #{response.status}"
353
+ puts response.body.inspect
354
+ end
355
+
356
+ response
357
+ end
358
+
359
+ # Coerce an Item ID in the formats 'Q123', '123' or 123 into a consistent
360
+ # 'Q123' format. We need to have the ID in the format 'Q123' for the API
361
+ # request, which is why coercion is necessary.
362
+ #
363
+ # @param id [String, Integer]
364
+ # @return [String]
365
+ def coerce_item_id(id)
366
+ return id if id.to_s.start_with?('Q')
367
+
368
+ "Q#{id}"
21
369
  end
22
370
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # For more information on the possible types that can be returned by
4
+ # datavalues, see the official documentation:
5
+ # https://doc.wikimedia.org/Wikibase/master/php/md_docs_topics_json.html#json_datavalues
6
+ module Wikidatum::DataValueType
7
+ class Base
8
+ # Represents the type for this datavalue instance.
9
+ #
10
+ # Possible values for the `type` attribute are:
11
+ #
12
+ # - `:no_value`: No value
13
+ # - `:some_value`: Unknown value
14
+ # - `:globe_coordinate`: {DataValueType::GlobeCoordinate}
15
+ # - `:monolingual_text`: {DataValueType::MonolingualText}
16
+ # - `:quantity`: {DataValueType::Quantity}
17
+ # - `:string`: {DataValueType::WikibaseString}
18
+ # - `:time`: {DataValueType::Time}
19
+ # - `:wikibase_entity_id`: {DataValueType::WikibaseEntityId}
20
+ #
21
+ # @return [Symbol]
22
+ attr_reader :type
23
+
24
+ # The value of the datavalue object in the response.
25
+ #
26
+ # If the `type` is `novalue` or `somevalue`, this returns `nil`.
27
+ #
28
+ # @return [DataValueType::GlobeCoordinate, DataValueType::MonolingualText, DataValueType::Quantity, DataValueType::WikibaseString, DataValueType::Time, DataValueType::WikibaseEntityId, nil]
29
+ attr_reader :value
30
+
31
+ # @param type [Symbol]
32
+ # @param value [DataValueType::GlobeCoordinate, DataValueType::MonolingualText, DataValueType::Quantity, DataValueType::WikibaseString, DataValueType::Time, DataValueType::WikibaseEntityId, nil] nil if type is no_value or some_value
33
+ # @return [void]
34
+ def initialize(type:, value:)
35
+ @type = type
36
+ @value = value
37
+ end
38
+
39
+ # @return [Hash]
40
+ def to_h
41
+ {
42
+ type: @type,
43
+ value: @value&.to_h
44
+ }
45
+ end
46
+
47
+ # @!visibility private
48
+ #
49
+ # @param data_value_type [String] The value of `type` for the given Snak's datavalue.
50
+ # @param data_value_json [Hash] The `value` part of datavalue.
51
+ # @return [Wikidatum::DataValueType::Base] An instance of Base.
52
+ def self.marshal_load(data_value_type, data_value_json)
53
+ unless Wikidatum::DataValueType::DATA_VALUE_TYPES.keys.include?(data_value_type.to_sym)
54
+ puts 'WARNING: Unsupported datavalue type.'
55
+ return nil
56
+ end
57
+
58
+ Object.const_get(Wikidatum::DataValueType::DATA_VALUE_TYPES[data_value_type.to_sym]).marshal_load(data_value_json)
59
+ end
60
+ end
61
+ end