bipm-data-importer 0.2.1 → 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.
- checksums.yaml +4 -4
- data/README.adoc +68 -9
- data/TODO.refactor/01-remove-debug-code.md +18 -0
- data/TODO.refactor/02-fix-gemspec.md +21 -0
- data/TODO.refactor/03-update-readme.md +28 -0
- data/TODO.refactor/04-replace-require-relative-with-autoload.md +34 -0
- data/TODO.refactor/05-fix-outcomes-bugs.md +22 -0
- data/TODO.refactor/06-unify-clause-taxonomy.md +31 -0
- data/TODO.refactor/07-extract-french-parsing.md +21 -0
- data/TODO.refactor/08-extract-data-quirks.md +25 -0
- data/TODO.refactor/09-introduce-body-registry.md +23 -0
- data/TODO.refactor/10-introduce-strategy-pattern.md +34 -0
- data/TODO.refactor/11-extract-cli-class.md +20 -0
- data/TODO.refactor/12-specs-common.md +23 -0
- data/TODO.refactor/13-specs-cgpm.md +22 -0
- data/TODO.refactor/14-specs-asciimath.md +23 -0
- data/TODO.refactor/15-specs-outcomes.md +24 -0
- data/TODO.refactor/16-quarantine-failing-specs.md +23 -0
- data/TODO.refactor/17-typed-domain-models.md +32 -0
- data/TODO.refactor/README.md +62 -0
- data/bipm-data-importer.gemspec +11 -7
- data/exe/bipm-fetch +2 -376
- data/lib/bipm/data/importer/asciimath.rb +3 -3
- data/lib/bipm/data/importer/bodies.rb +45 -0
- data/lib/bipm/data/importer/body.rb +43 -0
- data/lib/bipm/data/importer/cgpm.rb +21 -0
- data/lib/bipm/data/importer/clauses.rb +239 -0
- data/lib/bipm/data/importer/cli.rb +83 -0
- data/lib/bipm/data/importer/common.rb +32 -121
- data/lib/bipm/data/importer/fetcher.rb +62 -0
- data/lib/bipm/data/importer/language.rb +66 -0
- data/lib/bipm/data/importer/quirks.rb +73 -0
- data/lib/bipm/data/importer/strategies/base.rb +40 -0
- data/lib/bipm/data/importer/strategies/spa_meetings.rb +340 -0
- data/lib/bipm/data/importer/strategies/static_index.rb +324 -0
- data/lib/bipm/data/importer/strategies.rb +13 -0
- data/lib/bipm/data/importer/text/french.rb +49 -0
- data/lib/bipm/data/importer/text.rb +11 -0
- data/lib/bipm/data/importer/version.rb +1 -1
- data/lib/bipm/data/importer.rb +31 -4
- data/lib/bipm/data/outcomes/action.rb +1 -1
- data/lib/bipm/data/outcomes/approval.rb +1 -1
- data/lib/bipm/data/outcomes/body.rb +1 -1
- data/lib/bipm/data/outcomes/consideration.rb +1 -1
- data/lib/bipm/data/outcomes/localized_body.rb +1 -1
- data/lib/bipm/data/outcomes/meeting.rb +2 -2
- data/lib/bipm/data/outcomes/resolution.rb +1 -1
- data/lib/bipm/data/outcomes.rb +9 -13
- data/lib/bipm-data-importer.rb +1 -1
- metadata +46 -19
- data/.hound.yml +0 -5
- data/.rspec +0 -3
- data/.rubocop.yml +0 -10
- data/exe/bipm-fetch-cgpm +0 -3
data/bipm-data-importer.gemspec
CHANGED
|
@@ -11,17 +11,17 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.summary = "Importer for BIPM CGPM and CIPM content"
|
|
12
12
|
spec.description = "Importer for BIPM CGPM and CIPM content"
|
|
13
13
|
spec.homepage = "https://github.com/metanorma/bipm-data-importer"
|
|
14
|
-
spec.required_ruby_version = ">=
|
|
14
|
+
spec.required_ruby_version = ">= 3.3.0"
|
|
15
15
|
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
17
|
spec.metadata["source_code_uri"] = "https://github.com/metanorma/bipm-data-importer"
|
|
18
18
|
spec.metadata["changelog_uri"] = "https://github.com/metanorma/bipm-data-importer"
|
|
19
19
|
|
|
20
|
-
# Specify which files should be added to the gem when it is released.
|
|
21
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
22
20
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
23
21
|
`git ls-files -z`.split("\x0").reject do |f|
|
|
24
|
-
|
|
22
|
+
f == __FILE__ ||
|
|
23
|
+
f.match(%r{\A(?:spec|features|\.git|\.circleci|\.github)/}) ||
|
|
24
|
+
f.start_with?(".gitignore", ".rspec", ".rubocop")
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
spec.bindir = "exe"
|
|
@@ -30,10 +30,14 @@ Gem::Specification.new do |spec|
|
|
|
30
30
|
|
|
31
31
|
spec.add_dependency "nokogiri"
|
|
32
32
|
spec.add_dependency "mechanize"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
# coradoc 2.0 removed `coradoc/input/html` (called in
|
|
34
|
+
# lib/bipm/data/importer/common.rb) as part of a major refactor.
|
|
35
|
+
# Pinned to 1.x until a follow-up ports the HTML input calls to the
|
|
36
|
+
# coradoc 2.x API (or wherever that functionality has been extracted).
|
|
37
|
+
spec.add_dependency "coradoc", "~> 1.1"
|
|
36
38
|
spec.add_dependency "vcr"
|
|
39
|
+
|
|
40
|
+
spec.add_development_dependency "pry"
|
|
37
41
|
spec.add_development_dependency "webmock"
|
|
38
42
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
39
43
|
spec.add_development_dependency "rspec", "~> 3.0"
|
data/exe/bipm-fetch
CHANGED
|
@@ -1,378 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
bodies = {
|
|
6
|
-
"JCRB": 'https://www.bipm.org/en/committees/jc/jcrb',
|
|
7
|
-
"JCGM": 'https://www.bipm.org/en/committees/jc/jcgm',
|
|
8
|
-
"CCU": 'https://www.bipm.org/en/committees/cc/ccu',
|
|
9
|
-
"CCTF": 'https://www.bipm.org/en/committees/cc/cctf',
|
|
10
|
-
"CCT": 'https://www.bipm.org/en/committees/cc/cct',
|
|
11
|
-
"CCRI": 'https://www.bipm.org/en/committees/cc/ccri',
|
|
12
|
-
"CCPR": 'https://www.bipm.org/en/committees/cc/ccpr',
|
|
13
|
-
"CCQM": 'https://www.bipm.org/en/committees/cc/ccqm',
|
|
14
|
-
"CCM": 'https://www.bipm.org/en/committees/cc/ccm',
|
|
15
|
-
"CCL": 'https://www.bipm.org/en/committees/cc/ccl',
|
|
16
|
-
"CCEM": 'https://www.bipm.org/en/committees/cc/ccem',
|
|
17
|
-
"CCAUV": 'https://www.bipm.org/en/committees/cc/ccauv',
|
|
18
|
-
"CIPM": 'https://www.bipm.org/en/committees/ci/cipm',
|
|
19
|
-
"CGPM": 'https://www.bipm.org/en/committees/cg/cgpm',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
BASE_DIR = "data"
|
|
23
|
-
a = Mechanize.new
|
|
24
|
-
|
|
25
|
-
bodies.each do |bodyid, bodyurl|
|
|
26
|
-
next if ARGV[0] == '--fork' && fork
|
|
27
|
-
|
|
28
|
-
next if ARGV[0] && ARGV[0].start_with?("--body=") && ARGV[0].downcase != "--body=#{bodyid}".downcase
|
|
29
|
-
|
|
30
|
-
body = bodyid.to_s.downcase.gsub(" ", "-").to_sym
|
|
31
|
-
|
|
32
|
-
meetings_en = VCR.use_cassette "#{body}/#{body}-meetings" do
|
|
33
|
-
a.get "#{bodyurl}/meetings"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
meetings_fr = VCR.use_cassette "#{body}/#{body}-meetings-fr" do
|
|
37
|
-
a.get "#{bodyurl.gsub("/en/", "/fr/")}/meetings"
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
publications_en = VCR.use_cassette "#{body}/#{body}-publications" do
|
|
41
|
-
a.get "#{bodyurl}/publications"
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
publications_fr = VCR.use_cassette "#{body}/#{body}-publications-fr" do
|
|
45
|
-
a.get "#{bodyurl.gsub("/en/", "/fr/")}/publications"
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
resolutions = {}
|
|
49
|
-
%w[en fr].each do |meeting_lang|
|
|
50
|
-
next if ARGV[0] == '--fork' && fork
|
|
51
|
-
|
|
52
|
-
meeting_lang_sfx = (meeting_lang == 'fr') ? "-fr" : ""
|
|
53
|
-
meeting_lang_sfx_dir = (meeting_lang == 'fr') ? "-fr" : "-en"
|
|
54
|
-
|
|
55
|
-
meetings = (meeting_lang == 'en') ? meetings_en : meetings_fr
|
|
56
|
-
publications = (meeting_lang == 'en') ? publications_en : publications_fr
|
|
57
|
-
|
|
58
|
-
index = {
|
|
59
|
-
"meetings" => {"fr" => [], "en" => []},
|
|
60
|
-
"decisions" => {"fr" => [], "en" => []},
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
meetings.css('.meetings-list__item').each do |meeting_div|
|
|
64
|
-
date = Bipm::Data::Importer::Common.extract_date(meeting_div.at_css('.meetings-list__informations-date').text)
|
|
65
|
-
|
|
66
|
-
title = meeting_div.at_css('.meetings-list__informations-title').text.strip
|
|
67
|
-
href = meeting_div.at_css('.meetings-list__informations-title').attr('href')
|
|
68
|
-
href = "/#{meeting_lang}" + href unless href.start_with? "/#{meeting_lang}/"
|
|
69
|
-
|
|
70
|
-
ident = href.split("/#{body}/").last.gsub('/', '.')
|
|
71
|
-
yr = href.include?("/wg/") ? nil : href.split('-').last
|
|
72
|
-
meeting_id = if href.include?("/wg/")
|
|
73
|
-
# workgroup logic
|
|
74
|
-
wg = href.split("/wg/").last.split("/").first
|
|
75
|
-
href.split("/").last
|
|
76
|
-
else
|
|
77
|
-
parts = href.split("/").last.split("-")
|
|
78
|
-
if parts.length == 2
|
|
79
|
-
# Only the number
|
|
80
|
-
parts[0]
|
|
81
|
-
else
|
|
82
|
-
parts[0] + parts[1].sub("_", "-")
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
meeting = VCR.use_cassette "#{body}/#{body}-meeting-#{ident}#{meeting_lang_sfx}" do
|
|
87
|
-
a.get href
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Duplicate data upstream:
|
|
91
|
-
# - https://www.bipm.org/en/committees/ci/cipm/104-_1-2015
|
|
92
|
-
# - https://www.bipm.org/en/committees/ci/cipm/104-_2-2015
|
|
93
|
-
yr = nil if [bodyid, meeting_id] == [:CIPM, '104-2']
|
|
94
|
-
|
|
95
|
-
# meeting logic, as in for the `meetings-xx` structure
|
|
96
|
-
# this structure contains recommendations
|
|
97
|
-
|
|
98
|
-
if yr
|
|
99
|
-
has_recommendations = false
|
|
100
|
-
|
|
101
|
-
pdf = Bipm::Data::Importer::Common.extract_pdf(meeting, meeting_lang)
|
|
102
|
-
|
|
103
|
-
h = {
|
|
104
|
-
"metadata" => {
|
|
105
|
-
"title" => title,
|
|
106
|
-
"identifier" => meeting_id,
|
|
107
|
-
"date" => date.to_s,
|
|
108
|
-
"source" => "BIPM - Pavillon de Breteuil",
|
|
109
|
-
"url" => meeting.uri.to_s,
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
h["pdf"] = pdf if pdf
|
|
114
|
-
|
|
115
|
-
resolutions = meeting.css(".bipm-resolutions .publications__content").map do |res_div|
|
|
116
|
-
res_div.at_css('a').attr('href')
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# A mistake on a website, resolution 2 listed twice...
|
|
120
|
-
# https://www.bipm.org/fr/committees/ci/cipm/94-2005/
|
|
121
|
-
if [bodyid, meeting_lang, meeting_id] == [:CIPM, 'fr', '94'] && resolutions.sort.uniq != resolutions.sort
|
|
122
|
-
resolutions = (1..3).map do |i|
|
|
123
|
-
"https://www.bipm.org/fr/committees/ci/cipm/94-2005/resolution-#{i}"
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
h["resolutions"] = resolutions.map do |href|
|
|
128
|
-
href = href.gsub('/web/guest/', "/#{meeting_lang}/")
|
|
129
|
-
href = href.sub("www.bipm.org/", "www.bipm.org/#{meeting_lang}/") unless href.include? "/#{meeting_lang}/"
|
|
130
|
-
|
|
131
|
-
# error: https://www.bipm.org/fr/committees/ci/cipm/104-_1-2015 has wrong references to Recommandations
|
|
132
|
-
href = href.gsub('/104-2015/', '/104-_1-2015/')
|
|
133
|
-
|
|
134
|
-
res_id = href.split("-").last.to_i
|
|
135
|
-
res = VCR.use_cassette("#{body}/#{body}-recommendation-#{yr}-#{res_id}#{meeting_lang_sfx}") do
|
|
136
|
-
a.get href
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
has_recommendations = true
|
|
140
|
-
|
|
141
|
-
Bipm::Data::Importer::Common.parse_resolution(res, res_id, date, body, meeting_lang, "recommendation?")
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
index["meetings"][meeting_lang] << h
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# decisions logic, as in for the `decisions-xx` structure
|
|
148
|
-
# this structure contains decisions
|
|
149
|
-
|
|
150
|
-
if meeting_id
|
|
151
|
-
h = {
|
|
152
|
-
"metadata" => {
|
|
153
|
-
"title" => title,
|
|
154
|
-
"identifier" => meeting_id,
|
|
155
|
-
"date" => date.to_s,
|
|
156
|
-
"source" => "BIPM - Pavillon de Breteuil",
|
|
157
|
-
"url" => meeting.uri.to_s
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
h["metadata"]["workgroup"] = wg if wg
|
|
162
|
-
|
|
163
|
-
h["resolutions"] = meeting.css('.bipm-decisions .decisions').map do |titletr|
|
|
164
|
-
title = titletr.at_css('.title-third').text.strip
|
|
165
|
-
|
|
166
|
-
type = case title
|
|
167
|
-
when /\AD[eé]cision/
|
|
168
|
-
"decision"
|
|
169
|
-
when /\AR[eé]solution/
|
|
170
|
-
"resolution"
|
|
171
|
-
when /\AAction/
|
|
172
|
-
"action"
|
|
173
|
-
when /\ARecomm[ae]ndation/
|
|
174
|
-
"recommendation"
|
|
175
|
-
else
|
|
176
|
-
"decision"
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
r = {
|
|
180
|
-
"dates" => [date.to_s],
|
|
181
|
-
"subject" => bodyid.to_s,
|
|
182
|
-
"type" => type,
|
|
183
|
-
"title" => title,
|
|
184
|
-
"identifier" => "#{titletr.attr('data-meeting')}-#{titletr.attr('data-number')}",
|
|
185
|
-
"url" => meeting.uri.to_s,
|
|
186
|
-
#TODO: "reference" => meeting.uri.merge(titletr.attr('data-link')).to_s,
|
|
187
|
-
|
|
188
|
-
"categories" => JSON.parse(titletr.attr('data-decisioncategories')).map(&:strip).uniq,
|
|
189
|
-
|
|
190
|
-
"considerations" => [],
|
|
191
|
-
"actions" => [],
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
contenttr = titletr.attr('data-text')
|
|
195
|
-
if contenttr.empty?
|
|
196
|
-
# This document has empty data-text attribute https://www.bipm.org/fr/committees/jc/jcrb/4-2000
|
|
197
|
-
contenttr = titletr.css('p').map(&:inner_html).join("\n")
|
|
198
|
-
end
|
|
199
|
-
contenttr = Nokogiri::HTML(contenttr)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
Bipm::Data::Importer::Common.replace_links contenttr, meeting, meeting_lang
|
|
203
|
-
|
|
204
|
-
part = Bipm::Data::Importer::Common.ng_to_string(contenttr)
|
|
205
|
-
part = part.gsub(%r"<a name=\"haut\">(.*?)</a>"m, '\1')
|
|
206
|
-
parse = Nokogiri::HTML(part).text.strip
|
|
207
|
-
|
|
208
|
-
parse =~ /\A(((the|le|la|Le secrétaire du|Le président du|Les membres du|Le directeur du) +[BC][IG]PM( Director| President| Secretary| members)?|Dr May|W\.E\. May)[, ]+)/i
|
|
209
|
-
subject = $1
|
|
210
|
-
if subject
|
|
211
|
-
parse = parse[subject.length..-1]
|
|
212
|
-
part = part.gsub(/\A(<html><body>\n*<p>)#{Regexp.escape subject}/, '\1')
|
|
213
|
-
# Note: we set "CIPM" as the subject for all CIPM decisions
|
|
214
|
-
# r['subject'] = subject.strip
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
xparse = parse
|
|
218
|
-
|
|
219
|
-
(1..1024).each do |pass|
|
|
220
|
-
Bipm::Data::Importer::CONSIDERATIONS.any? do |k,v|
|
|
221
|
-
if xparse =~ /\A#{Bipm::Data::Importer::PREFIX}#{k}\b/i
|
|
222
|
-
r["considerations"] << {
|
|
223
|
-
"type" => v,
|
|
224
|
-
"date_effective" => date.to_s,
|
|
225
|
-
"message" => Bipm::Data::Importer::Common.format_message(part),
|
|
226
|
-
}
|
|
227
|
-
end
|
|
228
|
-
end && break
|
|
229
|
-
|
|
230
|
-
Bipm::Data::Importer::ACTIONS.any? do |k,v|
|
|
231
|
-
if xparse =~ /\A#{Bipm::Data::Importer::PREFIX}#{k}\b/i
|
|
232
|
-
r["actions"] << {
|
|
233
|
-
"type" => v,
|
|
234
|
-
"date_effective" => date.to_s,
|
|
235
|
-
"message" => Bipm::Data::Importer::Common.format_message(part),
|
|
236
|
-
}
|
|
237
|
-
end
|
|
238
|
-
end && break
|
|
239
|
-
|
|
240
|
-
case pass
|
|
241
|
-
when 1, 2, 3
|
|
242
|
-
xparse = xparse.gsub(/\A.*?(CIPM( President| in its meeting)?:?\n*|\((2018|CIPM)\)|de la 103e session) /m, '')
|
|
243
|
-
when 4
|
|
244
|
-
xparse = parse.gsub(/\A.*?, /m, '')
|
|
245
|
-
when 5
|
|
246
|
-
xparse = parse.gsub(/\A.*? (and|et) /m, '')
|
|
247
|
-
else
|
|
248
|
-
r["x-unparsed"] ||= []
|
|
249
|
-
r["x-unparsed"] << parse
|
|
250
|
-
|
|
251
|
-
break
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
r
|
|
256
|
-
end.sort_by { |i| [i["type"], i["identifier"].scan(/([0-9]+|[^0-9]+)/).map(&:first).map { |i| i =~ /[0-9]/ ? i.to_i : i }] }
|
|
257
|
-
|
|
258
|
-
# Mistake on a website: numbering is wrong
|
|
259
|
-
# https://www.bipm.org/fr/committees/jc/jcrb/43-2021
|
|
260
|
-
if [bodyid, meeting_lang, meeting_id] == [:JCRB, "fr", "43"]
|
|
261
|
-
ids = h["resolutions"].map { |i| [i["type"], i["identifier"]] }.sort
|
|
262
|
-
|
|
263
|
-
# Unfixed yet still, let's "massage" it
|
|
264
|
-
if ids != ids.uniq
|
|
265
|
-
idx = 1
|
|
266
|
-
h["resolutions"].each do |i|
|
|
267
|
-
next unless i["identifier"] == "43-1" && i["type"] == "action"
|
|
268
|
-
i["identifier"] += "-xxx-#{idx}"
|
|
269
|
-
idx += 1
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
h["working_documents"] = meeting.css('.portlet-boundary_CommitteePublications_ .publications__content').map do |d|
|
|
275
|
-
# Website depends on an Accept-Language header but caches responses. This creates non-determinism. We must unify those.
|
|
276
|
-
# $ curl -H "Accept-Language: " "https://www.bipm.org/en/committees/cc/ccqm/22-2016?$RANDOM$RANDOM" 2>/dev/null | grep -A2 publications__date
|
|
277
|
-
# <p class="publications__date">
|
|
278
|
-
# Mon Jul 09 00:00:00 GMT 2018
|
|
279
|
-
# </p>
|
|
280
|
-
# $ curl -H "Accept-Language: en-us, en" "https://www.bipm.org/en/committees/cc/ccqm/22-2016?$RANDOM$RANDOM" 2>/dev/null | grep -A2 publications__date
|
|
281
|
-
# <p class="publications__date">
|
|
282
|
-
# 09/07/2018
|
|
283
|
-
# </p>
|
|
284
|
-
date_str = d.at_css('.publications__date')&.text&.strip
|
|
285
|
-
date_str = Date.parse(date_str).strftime("%d/%m/%Y") if date_str
|
|
286
|
-
|
|
287
|
-
{
|
|
288
|
-
"title" => d.at_css('.title-third').text.strip,
|
|
289
|
-
"pdf" => d.at_css('.title-third').attr('href'),
|
|
290
|
-
"description" => d.css('.publications__body').first.text.strip,
|
|
291
|
-
"author" => d.css('.publications__body').last.text.strip,
|
|
292
|
-
"date" => date_str,
|
|
293
|
-
}.compact
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
index["decisions"][meeting_lang] << h
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
# index logic: creates an index of all meetings mentioned on the website and
|
|
300
|
-
# their correct references to `decisions-xx` and `meetings-xx` if present.
|
|
301
|
-
# meeting = {
|
|
302
|
-
# "title" => title,
|
|
303
|
-
# "url" => href,
|
|
304
|
-
# "ident" => ident,
|
|
305
|
-
# "meeting_ref" => yr,
|
|
306
|
-
# "decisions_ref" => meeting_id,
|
|
307
|
-
# }.compact
|
|
308
|
-
|
|
309
|
-
# index["meetings"] << meeting
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
# File.write("#{BASE_DIR}/cipm/index#{meeting_lang_sfx_dir}.yml", YAML.dump(index))
|
|
313
|
-
|
|
314
|
-
parts = index["meetings"][meeting_lang] + index["decisions"][meeting_lang]
|
|
315
|
-
parts = parts.group_by { |i| [i["metadata"]["workgroup"].to_s, i["metadata"]["identifier"].to_s] }
|
|
316
|
-
parts = parts.sort_by { |(wg,i),| [wg, i.to_i, i] }.to_h # Numeric sort by workgroup, then key
|
|
317
|
-
|
|
318
|
-
parts.each do |(wg, mid), hs|
|
|
319
|
-
h = {
|
|
320
|
-
"metadata" => hs.first["metadata"],
|
|
321
|
-
"pdf" => hs.first["pdf"],
|
|
322
|
-
"resolutions" => hs.map { |i| i["resolutions"] }.sum([]),
|
|
323
|
-
"working_documents" => hs.map { |i| i["working_documents"] || [] }.sum([])
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
h.delete("pdf") unless h["pdf"]
|
|
327
|
-
h.delete("working_documents") if h["working_documents"].empty?
|
|
328
|
-
|
|
329
|
-
wg = hs.first["metadata"]["workgroup"]
|
|
330
|
-
if wg
|
|
331
|
-
fn = "#{BASE_DIR}/#{body}/workgroups/#{wg}/meetings#{meeting_lang_sfx_dir}/meeting-#{mid}.yml"
|
|
332
|
-
else
|
|
333
|
-
fn = "#{BASE_DIR}/#{body}/meetings#{meeting_lang_sfx_dir}/meeting-#{mid}.yml"
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
FileUtils.mkdir_p(File.dirname(fn))
|
|
337
|
-
File.write(fn, YAML.dump(h))
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
# Publications handling
|
|
341
|
-
categories = publications.css(".publications").map do |i|
|
|
342
|
-
title = i.previous_element&.text&.strip || "Misc"
|
|
343
|
-
|
|
344
|
-
{
|
|
345
|
-
"title" => title,
|
|
346
|
-
"documents" => i.css(".publications__content").map do |d|
|
|
347
|
-
{
|
|
348
|
-
"title" => d.at_css(".title-third").text.strip.gsub(/\s+/, ' '),
|
|
349
|
-
"pdf" => d.at_css(".title-third")&.attr("href")&.split('?')&.first,
|
|
350
|
-
# "description" => d.css('.publications__body')[0]&.text&.strip,
|
|
351
|
-
# "author" => d.css('.publications__body')[1]&.text&.strip,
|
|
352
|
-
}.compact
|
|
353
|
-
end
|
|
354
|
-
}
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
unless categories.empty?
|
|
358
|
-
doc = {
|
|
359
|
-
"metadata" => {
|
|
360
|
-
"url" => bodyurl.gsub("/en/", "/#{meeting_lang}/") + "/publications/"
|
|
361
|
-
},
|
|
362
|
-
"categories" => categories
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
fn = "#{BASE_DIR}/#{body}/publications-#{meeting_lang}.yml"
|
|
366
|
-
|
|
367
|
-
File.write(fn, YAML.dump(doc))
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
puts "* #{bodyid}/#{meeting_lang} parsing done"
|
|
371
|
-
exit! if ARGV[0] == '--fork'
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
2.times { Process.wait } if ARGV[0] == '--fork'
|
|
375
|
-
exit! if ARGV[0] == '--fork'
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
bodies.length.times { Process.wait } if ARGV[0] == '--fork'
|
|
3
|
+
require "bipm-data-importer"
|
|
4
|
+
exit Bipm::Data::Importer::CLI.new(ARGV).run
|
|
@@ -9,10 +9,10 @@ module Bipm
|
|
|
9
9
|
SPACE_AFTER=/(?:\Z|(?= |\s|[,()\/.~"^]))/
|
|
10
10
|
|
|
11
11
|
PREFIXES = /m|c|d|k|M|G|T|/
|
|
12
|
-
UNITS = /t|m|mol|cal|µ|s|g|W|cd|Hz|J|K|N|V|H|A|C|F|T|Wb|sr|lx|lm|bar|sb|h|rad|°C|°F|°K/
|
|
12
|
+
UNITS = /t|m|mol|cal|µ|s|g|W|cd|Hz|J|K|N|V|H|A|C|F|T|Wb|sr|lx|lm|bar|sb|h|rad|fm|°C|°F|°K/
|
|
13
13
|
|
|
14
14
|
def asciidoc_extract_math str
|
|
15
|
-
str.gsub(/\b_(#{MATH}{1,3})_/, 'stem:[\1]')
|
|
15
|
+
str.gsub(/\b_?_(#{MATH}{1,3})_?_/, 'stem:[\1]')
|
|
16
16
|
.gsub("_,_", ',') # Some mistake in formatting
|
|
17
17
|
.gsub("^er^", 'ESCUPerESCUP') # French specialities
|
|
18
18
|
.gsub(/(bar|A) (table|of|key|de|being|full|1)( |,)/, 'ESC\1 \2\3') # A is Ampere, but also a particle, bar is a bar but also a bar
|
|
@@ -69,4 +69,4 @@ module Bipm
|
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
|
-
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bipm
|
|
4
|
+
module Data
|
|
5
|
+
module Importer
|
|
6
|
+
# Open/closed registry of all known BIPM bodies.
|
|
7
|
+
# Adding a body = adding a row here, not editing CLI/scraper code.
|
|
8
|
+
module Bodies
|
|
9
|
+
ALL = {
|
|
10
|
+
jcrb: Body.new(id: :jcrb, display_name: "JCRB", path: "committees/jc/jcrb", strategy: :spa_meetings),
|
|
11
|
+
jcgm: Body.new(id: :jcgm, display_name: "JCGM", path: "committees/jc/jcgm", strategy: :spa_meetings),
|
|
12
|
+
ccu: Body.new(id: :ccu, display_name: "CCU", path: "committees/cc/ccu", strategy: :spa_meetings),
|
|
13
|
+
cctf: Body.new(id: :cctf, display_name: "CCTF", path: "committees/cc/cctf", strategy: :spa_meetings),
|
|
14
|
+
cct: Body.new(id: :cct, display_name: "CCT", path: "committees/cc/cct", strategy: :spa_meetings),
|
|
15
|
+
ccri: Body.new(id: :ccri, display_name: "CCRI", path: "committees/cc/ccri", strategy: :spa_meetings),
|
|
16
|
+
ccpr: Body.new(id: :ccpr, display_name: "CCPR", path: "committees/cc/ccpr", strategy: :spa_meetings),
|
|
17
|
+
ccqm: Body.new(id: :ccqm, display_name: "CCQM", path: "committees/cc/ccqm", strategy: :spa_meetings),
|
|
18
|
+
ccm: Body.new(id: :ccm, display_name: "CCM", path: "committees/cc/ccm", strategy: :spa_meetings),
|
|
19
|
+
ccl: Body.new(id: :ccl, display_name: "CCL", path: "committees/cc/ccl", strategy: :spa_meetings),
|
|
20
|
+
ccem: Body.new(id: :ccem, display_name: "CCEM", path: "committees/cc/ccem", strategy: :spa_meetings),
|
|
21
|
+
ccauv: Body.new(id: :ccauv, display_name: "CCAUV", path: "committees/cc/ccauv", strategy: :spa_meetings),
|
|
22
|
+
cipm: Body.new(id: :cipm, display_name: "CIPM", path: "committees/ci/cipm", strategy: :spa_meetings),
|
|
23
|
+
cgpm: Body.new(id: :cgpm, display_name: "CGPM", path: "worldwide-metrology/cgpm/resolutions.html", strategy: :static_index),
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
UnknownBodyError = Class.new(ArgumentError)
|
|
27
|
+
|
|
28
|
+
def self.all
|
|
29
|
+
ALL.values
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.find(id)
|
|
33
|
+
key = id.to_sym
|
|
34
|
+
ALL[key] || raise(UnknownBodyError, "unknown body: #{id.inspect} (known: #{ALL.keys.inspect})")
|
|
35
|
+
end
|
|
36
|
+
singleton_class.alias_method :[], :find
|
|
37
|
+
|
|
38
|
+
def self.each(&block)
|
|
39
|
+
ALL.values.each(&block)
|
|
40
|
+
end
|
|
41
|
+
singleton_class.include Enumerable
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bipm
|
|
4
|
+
module Data
|
|
5
|
+
module Importer
|
|
6
|
+
# A BIPM committee body (CIPM, CGPM, CCAUV, etc.).
|
|
7
|
+
#
|
|
8
|
+
# `path` is locale-agnostic — `url(:en)` and `url(:fr)` are constructed
|
|
9
|
+
# symmetrically by inserting the language segment into the BIPM URL
|
|
10
|
+
# template. Neither language is canonical; both are first-class.
|
|
11
|
+
class Body
|
|
12
|
+
BIPM_ORIGIN = "https://www.bipm.org".freeze
|
|
13
|
+
|
|
14
|
+
attr_reader :id, :display_name, :path, :strategy
|
|
15
|
+
|
|
16
|
+
def initialize(id:, display_name:, path:, strategy:)
|
|
17
|
+
@id = id
|
|
18
|
+
@display_name = display_name
|
|
19
|
+
@path = path
|
|
20
|
+
@strategy = strategy
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def url(language)
|
|
24
|
+
lang = language.is_a?(Language) ? language : Language.find(language)
|
|
25
|
+
"#{BIPM_ORIGIN}/#{lang}/#{path}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def eql?(other)
|
|
29
|
+
other.is_a?(Body) && @id == other.id
|
|
30
|
+
end
|
|
31
|
+
alias == eql?
|
|
32
|
+
|
|
33
|
+
def hash
|
|
34
|
+
@id.hash
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_s
|
|
38
|
+
"#{display_name} (#{id})"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bipm
|
|
4
|
+
module Data
|
|
5
|
+
module Importer
|
|
6
|
+
# Backward-compatibility facade. The implementation lives in
|
|
7
|
+
# Bipm::Data::Importer::Strategies::StaticIndex; this module exists
|
|
8
|
+
# so that older code calling CGPM.run(agent, base_dir) keeps working.
|
|
9
|
+
#
|
|
10
|
+
# New code should go through Bipm::Data::Importer.fetch(:cgpm).
|
|
11
|
+
module CGPM
|
|
12
|
+
def self.run(agent, base_dir)
|
|
13
|
+
Strategies::StaticIndex.new(
|
|
14
|
+
body: Bodies[:cgpm],
|
|
15
|
+
base_dir: base_dir,
|
|
16
|
+
).call(agent: agent)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|