wikidata_position_history 1.3.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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.reek.yml +14 -0
- data/.rubocop.yml +54 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +71 -0
- data/Rakefile +24 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/position-history-for-item +11 -0
- data/exe/update_wikidata_page +26 -0
- data/lib/query_service.rb +103 -0
- data/lib/sparql/item_query.rb +28 -0
- data/lib/sparql/mandates.rb +88 -0
- data/lib/sparql/position_data.rb +62 -0
- data/lib/wikidata_position_history.rb +87 -0
- data/lib/wikidata_position_history/checks.rb +157 -0
- data/lib/wikidata_position_history/report.rb +140 -0
- data/lib/wikidata_position_history/version.rb +5 -0
- data/wikidata_position_history.gemspec +38 -0
- metadata +211 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 43e80159fad0253e687e1dc8d52318079bdc2bf5
|
|
4
|
+
data.tar.gz: 26974422ae9f8584c6907d8531b63eeccee42231
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c1a7fb0eb3c293884452995d44dd7371ace248f48f676c15e64f87e1ed8604eedf0a8df53599186a5c978f8b93cafcd0d0f1282c8592296288d3ef5d7bfbf22f
|
|
7
|
+
data.tar.gz: 3dc935ea7ad2f67e9c1c8a4ca520a8a129fd05f2b78cb27d22f81e397fff81fcb311f543aa75949d824f5e565c25fdcce18bf478b9a7e3846110f4264036b1d2
|
data/.gitignore
ADDED
data/.reek.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
exclude_paths:
|
|
3
|
+
- test
|
|
4
|
+
|
|
5
|
+
detectors:
|
|
6
|
+
DuplicateMethodCall:
|
|
7
|
+
exclude:
|
|
8
|
+
- QueryService::WikidataDate#<=>
|
|
9
|
+
MissingSafeMethod:
|
|
10
|
+
exclude:
|
|
11
|
+
- WikidataPositionHistory::PageRewriter
|
|
12
|
+
UncommunicativeVariableName:
|
|
13
|
+
exclude:
|
|
14
|
+
- QueryService::Query#results
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
Exclude:
|
|
3
|
+
- 'Vagrantfile'
|
|
4
|
+
- 'vendor/**/*'
|
|
5
|
+
TargetRubyVersion: 2.4
|
|
6
|
+
NewCops: enable
|
|
7
|
+
|
|
8
|
+
Metrics/AbcSize:
|
|
9
|
+
Exclude:
|
|
10
|
+
- 'test/test_helper.rb'
|
|
11
|
+
|
|
12
|
+
Metrics/BlockLength:
|
|
13
|
+
Exclude:
|
|
14
|
+
- '**/*.gemspec'
|
|
15
|
+
- 'test/**/*.rb'
|
|
16
|
+
|
|
17
|
+
Layout/LineLength:
|
|
18
|
+
Max: 150
|
|
19
|
+
Exclude:
|
|
20
|
+
- 'test/**/*.rb'
|
|
21
|
+
|
|
22
|
+
Layout/HashAlignment:
|
|
23
|
+
EnforcedHashRocketStyle: table
|
|
24
|
+
EnforcedColonStyle: table
|
|
25
|
+
|
|
26
|
+
Lint/AssignmentInCondition:
|
|
27
|
+
Enabled: false
|
|
28
|
+
|
|
29
|
+
Naming/ClassAndModuleCamelCase:
|
|
30
|
+
Enabled: false
|
|
31
|
+
|
|
32
|
+
Style/CollectionMethods:
|
|
33
|
+
Enabled: true
|
|
34
|
+
|
|
35
|
+
Style/Documentation:
|
|
36
|
+
Enabled: false
|
|
37
|
+
|
|
38
|
+
Style/FormatStringToken:
|
|
39
|
+
Enabled: false
|
|
40
|
+
|
|
41
|
+
Style/HashSyntax:
|
|
42
|
+
EnforcedStyle: ruby19_no_mixed_keys
|
|
43
|
+
|
|
44
|
+
Style/RescueModifier:
|
|
45
|
+
Enabled: false
|
|
46
|
+
|
|
47
|
+
Style/SymbolArray:
|
|
48
|
+
Enabled: true
|
|
49
|
+
|
|
50
|
+
Style/TrailingCommaInHashLiteral:
|
|
51
|
+
EnforcedStyleForMultiline: consistent_comma
|
|
52
|
+
|
|
53
|
+
Style/TrailingCommaInArrayLiteral:
|
|
54
|
+
EnforcedStyleForMultiline: no_comma
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
# [1.3.2] - 2020-08-29
|
|
4
|
+
|
|
5
|
+
## Fixes
|
|
6
|
+
|
|
7
|
+
* Skip all date checks for items with no dates
|
|
8
|
+
|
|
9
|
+
# [1.3.1] - 2020-08-28
|
|
10
|
+
|
|
11
|
+
## Fixes
|
|
12
|
+
|
|
13
|
+
* Skip the dates line if neither date is set
|
|
14
|
+
|
|
15
|
+
# [1.3.0] - 2020-08-17
|
|
16
|
+
|
|
17
|
+
## Enhancements
|
|
18
|
+
|
|
19
|
+
* Display dates at more accurate levels of precision
|
|
20
|
+
|
|
21
|
+
# [1.2.0] - 2020-08-14
|
|
22
|
+
|
|
23
|
+
## Enhancements
|
|
24
|
+
|
|
25
|
+
* Display inception and/or abolition dates for the position itself.
|
|
26
|
+
|
|
27
|
+
## Fixes
|
|
28
|
+
|
|
29
|
+
* If an officeholder held multiple consecutive terms, do not warn that
|
|
30
|
+
they do not have 'replaces' or 'replaced by' statements pointing at
|
|
31
|
+
themselves
|
|
32
|
+
|
|
33
|
+
# [1.1.0] - 2020-08-14
|
|
34
|
+
|
|
35
|
+
## Enhancements
|
|
36
|
+
|
|
37
|
+
* 'Acting' officeholders are visually differtiated in the output, and do
|
|
38
|
+
not require replaces/replaced-by statements
|
|
39
|
+
* If no officeholders are found, explicitly say so, rather than
|
|
40
|
+
displaying a completely empty table
|
|
41
|
+
* Position ID will be derived from Page name if not supplied
|
|
42
|
+
|
|
43
|
+
## Fixes
|
|
44
|
+
|
|
45
|
+
* Deprecated P39 (position held) statements are no longer included
|
|
46
|
+
|
|
47
|
+
# [1.0.0] - 2017-11-03
|
|
48
|
+
|
|
49
|
+
Original release by mySociety
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Tony Bowden, 2017 mySociety
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# WikidataPositionHistory
|
|
2
|
+
|
|
3
|
+
Rewrites Mediawiki pages that include a `PositionHolderHistory`
|
|
4
|
+
template, to show a timeline of people who have held a particular
|
|
5
|
+
office, along with helpful diagnostic warnings for common errors.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'wikidata_position_history', github: 'tmtmtmtm/wikidata-position-history'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
$ bundle
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
WikidataPositionHistory::PageRewriter.new(
|
|
23
|
+
mediawiki_site: 'www.wikidata.org',
|
|
24
|
+
page_title: 'User:Mhl20/Prime_minister_test'
|
|
25
|
+
).run!
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This looks for a Template call in that page of the form:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
{{PositionHolderHistory|id=Q14211}}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If such a template is found, a table is inserted after it listing all
|
|
35
|
+
people who have held (i.e. have a relevant P39 "position held"
|
|
36
|
+
statement) position Q14211.
|
|
37
|
+
|
|
38
|
+
A sentinel HTML comment is also inserted, so that on subsequent runs
|
|
39
|
+
only the text between the template and that comment are rewritten.
|
|
40
|
+
|
|
41
|
+
## Development
|
|
42
|
+
|
|
43
|
+
After checking out the repo, run `bin/setup` to install
|
|
44
|
+
dependencies. Then, run `rake test` to run the tests. You can
|
|
45
|
+
also run `bin/console` for an interactive prompt that will allow
|
|
46
|
+
you to experiment.
|
|
47
|
+
|
|
48
|
+
To install this gem onto your local machine, run `bundle exec
|
|
49
|
+
rake install`. To release a new version, update the version
|
|
50
|
+
number in `version.rb`, and then run `bundle exec rake release`,
|
|
51
|
+
which will create a git tag for the version, push git commits
|
|
52
|
+
and tags, and push the `.gem` file to
|
|
53
|
+
[rubygems.org](https://rubygems.org).
|
|
54
|
+
|
|
55
|
+
## Contributing
|
|
56
|
+
|
|
57
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
58
|
+
https://github.com/tmtmtmtm/wikidata-position-history
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
The gem is available as open source under the terms of the
|
|
63
|
+
[MIT License](http://opensource.org/licenses/MIT).
|
|
64
|
+
|
|
65
|
+
## History
|
|
66
|
+
|
|
67
|
+
This was originally developed by Tony Bowden and Mark Longair at
|
|
68
|
+
mySociety as part of a [Wikimedia Foundation grant-funded
|
|
69
|
+
project](https://meta.wikimedia.org/wiki/Grants:Project/mySociety/EveryPolitician).
|
|
70
|
+
|
|
71
|
+
This version is now maintained independently by Tony Bowden.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rake/testtask'
|
|
5
|
+
require 'reek/rake/task'
|
|
6
|
+
require 'rubocop/rake_task'
|
|
7
|
+
|
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
|
9
|
+
t.libs << 'test'
|
|
10
|
+
t.libs << 'lib'
|
|
11
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
desc 'Run rubocop'
|
|
15
|
+
task :rubocop do
|
|
16
|
+
RuboCop::RakeTask.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc 'Run reek'
|
|
20
|
+
Reek::Rake::Task.new do |t|
|
|
21
|
+
t.fail_on_error = true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
task default: %i[test rubocop reek]
|
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'wikidata_position_history'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require 'irb'
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'wikidata_position_history'
|
|
5
|
+
|
|
6
|
+
if ARGV.size != 1
|
|
7
|
+
abort "Usage: #{$PROGRAM_NAME} ITEM_ID
|
|
8
|
+
e.g. #{$PROGRAM_NAME} Q14211'"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
puts WikidataPositionHistory::Report.new(ARGV.first).wikitext_with_header
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'wikidata_position_history'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
if ARGV.size != 1
|
|
8
|
+
abort "Usage: #{$PROGRAM_NAME} REFRESH_URL_OR_QUERYSTRING
|
|
9
|
+
e.g. wikidata_position_history 'mediawiki_site=www.wikidata.org&page_title=User%3AMhl20%2FPrime_minister_test'
|
|
10
|
+
or wikidata_position_history https://www.wikidata.org/wiki/User:Mhl20/Prime_minister_test"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if ARGV.first.start_with?('http')
|
|
14
|
+
uri = URI.parse ARGV.first
|
|
15
|
+
options = {
|
|
16
|
+
mediawiki_site: uri.hostname,
|
|
17
|
+
page_title: uri.path.gsub('/wiki/', ''), # with 2.5.1 we could use delete_prefix
|
|
18
|
+
}
|
|
19
|
+
else
|
|
20
|
+
options = URI.decode_www_form(ARGV.first).map { |k, v| [k.to_sym, v] }.to_h
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
warn "Running with options: #{options.inspect}" if ENV.key?('DEBUG')
|
|
24
|
+
|
|
25
|
+
rewriter = WikidataPositionHistory::PageRewriter.new(**options)
|
|
26
|
+
rewriter.run!
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'rest-client'
|
|
5
|
+
|
|
6
|
+
module QueryService
|
|
7
|
+
# A SPARQL query against the Wikidata Query Service
|
|
8
|
+
class Query
|
|
9
|
+
WIKIDATA_SPARQL_URL = 'https://query.wikidata.org/sparql'
|
|
10
|
+
|
|
11
|
+
def initialize(query)
|
|
12
|
+
@query = query
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def results
|
|
16
|
+
json
|
|
17
|
+
rescue RestClient::Exception => e
|
|
18
|
+
raise "Wikidata query #{query} failed: #{e.message}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :query
|
|
24
|
+
|
|
25
|
+
def result
|
|
26
|
+
@result ||= RestClient.get WIKIDATA_SPARQL_URL, accept: 'application/sparql-results+json', params: { query: query }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def json
|
|
30
|
+
JSON.parse(result, symbolize_names: true)[:results][:bindings]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# different views of a Wikidata item
|
|
35
|
+
class WikidataItem
|
|
36
|
+
def initialize(url)
|
|
37
|
+
@url = url
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def id
|
|
41
|
+
url.split('/').last unless url.to_s.empty?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def qlink
|
|
45
|
+
"{{Q|#{id}}}" if id
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
attr_reader :url
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# a Wikidata date of a given precision
|
|
54
|
+
class WikidataDate
|
|
55
|
+
include Comparable
|
|
56
|
+
|
|
57
|
+
def initialize(str, precision)
|
|
58
|
+
@str = str
|
|
59
|
+
@raw_precision = precision.to_s
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def <=>(other)
|
|
63
|
+
return to_s <=> other.to_s if precision == other.precision
|
|
64
|
+
return year <=> other.year if year != other.year
|
|
65
|
+
return month <=> other.month if month && other.month
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_s
|
|
69
|
+
return str if precision == '11'
|
|
70
|
+
return str[0..6] if precision == '10'
|
|
71
|
+
return str[0..3] if precision == '9'
|
|
72
|
+
|
|
73
|
+
warn "Cannot handle precision #{precision} for #{str}"
|
|
74
|
+
str
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def empty?
|
|
78
|
+
str.to_s.empty?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def precision
|
|
82
|
+
return '11' if raw_precision.empty? # default to YYYY-MM-DD
|
|
83
|
+
|
|
84
|
+
raw_precision
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def year
|
|
88
|
+
parts[0]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def month
|
|
92
|
+
parts[1]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
attr_reader :str, :raw_precision
|
|
98
|
+
|
|
99
|
+
def parts
|
|
100
|
+
to_s.split('-')
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WikidataPositionHistory
|
|
4
|
+
module SPARQL
|
|
5
|
+
# Turn raw SPARQL into result objects
|
|
6
|
+
class ItemQuery
|
|
7
|
+
def initialize(itemid)
|
|
8
|
+
@itemid = itemid
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def results_as(klass)
|
|
12
|
+
json.map { |result| klass.new(result) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :itemid
|
|
18
|
+
|
|
19
|
+
def sparql
|
|
20
|
+
raw_sparql % itemid
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def json
|
|
24
|
+
@json ||= QueryService::Query.new(sparql).results
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WikidataPositionHistory
|
|
4
|
+
module SPARQL
|
|
5
|
+
# SPARQL for fetching all officeholdings of a position
|
|
6
|
+
class Mandates < ItemQuery
|
|
7
|
+
def raw_sparql
|
|
8
|
+
<<~SPARQL
|
|
9
|
+
# position-mandates
|
|
10
|
+
SELECT DISTINCT ?ordinal ?item ?start_date ?start_precision ?end_date ?end_precision ?prev ?next ?nature
|
|
11
|
+
WHERE {
|
|
12
|
+
?item wdt:P31 wd:Q5 ; p:P39 ?posn .
|
|
13
|
+
?posn ps:P39 wd:%s .
|
|
14
|
+
FILTER NOT EXISTS { ?posn wikibase:rank wikibase:DeprecatedRank }
|
|
15
|
+
|
|
16
|
+
OPTIONAL { ?posn pqv:P580 [ wikibase:timeValue ?start_date; wikibase:timePrecision ?start_precision ] }
|
|
17
|
+
OPTIONAL { ?posn pqv:P582 [ wikibase:timeValue ?end_date; wikibase:timePrecision ?end_precision ] }
|
|
18
|
+
OPTIONAL { ?posn pq:P1365|pq:P155 ?prev }
|
|
19
|
+
OPTIONAL { ?posn pq:P1366|pq:P156 ?next }
|
|
20
|
+
OPTIONAL { ?posn pq:P1545 ?ordinal }
|
|
21
|
+
OPTIONAL { ?posn pq:P5102 ?nature }
|
|
22
|
+
OPTIONAL { ?posn pq:P5102 ?nature }
|
|
23
|
+
}
|
|
24
|
+
ORDER BY DESC(?start_date)
|
|
25
|
+
SPARQL
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Represents a single row returned from the Mandates query
|
|
31
|
+
class Mandate
|
|
32
|
+
def initialize(row)
|
|
33
|
+
@row = row
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ordinal
|
|
37
|
+
row.dig(:ordinal, :value)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def item
|
|
41
|
+
QueryService::WikidataItem.new(row.dig(:item, :value)).qlink
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def prev
|
|
45
|
+
QueryService::WikidataItem.new(row.dig(:prev, :value)).qlink
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def next
|
|
49
|
+
QueryService::WikidataItem.new(row.dig(:next, :value)).qlink
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def nature
|
|
53
|
+
QueryService::WikidataItem.new(row.dig(:nature, :value)).id
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def acting?
|
|
57
|
+
nature == 'Q4676846'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def start_date
|
|
61
|
+
QueryService::WikidataDate.new(start_date_raw, start_date_precision)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def end_date
|
|
65
|
+
QueryService::WikidataDate.new(end_date_raw, end_date_precision)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def start_date_raw
|
|
69
|
+
row.dig(:start_date, :value).to_s[0..9]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def end_date_raw
|
|
73
|
+
row.dig(:end_date, :value).to_s[0..9]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def start_date_precision
|
|
77
|
+
row.dig(:start_precision, :value)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def end_date_precision
|
|
81
|
+
row.dig(:end_precision, :value)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
attr_reader :row
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WikidataPositionHistory
|
|
4
|
+
module SPARQL
|
|
5
|
+
# SPARQL for fetching metadata about a position
|
|
6
|
+
class PositionData < ItemQuery
|
|
7
|
+
def raw_sparql
|
|
8
|
+
<<~SPARQL
|
|
9
|
+
# position-metadata
|
|
10
|
+
|
|
11
|
+
SELECT DISTINCT ?inception ?inception_precision ?abolition ?abolition_precision ?isPosition
|
|
12
|
+
WHERE {
|
|
13
|
+
VALUES ?item { wd:%s }
|
|
14
|
+
BIND(EXISTS { ?item wdt:P279+ wd:Q4164871 } as ?isPosition)
|
|
15
|
+
OPTIONAL { ?item p:P571/psv:P571 [ wikibase:timeValue ?inception; wikibase:timePrecision ?inception_precision ] }
|
|
16
|
+
OPTIONAL { ?item p:P576/psv:P576 [ wikibase:timeValue ?abolition; wikibase:timePrecision ?abolition_precision ] }
|
|
17
|
+
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
|
|
18
|
+
}
|
|
19
|
+
SPARQL
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Represents a single row returned from the Position query
|
|
25
|
+
class PositionData
|
|
26
|
+
def initialize(row)
|
|
27
|
+
@row = row
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def inception_date
|
|
31
|
+
QueryService::WikidataDate.new(inception_date_raw, inception_date_precision)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def abolition_date
|
|
35
|
+
QueryService::WikidataDate.new(abolition_date_raw, abolition_date_precision)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def position?
|
|
39
|
+
row.dig(:isPosition, :value) == 'true'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
attr_reader :row
|
|
45
|
+
|
|
46
|
+
def inception_date_raw
|
|
47
|
+
row.dig(:inception, :value).to_s[0..9]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def abolition_date_raw
|
|
51
|
+
row.dig(:abolition, :value).to_s[0..9]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def inception_date_precision
|
|
55
|
+
row.dig(:inception_precision, :value)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def abolition_date_precision
|
|
59
|
+
row.dig(:abolition_precision, :value)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'query_service'
|
|
4
|
+
require 'sparql/item_query'
|
|
5
|
+
require 'sparql/position_data'
|
|
6
|
+
require 'sparql/mandates'
|
|
7
|
+
require 'wikidata_position_history/checks'
|
|
8
|
+
require 'wikidata_position_history/report'
|
|
9
|
+
require 'wikidata_position_history/version'
|
|
10
|
+
|
|
11
|
+
require 'date'
|
|
12
|
+
|
|
13
|
+
require 'mediawiki/client'
|
|
14
|
+
require 'mediawiki/page'
|
|
15
|
+
|
|
16
|
+
module WikidataPositionHistory
|
|
17
|
+
# Rewrites a Wiki page
|
|
18
|
+
class PageRewriter
|
|
19
|
+
WIKI_TEMPLATE_NAME = 'PositionHolderHistory'
|
|
20
|
+
WIKI_USERNAME = ENV['WIKI_USERNAME']
|
|
21
|
+
WIKI_PASSWORD = ENV['WIKI_PASSWORD']
|
|
22
|
+
|
|
23
|
+
def initialize(mediawiki_site:, page_title:)
|
|
24
|
+
@mediawiki_site = mediawiki_site
|
|
25
|
+
@page_title = page_title.tr('_', ' ')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def run!
|
|
29
|
+
section.replace_output(*new_content)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def new_content
|
|
33
|
+
return [NO_ID_ERROR, 'The id parameter was missing'] if position_id.empty?
|
|
34
|
+
return [MALFORMED_ID_ERROR, 'The id parameter was malformed'] unless position_id =~ /^Q\d+$/
|
|
35
|
+
|
|
36
|
+
[WikidataPositionHistory::Report.new(position_id).wikitext, "Successfully updated holders of #{position_id}"]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
attr_reader :mediawiki_site, :page_title
|
|
42
|
+
|
|
43
|
+
NO_ID_ERROR = <<~EOERROR
|
|
44
|
+
'''#{WIKI_TEMPLATE_NAME} Error''': You must pass the <code>id</code>
|
|
45
|
+
parameter to the <code>#{WIKI_TEMPLATE_NAME}</code> template; e.g.
|
|
46
|
+
<nowiki>{{#{WIKI_TEMPLATE_NAME}|id=Q14211}}</nowiki>
|
|
47
|
+
EOERROR
|
|
48
|
+
|
|
49
|
+
MALFORMED_ID_ERROR = <<~EOERROR
|
|
50
|
+
'''#{WIKI_TEMPLATE_NAME} Error''': The <code>id</code> parameter was
|
|
51
|
+
malformed; it should be Q followed by a number of digits, e.g. as in:
|
|
52
|
+
|
|
53
|
+
<nowiki>{{#{WIKI_TEMPLATE_NAME}|id=Q14211}}</nowiki>
|
|
54
|
+
EOERROR
|
|
55
|
+
|
|
56
|
+
def position_id
|
|
57
|
+
return id_param unless id_param.empty?
|
|
58
|
+
|
|
59
|
+
derived_id
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def id_param
|
|
63
|
+
section.params[:id].to_s.strip
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def derived_id
|
|
67
|
+
page_title.scan(/Q\d+/).last.to_s
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def client
|
|
71
|
+
abort 'You must set the WIKI_USERNAME and WIKI_PASSWORD environment variables' unless WIKI_USERNAME && WIKI_PASSWORD
|
|
72
|
+
@client ||= MediaWiki::Client.new(
|
|
73
|
+
site: mediawiki_site,
|
|
74
|
+
username: ENV['WIKI_USERNAME'],
|
|
75
|
+
password: ENV['WIKI_PASSWORD']
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def section
|
|
80
|
+
@section ||= MediaWiki::Page::ReplaceableContent.new(
|
|
81
|
+
client: client,
|
|
82
|
+
title: page_title,
|
|
83
|
+
template: WIKI_TEMPLATE_NAME
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WikidataPositionHistory
|
|
4
|
+
# Checks if an Officeholder has any warning signs to report on
|
|
5
|
+
class Check
|
|
6
|
+
def initialize(later, current, earlier)
|
|
7
|
+
@later = later
|
|
8
|
+
@current = current
|
|
9
|
+
@earlier = earlier
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def explanation
|
|
13
|
+
possible_explanation if problem?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
attr_reader :later, :current, :earlier
|
|
19
|
+
|
|
20
|
+
def successor
|
|
21
|
+
current.next
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def predecessor
|
|
25
|
+
current.prev
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def latest_holder?
|
|
29
|
+
!!later
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def earliest_holder?
|
|
33
|
+
!!earlier
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Check
|
|
38
|
+
# Does the Officeholder have all the properties we expect?
|
|
39
|
+
class MissingFields < Check
|
|
40
|
+
def problem?
|
|
41
|
+
missing.any?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def headline
|
|
45
|
+
"Missing field#{missing.count > 1 ? 's' : ''}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def possible_explanation
|
|
49
|
+
"#{current.item} is missing #{missing.map { |field| "{{P|#{field_map[field]}}}" }.join(', ')}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def missing
|
|
53
|
+
expected.reject { |field| current.send(field) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def field_map
|
|
57
|
+
{
|
|
58
|
+
start_date: 580,
|
|
59
|
+
prev: 1365,
|
|
60
|
+
end_date: 582,
|
|
61
|
+
next: 1366,
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def expected
|
|
66
|
+
field_map.keys.select { |field| send("expect_#{field}?") }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def expect_start_date?
|
|
70
|
+
true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def expect_end_date?
|
|
74
|
+
later
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def expect_prev?
|
|
78
|
+
return unless earlier
|
|
79
|
+
return if earlier.item == current.item # sucessive terms by same person
|
|
80
|
+
|
|
81
|
+
!current.acting?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def expect_next?
|
|
85
|
+
return unless later
|
|
86
|
+
return if later.item == current.item # sucessive terms by same person
|
|
87
|
+
|
|
88
|
+
!current.acting?
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Does the 'replaces' match the previous item in the list?
|
|
93
|
+
class WrongPredecessor < Check
|
|
94
|
+
def problem?
|
|
95
|
+
earliest_holder? && !!predecessor && (earlier.item != predecessor)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def headline
|
|
99
|
+
'Inconsistent predecessor'
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def possible_explanation
|
|
103
|
+
"#{current.item} has a {{P|1365}} of #{predecessor}, which differs from #{earlier.item}"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Does the 'replaced by' match the next item in the list?
|
|
108
|
+
class WrongSuccessor < Check
|
|
109
|
+
def problem?
|
|
110
|
+
latest_holder? && !!successor && (later.item != successor)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def headline
|
|
114
|
+
'Inconsistent successor'
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def possible_explanation
|
|
118
|
+
"#{current.item} has a {{P|1366}} of #{successor}, which differs from #{later.item}"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Does the end date overlap with the successor's start date?
|
|
123
|
+
class Overlap < Check
|
|
124
|
+
def problem?
|
|
125
|
+
return false unless later
|
|
126
|
+
|
|
127
|
+
ends = current.end_date
|
|
128
|
+
return false if ends.empty?
|
|
129
|
+
|
|
130
|
+
ends > later.start_date
|
|
131
|
+
rescue ArgumentError
|
|
132
|
+
true
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def headline
|
|
136
|
+
comparable? ? 'Date overlap' : 'Date precision'
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def possible_explanation
|
|
140
|
+
"#{current.item} has a {{P|582}} of #{current.end_date}, which #{overlap_explanation} the {{P|580}} of #{later.start_date} for #{later.item}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
protected
|
|
144
|
+
|
|
145
|
+
def comparable?
|
|
146
|
+
# Seems like there must be a better way to do this
|
|
147
|
+
[current.end_date, later.start_date].sort
|
|
148
|
+
rescue ArgumentError
|
|
149
|
+
false
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def overlap_explanation
|
|
153
|
+
comparable? ? 'is later than' : 'may overlap with'
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WikidataPositionHistory
|
|
4
|
+
# A single output row of Wikitext for an officeholding
|
|
5
|
+
class MandateReport
|
|
6
|
+
def initialize(later, current, earlier)
|
|
7
|
+
@later = later
|
|
8
|
+
@current = current
|
|
9
|
+
@earlier = earlier
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def output
|
|
13
|
+
[row_start, ordinal_cell, member_cell, warnings_cell].join("\n")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
CHECKS = [Check::MissingFields, Check::WrongPredecessor, Check::WrongSuccessor, Check::Overlap].freeze
|
|
19
|
+
|
|
20
|
+
WARNING_LAYOUT = [
|
|
21
|
+
'<span style="display: block">[[File:Pictogram voting comment.svg|15px|link=]] ',
|
|
22
|
+
'<span style="color: #d33; font-weight: bold; vertical-align: middle;">%s</span> ',
|
|
23
|
+
'<ref>%s</ref></span>'
|
|
24
|
+
].join
|
|
25
|
+
|
|
26
|
+
attr_reader :later, :current, :earlier
|
|
27
|
+
|
|
28
|
+
def row_start
|
|
29
|
+
'|-'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ordinal_cell
|
|
33
|
+
%(| style="padding:0.5em 2em" | #{ordinal_string})
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ordinal_string
|
|
37
|
+
ordinal = current.ordinal or return ''
|
|
38
|
+
ordinal.concat('.')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def member_style
|
|
42
|
+
return 'font-size: 1.25em; display: block; font-style: italic;' if current.acting?
|
|
43
|
+
|
|
44
|
+
'font-size: 1.5em; display: block;'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def member_cell
|
|
48
|
+
format('| style="padding:0.5em 2em" | <span style="%s">%s</span> %s',
|
|
49
|
+
member_style, membership_person, membership_dates)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def warnings_cell
|
|
53
|
+
format('| style="padding:0.5em 2em 0.5em 1em; border: none; background: #fff; text-align: left;" | %s',
|
|
54
|
+
combined_warnings)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def combined_warnings
|
|
58
|
+
CHECKS.map do |check_class|
|
|
59
|
+
check = check_class.new(later, current, earlier)
|
|
60
|
+
format(WARNING_LAYOUT, check.headline, check.explanation) if check.problem?
|
|
61
|
+
end.join
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def membership_person
|
|
65
|
+
current.item
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def membership_dates
|
|
69
|
+
dates = [current.start_date, current.end_date]
|
|
70
|
+
# compact doesn't work here, even if we add #nil? to WikidataDate
|
|
71
|
+
return '' if dates.reject(&:empty?).empty?
|
|
72
|
+
|
|
73
|
+
dates.join(' – ')
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The entire wikitext generated for this report
|
|
78
|
+
class Report
|
|
79
|
+
def initialize(subject_item_id)
|
|
80
|
+
@subject_item_id = subject_item_id
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
attr_reader :subject_item_id
|
|
84
|
+
|
|
85
|
+
def wikitext
|
|
86
|
+
return no_items_output if mandates.empty?
|
|
87
|
+
|
|
88
|
+
[table_header, table_rows, table_footer].compact.join("\n")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def header
|
|
92
|
+
"== {{Q|#{subject_item_id}}} officeholders #{position_dates} =="
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def position_dates
|
|
96
|
+
dates = [metadata.inception_date, metadata.abolition_date]
|
|
97
|
+
return '' if dates.compact.empty?
|
|
98
|
+
|
|
99
|
+
format('(%s)', dates.join(' – '))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def wikitext_with_header
|
|
103
|
+
[header, wikitext].join("\n")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def metadata
|
|
109
|
+
# TODO: we might get more than one response, if a position has
|
|
110
|
+
# multiple dates
|
|
111
|
+
@metadata ||= SPARQL::PositionData.new(subject_item_id).results_as(PositionData).first
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def padded_mandates
|
|
115
|
+
[nil, mandates, nil].flatten(1)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def mandates
|
|
119
|
+
@mandates ||= SPARQL::Mandates.new(subject_item_id).results_as(Mandate)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def no_items_output
|
|
123
|
+
"\n{{PositionHolderHistory/error_no_holders|id=#{subject_item_id}}}\n"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def table_header
|
|
127
|
+
'{| class="wikitable" style="text-align: center; border: none;"'
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def table_footer
|
|
131
|
+
"|}\n"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def table_rows
|
|
135
|
+
padded_mandates.each_cons(3).map do |later, current, earlier|
|
|
136
|
+
MandateReport.new(later, current, earlier).output
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'wikidata_position_history/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.required_ruby_version = '>= 2.4.0'
|
|
9
|
+
spec.name = 'wikidata_position_history'
|
|
10
|
+
spec.version = WikidataPositionHistory::VERSION
|
|
11
|
+
spec.authors = ['Tony Bowden', 'Mark Longair']
|
|
12
|
+
spec.email = ['tony@tmtm.com']
|
|
13
|
+
|
|
14
|
+
spec.summary = 'Generates a wikitext history of a holders of a position in Wikidata'
|
|
15
|
+
spec.homepage = 'https://github.com/everypolitician/wikidata-position-history/'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
|
|
18
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
19
|
+
|
|
20
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
21
|
+
f.match(%r{^(test|spec|features)/})
|
|
22
|
+
end
|
|
23
|
+
spec.bindir = 'exe'
|
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
25
|
+
spec.require_paths = ['lib']
|
|
26
|
+
|
|
27
|
+
spec.add_runtime_dependency 'mediawiki-page-replaceable_content', '0.1.3'
|
|
28
|
+
spec.add_runtime_dependency 'rest-client', '~> 2.0'
|
|
29
|
+
|
|
30
|
+
spec.add_development_dependency 'bundler', '~> 2.1'
|
|
31
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
|
32
|
+
spec.add_development_dependency 'pry', '~> 0.10'
|
|
33
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
34
|
+
spec.add_development_dependency 'reek', '~> 6.0'
|
|
35
|
+
spec.add_development_dependency 'rubocop', '~> 0.89'
|
|
36
|
+
spec.add_development_dependency 'warning', '~> 1.1'
|
|
37
|
+
spec.add_development_dependency 'webmock', '~> 3.0.0'
|
|
38
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: wikidata_position_history
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.3.3
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tony Bowden
|
|
8
|
+
- Mark Longair
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: exe
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2020-08-29 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: mediawiki-page-replaceable_content
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - '='
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: 0.1.3
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - '='
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: 0.1.3
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: rest-client
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - "~>"
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '2.0'
|
|
35
|
+
type: :runtime
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - "~>"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '2.0'
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
name: bundler
|
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - "~>"
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '2.1'
|
|
49
|
+
type: :development
|
|
50
|
+
prerelease: false
|
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - "~>"
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '2.1'
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: minitest
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - "~>"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '5.0'
|
|
63
|
+
type: :development
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - "~>"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '5.0'
|
|
70
|
+
- !ruby/object:Gem::Dependency
|
|
71
|
+
name: pry
|
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - "~>"
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '0.10'
|
|
77
|
+
type: :development
|
|
78
|
+
prerelease: false
|
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - "~>"
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '0.10'
|
|
84
|
+
- !ruby/object:Gem::Dependency
|
|
85
|
+
name: rake
|
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - "~>"
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '13.0'
|
|
91
|
+
type: :development
|
|
92
|
+
prerelease: false
|
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - "~>"
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: '13.0'
|
|
98
|
+
- !ruby/object:Gem::Dependency
|
|
99
|
+
name: reek
|
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - "~>"
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '6.0'
|
|
105
|
+
type: :development
|
|
106
|
+
prerelease: false
|
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
108
|
+
requirements:
|
|
109
|
+
- - "~>"
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: '6.0'
|
|
112
|
+
- !ruby/object:Gem::Dependency
|
|
113
|
+
name: rubocop
|
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - "~>"
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: '0.89'
|
|
119
|
+
type: :development
|
|
120
|
+
prerelease: false
|
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
122
|
+
requirements:
|
|
123
|
+
- - "~>"
|
|
124
|
+
- !ruby/object:Gem::Version
|
|
125
|
+
version: '0.89'
|
|
126
|
+
- !ruby/object:Gem::Dependency
|
|
127
|
+
name: warning
|
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
|
129
|
+
requirements:
|
|
130
|
+
- - "~>"
|
|
131
|
+
- !ruby/object:Gem::Version
|
|
132
|
+
version: '1.1'
|
|
133
|
+
type: :development
|
|
134
|
+
prerelease: false
|
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
136
|
+
requirements:
|
|
137
|
+
- - "~>"
|
|
138
|
+
- !ruby/object:Gem::Version
|
|
139
|
+
version: '1.1'
|
|
140
|
+
- !ruby/object:Gem::Dependency
|
|
141
|
+
name: webmock
|
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
|
143
|
+
requirements:
|
|
144
|
+
- - "~>"
|
|
145
|
+
- !ruby/object:Gem::Version
|
|
146
|
+
version: 3.0.0
|
|
147
|
+
type: :development
|
|
148
|
+
prerelease: false
|
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
150
|
+
requirements:
|
|
151
|
+
- - "~>"
|
|
152
|
+
- !ruby/object:Gem::Version
|
|
153
|
+
version: 3.0.0
|
|
154
|
+
description:
|
|
155
|
+
email:
|
|
156
|
+
- tony@tmtm.com
|
|
157
|
+
executables:
|
|
158
|
+
- position-history-for-item
|
|
159
|
+
- update_wikidata_page
|
|
160
|
+
extensions: []
|
|
161
|
+
extra_rdoc_files: []
|
|
162
|
+
files:
|
|
163
|
+
- ".gitignore"
|
|
164
|
+
- ".reek.yml"
|
|
165
|
+
- ".rubocop.yml"
|
|
166
|
+
- ".travis.yml"
|
|
167
|
+
- CHANGELOG.md
|
|
168
|
+
- Gemfile
|
|
169
|
+
- Gemfile.lock
|
|
170
|
+
- LICENSE.txt
|
|
171
|
+
- README.md
|
|
172
|
+
- Rakefile
|
|
173
|
+
- bin/console
|
|
174
|
+
- bin/setup
|
|
175
|
+
- exe/position-history-for-item
|
|
176
|
+
- exe/update_wikidata_page
|
|
177
|
+
- lib/query_service.rb
|
|
178
|
+
- lib/sparql/item_query.rb
|
|
179
|
+
- lib/sparql/mandates.rb
|
|
180
|
+
- lib/sparql/position_data.rb
|
|
181
|
+
- lib/wikidata_position_history.rb
|
|
182
|
+
- lib/wikidata_position_history/checks.rb
|
|
183
|
+
- lib/wikidata_position_history/report.rb
|
|
184
|
+
- lib/wikidata_position_history/version.rb
|
|
185
|
+
- wikidata_position_history.gemspec
|
|
186
|
+
homepage: https://github.com/everypolitician/wikidata-position-history/
|
|
187
|
+
licenses:
|
|
188
|
+
- MIT
|
|
189
|
+
metadata:
|
|
190
|
+
allowed_push_host: https://rubygems.org
|
|
191
|
+
post_install_message:
|
|
192
|
+
rdoc_options: []
|
|
193
|
+
require_paths:
|
|
194
|
+
- lib
|
|
195
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
196
|
+
requirements:
|
|
197
|
+
- - ">="
|
|
198
|
+
- !ruby/object:Gem::Version
|
|
199
|
+
version: 2.4.0
|
|
200
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
201
|
+
requirements:
|
|
202
|
+
- - ">="
|
|
203
|
+
- !ruby/object:Gem::Version
|
|
204
|
+
version: '0'
|
|
205
|
+
requirements: []
|
|
206
|
+
rubyforge_project:
|
|
207
|
+
rubygems_version: 2.6.14.1
|
|
208
|
+
signing_key:
|
|
209
|
+
specification_version: 4
|
|
210
|
+
summary: Generates a wikitext history of a holders of a position in Wikidata
|
|
211
|
+
test_files: []
|