jekyll-open-sdg-plugins 1.6.1 → 1.8.0.pre.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +16 -16
  3. data/.github/workflows/test-pull-requests.yml +17 -17
  4. data/.gitignore +6 -6
  5. data/LICENSE +21 -21
  6. data/Makefile +33 -33
  7. data/README.md +7 -7
  8. data/jekyll-open-sdg-plugins.gemspec +18 -18
  9. data/lib/jekyll-open-sdg-plugins/backwards_compatibility.rb +97 -64
  10. data/lib/jekyll-open-sdg-plugins/create_goals.rb +88 -85
  11. data/lib/jekyll-open-sdg-plugins/create_indicators.rb +209 -206
  12. data/lib/jekyll-open-sdg-plugins/create_pages.rb +150 -135
  13. data/lib/jekyll-open-sdg-plugins/fetch_remote_data.rb +188 -188
  14. data/lib/jekyll-open-sdg-plugins/helpers.rb +132 -132
  15. data/lib/jekyll-open-sdg-plugins/metadata_schema_to_config.rb +72 -72
  16. data/lib/jekyll-open-sdg-plugins/schema-indicator-config.json +787 -709
  17. data/lib/jekyll-open-sdg-plugins/schema-site-config.json +1712 -1607
  18. data/lib/jekyll-open-sdg-plugins/sdg_variables.rb +614 -549
  19. data/lib/jekyll-open-sdg-plugins/search_index.rb +102 -102
  20. data/lib/jekyll-open-sdg-plugins/site_configuration.rb +87 -73
  21. data/lib/jekyll-open-sdg-plugins/translate_date.rb +122 -122
  22. data/lib/jekyll-open-sdg-plugins/translate_key.rb +20 -20
  23. data/lib/jekyll-open-sdg-plugins/translate_metadata_field.rb +111 -111
  24. data/lib/jekyll-open-sdg-plugins/validate_indicator_config.rb +52 -52
  25. data/lib/jekyll-open-sdg-plugins/validate_site_config.rb +34 -34
  26. data/lib/jekyll-open-sdg-plugins/version.rb +3 -3
  27. data/lib/jekyll-open-sdg-plugins.rb +18 -18
  28. data/tests/Gemfile +7 -7
  29. data/tests/_config.yml +168 -168
  30. metadata +5 -5
@@ -1,188 +1,188 @@
1
- require "jekyll"
2
- require 'json'
3
- require 'deep_merge'
4
- require 'open-uri'
5
- require_relative "helpers"
6
-
7
- module JekyllOpenSdgPlugins
8
- class FetchRemoteData < Jekyll::Generator
9
- safe true
10
- priority :highest
11
-
12
- # Fix a Unix path in case we are on Windows.
13
- def fix_path(path)
14
- path_parts = path.split('/')
15
- return path_parts.join(File::SEPARATOR)
16
- end
17
-
18
- # Our hardcoded list of pieces of the build that we expect.
19
- def get_endpoints()
20
- return {
21
- 'meta' => 'meta/all.json',
22
- 'headlines' => 'headline/all.json',
23
- 'schema' => 'meta/schema.json',
24
- 'reporting' => 'stats/reporting.json',
25
- 'disaggregation' => 'stats/disaggregation.json',
26
- 'translations' => 'translations/translations.json',
27
- 'zip' => 'zip/all_indicators.json',
28
- 'indicator_downloads' => 'downloads/indicator-downloads.json',
29
- 'data_packages' => 'data-packages/all.json',
30
- }
31
- end
32
-
33
- # Get a build from a local folder on disk or a remote URL on the Internet.
34
- def fetch_build(path)
35
-
36
- is_remote = opensdg_is_path_remote(path)
37
- build = {}
38
- get_endpoints().each do |key, value|
39
- endpoint = is_remote ? path + '/' + value : File.join(path, fix_path(value))
40
-
41
- begin
42
- json_file = is_remote ? open(endpoint) : File.open(endpoint)
43
- build[key] = JSON.load(json_file)
44
- rescue StandardError => e
45
- # For backwards compatibility, forego the exception in some cases.
46
- abort_build = true
47
- if ['translations', 'indicator_downloads', 'disaggregation', 'data_packages'].include? key
48
- abort_build = false
49
- elsif endpoint.include? '/untranslated/'
50
- abort_build = false
51
- end
52
- if abort_build
53
- puts e.message
54
- abort 'Unable to read data from: ' + endpoint
55
- end
56
- end
57
- end
58
-
59
- return build
60
- end
61
-
62
- # Predict (before data has been fetched) whether the site is using
63
- # translated builds or not.
64
- def site_uses_translated_builds(path)
65
-
66
- is_remote = opensdg_is_path_remote(path)
67
- endpoints = get_endpoints()
68
- # For a quick test, we just use 'meta'.
69
- meta = endpoints['meta']
70
- endpoint = is_remote ? path + '/' + meta : File.join(path, fix_path(meta))
71
-
72
- begin
73
- json_file = is_remote ? open(endpoint) : File.open(endpoint)
74
- rescue StandardError => e
75
- # If we didn't find an untranslated 'meta', we assume translated builds.
76
- return true
77
- end
78
-
79
- # Other wise assume untranslated builds.
80
- return false
81
- end
82
-
83
- def generate(site)
84
-
85
- # For below, make sure there is at least an empty hash at
86
- # site.data.translations.
87
- if !site.data.has_key?('translations')
88
- site.data['translations'] = {}
89
- end
90
-
91
- remote = site.config['remote_data_prefix']
92
- local = site.config['local_data_folder']
93
-
94
- if !remote && !local
95
- abort 'Site config must include "remote_data_prefix".'
96
- end
97
-
98
- build_location = remote ? remote : local
99
- is_remote = opensdg_is_path_remote(build_location)
100
-
101
- build_location = is_remote ? build_location : File.join(Dir.pwd, build_location)
102
- translated_builds = site_uses_translated_builds(build_location)
103
-
104
- if translated_builds
105
- # For translated builds, we get a build for each language, and
106
- # place them in "subfolders" (so to speak) of site.data.
107
- subfolders = site.config['languages'].clone
108
- subfolders.append('untranslated')
109
- subfolders.each do |language|
110
- data_target = site.data[language]
111
- translated_build = is_remote ? build_location + '/' + language : File.join(build_location, language)
112
- data_source = fetch_build(translated_build)
113
- if !data_source.empty?
114
- if data_target
115
- data_target.deep_merge(data_source)
116
- else
117
- site.data[language] = data_source
118
- end
119
- end
120
- end
121
- # We move the language-specific translations to the
122
- # site.data.translations location, where all translations are kept.
123
- site.config['languages'].each do |language|
124
- translation_target = site.data['translations'][language]
125
- translation_source = site.data[language]['translations']
126
- if translation_target
127
- translation_target.deep_merge(translation_source)
128
- else
129
- site.data['translations'][language] = translation_source
130
- end
131
- end
132
- # And there are some parts of the build that don't need to be translated
133
- # and should be moved to the top level.
134
- first_language = site.config['languages'][0]
135
- site.data['reporting'] = site.data[first_language]['reporting']
136
- site.data['schema'] = site.data[first_language]['schema']
137
- site.data['zip'] = site.data[first_language]['zip']
138
- else
139
- # For untranslated builds, we download one build only, and place it
140
- # in the "root" (so to speak) of site.data. Nothing else is needed.
141
- target = site.data
142
- source = fetch_build(build_location)
143
- if !source.empty?
144
- target.deep_merge(source)
145
- end
146
- end
147
-
148
- # Finally support the deprecated 'remote_translations' option.
149
- # This is deprecated because translations should now be in the
150
- # data repository, where they will be fetched in fetch_build().
151
- if site.config['remote_translations']
152
- key = 'translations'
153
- target = site.data[key]
154
- site.config['remote_translations'].each do |endpoint|
155
- begin
156
- source = JSON.load(open(endpoint))
157
- if target
158
- target.deep_merge(source)
159
- else
160
- site.data[key] = source
161
- end
162
- rescue StandardError => e
163
- puts e.message
164
- abort 'Unable to fetch remote translation from: ' + endpoint
165
- end
166
- end
167
- end
168
- end
169
- end
170
-
171
- # This makes sure that the contents of the "local_data_folder" get copied
172
- # into the Jekyll build, so that they can be served from the website.
173
- Jekyll::Hooks.register :site, :post_write do |site|
174
- if site.config['local_data_folder']
175
- source = File.join(Dir.pwd, site.config['local_data_folder'], '.')
176
- destination = site.config['destination']
177
- FileUtils.cp_r(source, destination)
178
- # Do the same in the case that "remote_data_prefix" is being used for a local
179
- # data folder (since "local_data_folder" is deprecated and undocumented).
180
- elsif site.config['remote_data_prefix']
181
- if !opensdg_is_path_remote(site.config['remote_data_prefix'])
182
- source = File.join(Dir.pwd, site.config['remote_data_prefix'], '.')
183
- destination = site.config['destination']
184
- FileUtils.cp_r(source, destination)
185
- end
186
- end
187
- end
188
- end
1
+ require "jekyll"
2
+ require 'json'
3
+ require 'deep_merge'
4
+ require 'open-uri'
5
+ require_relative "helpers"
6
+
7
+ module JekyllOpenSdgPlugins
8
+ class FetchRemoteData < Jekyll::Generator
9
+ safe true
10
+ priority :highest
11
+
12
+ # Fix a Unix path in case we are on Windows.
13
+ def fix_path(path)
14
+ path_parts = path.split('/')
15
+ return path_parts.join(File::SEPARATOR)
16
+ end
17
+
18
+ # Our hardcoded list of pieces of the build that we expect.
19
+ def get_endpoints()
20
+ return {
21
+ 'meta' => 'meta/all.json',
22
+ 'headlines' => 'headline/all.json',
23
+ 'schema' => 'meta/schema.json',
24
+ 'reporting' => 'stats/reporting.json',
25
+ 'disaggregation' => 'stats/disaggregation.json',
26
+ 'translations' => 'translations/translations.json',
27
+ 'zip' => 'zip/all_indicators.json',
28
+ 'indicator_downloads' => 'downloads/indicator-downloads.json',
29
+ 'data_packages' => 'data-packages/all.json',
30
+ }
31
+ end
32
+
33
+ # Get a build from a local folder on disk or a remote URL on the Internet.
34
+ def fetch_build(path)
35
+
36
+ is_remote = opensdg_is_path_remote(path)
37
+ build = {}
38
+ get_endpoints().each do |key, value|
39
+ endpoint = is_remote ? path + '/' + value : File.join(path, fix_path(value))
40
+
41
+ begin
42
+ json_file = is_remote ? open(endpoint) : File.open(endpoint)
43
+ build[key] = JSON.load(json_file)
44
+ rescue StandardError => e
45
+ # For backwards compatibility, forego the exception in some cases.
46
+ abort_build = true
47
+ if ['translations', 'indicator_downloads', 'disaggregation', 'data_packages'].include? key
48
+ abort_build = false
49
+ elsif endpoint.include? '/untranslated/'
50
+ abort_build = false
51
+ end
52
+ if abort_build
53
+ puts e.message
54
+ abort 'Unable to read data from: ' + endpoint
55
+ end
56
+ end
57
+ end
58
+
59
+ return build
60
+ end
61
+
62
+ # Predict (before data has been fetched) whether the site is using
63
+ # translated builds or not.
64
+ def site_uses_translated_builds(path)
65
+
66
+ is_remote = opensdg_is_path_remote(path)
67
+ endpoints = get_endpoints()
68
+ # For a quick test, we just use 'meta'.
69
+ meta = endpoints['meta']
70
+ endpoint = is_remote ? path + '/' + meta : File.join(path, fix_path(meta))
71
+
72
+ begin
73
+ json_file = is_remote ? open(endpoint) : File.open(endpoint)
74
+ rescue StandardError => e
75
+ # If we didn't find an untranslated 'meta', we assume translated builds.
76
+ return true
77
+ end
78
+
79
+ # Other wise assume untranslated builds.
80
+ return false
81
+ end
82
+
83
+ def generate(site)
84
+
85
+ # For below, make sure there is at least an empty hash at
86
+ # site.data.translations.
87
+ if !site.data.has_key?('translations')
88
+ site.data['translations'] = {}
89
+ end
90
+
91
+ remote = site.config['remote_data_prefix']
92
+ local = site.config['local_data_folder']
93
+
94
+ if !remote && !local
95
+ abort 'Site config must include "remote_data_prefix".'
96
+ end
97
+
98
+ build_location = remote ? remote : local
99
+ is_remote = opensdg_is_path_remote(build_location)
100
+
101
+ build_location = is_remote ? build_location : File.join(Dir.pwd, build_location)
102
+ translated_builds = site_uses_translated_builds(build_location)
103
+
104
+ if translated_builds
105
+ # For translated builds, we get a build for each language, and
106
+ # place them in "subfolders" (so to speak) of site.data.
107
+ subfolders = site.config['languages'].clone
108
+ subfolders.append('untranslated')
109
+ subfolders.each do |language|
110
+ data_target = site.data[language]
111
+ translated_build = is_remote ? build_location + '/' + language : File.join(build_location, language)
112
+ data_source = fetch_build(translated_build)
113
+ if !data_source.empty?
114
+ if data_target
115
+ data_target.deep_merge(data_source)
116
+ else
117
+ site.data[language] = data_source
118
+ end
119
+ end
120
+ end
121
+ # We move the language-specific translations to the
122
+ # site.data.translations location, where all translations are kept.
123
+ site.config['languages'].each do |language|
124
+ translation_target = site.data['translations'][language]
125
+ translation_source = site.data[language]['translations']
126
+ if translation_target
127
+ translation_target.deep_merge(translation_source)
128
+ else
129
+ site.data['translations'][language] = translation_source
130
+ end
131
+ end
132
+ # And there are some parts of the build that don't need to be translated
133
+ # and should be moved to the top level.
134
+ first_language = site.config['languages'][0]
135
+ site.data['reporting'] = site.data[first_language]['reporting']
136
+ site.data['schema'] = site.data[first_language]['schema']
137
+ site.data['zip'] = site.data[first_language]['zip']
138
+ else
139
+ # For untranslated builds, we download one build only, and place it
140
+ # in the "root" (so to speak) of site.data. Nothing else is needed.
141
+ target = site.data
142
+ source = fetch_build(build_location)
143
+ if !source.empty?
144
+ target.deep_merge(source)
145
+ end
146
+ end
147
+
148
+ # Finally support the deprecated 'remote_translations' option.
149
+ # This is deprecated because translations should now be in the
150
+ # data repository, where they will be fetched in fetch_build().
151
+ if site.config['remote_translations']
152
+ key = 'translations'
153
+ target = site.data[key]
154
+ site.config['remote_translations'].each do |endpoint|
155
+ begin
156
+ source = JSON.load(open(endpoint))
157
+ if target
158
+ target.deep_merge(source)
159
+ else
160
+ site.data[key] = source
161
+ end
162
+ rescue StandardError => e
163
+ puts e.message
164
+ abort 'Unable to fetch remote translation from: ' + endpoint
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ # This makes sure that the contents of the "local_data_folder" get copied
172
+ # into the Jekyll build, so that they can be served from the website.
173
+ Jekyll::Hooks.register :site, :post_write do |site|
174
+ if site.config['local_data_folder']
175
+ source = File.join(Dir.pwd, site.config['local_data_folder'], '.')
176
+ destination = site.config['destination']
177
+ FileUtils.cp_r(source, destination)
178
+ # Do the same in the case that "remote_data_prefix" is being used for a local
179
+ # data folder (since "local_data_folder" is deprecated and undocumented).
180
+ elsif site.config['remote_data_prefix']
181
+ if !opensdg_is_path_remote(site.config['remote_data_prefix'])
182
+ source = File.join(Dir.pwd, site.config['remote_data_prefix'], '.')
183
+ destination = site.config['destination']
184
+ FileUtils.cp_r(source, destination)
185
+ end
186
+ end
187
+ end
188
+ end
@@ -1,132 +1,132 @@
1
- # Simple collection of helper functions for use in these plugins.
2
-
3
- require "jekyll"
4
-
5
- # Takes a translation key and returns a translated string according to the
6
- # language of the current page. Or if none is found, returns the original
7
- # key.
8
- def opensdg_translate_key(key, translations, language)
9
-
10
- # Safety code - abort now if key is nil.
11
- if key.nil?
12
- return ""
13
- end
14
-
15
- # Also make sure it is a string, and other just return it.
16
- if not key.is_a? String
17
- return key
18
- end
19
-
20
- # More safety code - abort now if key is empty.
21
- if key.empty?
22
- return ""
23
- end
24
-
25
- # Keep track of the last thing we drilled to.
26
- drilled = translations[language]
27
-
28
- # Keep track of how many levels we have drilled.
29
- levels_drilled = 0
30
- levels = key.split('.')
31
-
32
- # Loop through each level.
33
- levels.each do |level|
34
-
35
- # If we have drilled down to a scalar value too soon, abort.
36
- break if drilled.class != Hash
37
-
38
- if drilled.has_key? level
39
- # If we find something, continue drilling.
40
- drilled = drilled[level]
41
- levels_drilled += 1
42
- end
43
-
44
- end
45
-
46
- # If we didn't drill the right number of levels, return the
47
- # original string.
48
- if levels.length != levels_drilled
49
- return key
50
- end
51
-
52
- # Otherwise we must have drilled all they way.
53
- return drilled
54
- end
55
-
56
- # Takes a site object and decides whether it is using translated builds.
57
- def opensdg_translated_builds(site)
58
- # Assume the site is using translated builds.
59
- translated_builds = true
60
- site.config['languages'].each do |language|
61
- # If any languages don't have a key in site.data, the site is not using
62
- # translated builds.
63
- if !site.data.has_key? language
64
- translated_builds = false
65
- end
66
- end
67
- return translated_builds
68
- end
69
-
70
- # Print a notice during compilation.
71
- def opensdg_notice(message)
72
- Jekyll.logger.warn message.yellow
73
- end
74
-
75
- # Get the public language codes for a site, keyed by the actual language codes.
76
- def opensdg_languages_public(site)
77
- languages_public = site.config['languages_public']
78
-
79
- # The current structure of the setting is an array of hashes, each containing
80
- # keys for "language" and "language_public".
81
- if languages_public.is_a?(Array)
82
- converted_languages_public = Hash.new
83
- languages_public.each do |language_public|
84
- language_code = language_public['language']
85
- language_code_public = language_public['language_public']
86
- converted_languages_public[language_code] = language_code_public
87
- end
88
- return converted_languages_public
89
- end
90
-
91
- # Fallback to exactly what was retrieved from site.confg['languages_public'],
92
- # since the deprecated structure is exactly what this function wants.
93
- return languages_public
94
- end
95
-
96
- # Print notices about a validation error.
97
- def opensdg_validation_error(error)
98
- if error['type'] == 'required'
99
- missing = []
100
- error['schema']['required'].each do |required_property|
101
- unless error['data'].has_key?(required_property)
102
- message = 'Missing configuration setting: ' + required_property
103
- if error['schema'].has_key?('title')
104
- message += ' (' + error['schema']['title'] + ')'
105
- end
106
- opensdg_notice(message)
107
- end
108
- end
109
- else
110
- message = 'Validation error of type: ' + error['type']
111
- if error['schema'] && error['schema'].has_key?('title')
112
- message += ' (' + error['schema']['title'] + ')'
113
- end
114
- opensdg_notice(message)
115
- if error['schema']
116
- opensdg_notice('Expected schema:')
117
- puts error['schema'].inspect
118
- end
119
- if error['data']
120
- opensdg_notice('Actual data:')
121
- puts error['data'].inspect
122
- end
123
- end
124
- end
125
-
126
- # Is this path a remote path?
127
- def opensdg_is_path_remote(path)
128
- if path.nil?
129
- return false
130
- end
131
- return path.start_with?('http')
132
- end
1
+ # Simple collection of helper functions for use in these plugins.
2
+
3
+ require "jekyll"
4
+
5
+ # Takes a translation key and returns a translated string according to the
6
+ # language of the current page. Or if none is found, returns the original
7
+ # key.
8
+ def opensdg_translate_key(key, translations, language)
9
+
10
+ # Safety code - abort now if key is nil.
11
+ if key.nil?
12
+ return ""
13
+ end
14
+
15
+ # Also make sure it is a string, and other just return it.
16
+ if not key.is_a? String
17
+ return key
18
+ end
19
+
20
+ # More safety code - abort now if key is empty.
21
+ if key.empty?
22
+ return ""
23
+ end
24
+
25
+ # Keep track of the last thing we drilled to.
26
+ drilled = translations[language]
27
+
28
+ # Keep track of how many levels we have drilled.
29
+ levels_drilled = 0
30
+ levels = key.split('.')
31
+
32
+ # Loop through each level.
33
+ levels.each do |level|
34
+
35
+ # If we have drilled down to a scalar value too soon, abort.
36
+ break if drilled.class != Hash
37
+
38
+ if drilled.has_key? level
39
+ # If we find something, continue drilling.
40
+ drilled = drilled[level]
41
+ levels_drilled += 1
42
+ end
43
+
44
+ end
45
+
46
+ # If we didn't drill the right number of levels, return the
47
+ # original string.
48
+ if levels.length != levels_drilled
49
+ return key
50
+ end
51
+
52
+ # Otherwise we must have drilled all they way.
53
+ return drilled
54
+ end
55
+
56
+ # Takes a site object and decides whether it is using translated builds.
57
+ def opensdg_translated_builds(site)
58
+ # Assume the site is using translated builds.
59
+ translated_builds = true
60
+ site.config['languages'].each do |language|
61
+ # If any languages don't have a key in site.data, the site is not using
62
+ # translated builds.
63
+ if !site.data.has_key? language
64
+ translated_builds = false
65
+ end
66
+ end
67
+ return translated_builds
68
+ end
69
+
70
+ # Print a notice during compilation.
71
+ def opensdg_notice(message)
72
+ Jekyll.logger.warn message.yellow
73
+ end
74
+
75
+ # Get the public language codes for a site, keyed by the actual language codes.
76
+ def opensdg_languages_public(site)
77
+ languages_public = site.config['languages_public']
78
+
79
+ # The current structure of the setting is an array of hashes, each containing
80
+ # keys for "language" and "language_public".
81
+ if languages_public.is_a?(Array)
82
+ converted_languages_public = Hash.new
83
+ languages_public.each do |language_public|
84
+ language_code = language_public['language']
85
+ language_code_public = language_public['language_public']
86
+ converted_languages_public[language_code] = language_code_public
87
+ end
88
+ return converted_languages_public
89
+ end
90
+
91
+ # Fallback to exactly what was retrieved from site.confg['languages_public'],
92
+ # since the deprecated structure is exactly what this function wants.
93
+ return languages_public
94
+ end
95
+
96
+ # Print notices about a validation error.
97
+ def opensdg_validation_error(error)
98
+ if error['type'] == 'required'
99
+ missing = []
100
+ error['schema']['required'].each do |required_property|
101
+ unless error['data'].has_key?(required_property)
102
+ message = 'Missing configuration setting: ' + required_property
103
+ if error['schema'].has_key?('title')
104
+ message += ' (' + error['schema']['title'] + ')'
105
+ end
106
+ opensdg_notice(message)
107
+ end
108
+ end
109
+ else
110
+ message = 'Validation error of type: ' + error['type']
111
+ if error['schema'] && error['schema'].has_key?('title')
112
+ message += ' (' + error['schema']['title'] + ')'
113
+ end
114
+ opensdg_notice(message)
115
+ if error['schema']
116
+ opensdg_notice('Expected schema:')
117
+ puts error['schema'].inspect
118
+ end
119
+ if error['data']
120
+ opensdg_notice('Actual data:')
121
+ puts error['data'].inspect
122
+ end
123
+ end
124
+ end
125
+
126
+ # Is this path a remote path?
127
+ def opensdg_is_path_remote(path)
128
+ if path.nil?
129
+ return false
130
+ end
131
+ return path.start_with?('http')
132
+ end