jekyll-theme-rop 2.1.15 → 2.1.17

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: f627e9c8cae1ceba4fe1018fe70cb1dd6673dc5fd8b231e2b1d0280d855cb8b6
4
- data.tar.gz: b84293b7c2075ccf37c808fe6941029712ada0bd3ffdb155a80f858b5bb9117c
3
+ metadata.gz: 1bee81c0a7fca76215c0427c144461bce8404e4723dda476216d244b330a53f0
4
+ data.tar.gz: ef28333597b6ec3a2fc2d54dbfbb314bd3f6a05fbc4dafe78fa427a81e240e35
5
5
  SHA512:
6
- metadata.gz: e928008a554403ab643ca0a65c02b2453f94afde4a3491b42b48f6d9e3c4bb9a3d05565c6e2e4b162eea4ccf1bbf7095b2a2a6a3db058103830c9934fd77d3a7
7
- data.tar.gz: 94d7198cdbcc0f01b40492bcbd33cd85e596106632907bbd2dbebe54300e720edf97174cc8c3e046b67e6c9f3ca0808922f1f294c7ace9945d2a8cdc2836cee0
6
+ metadata.gz: e07a18a8fb56e118df8d889105ef35eb95e232c597c1d4888a48167403901c4d952354221686f59a60cbf5e88626fae119b47775a8f373516d5929add8f6ab59
7
+ data.tar.gz: a93000ecfc68eb341933e9e70f28e11325d6510e0fa7e0b88813ea97e2e80c27a88ad288bbb6137b0c0a4e4298947e6cff7d48c8851e4c8cb876e4429f7e2dd7
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ # require "rspec/core/rake_task"
5
+
6
+ # Uncomment to enable the "spec" task
7
+ # RSpec::Core::RakeTask.new(:spec)
8
+ #
9
+ # task :default => :spec
@@ -0,0 +1,7 @@
1
+ module Jekyll
2
+ module Theme
3
+ module Rop
4
+ VERSION = '2.1.17'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Compatibility file for old name
4
+ require_relative 'jekyll-theme-rop'
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Theme
5
+ module Rop
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative 'jekyll/theme/rop/version'
11
+
12
+ puts '[jekyll-theme-rop] Loaded.'
13
+
14
+ require 'jekyll'
15
+ require_relative 'rop'
16
+
17
+ Jekyll::Hooks.register :site, :after_init do |site|
18
+ site.reader = Rop::ProjectReader.new(site) if site.theme # TODO: Check theme name
19
+ end
20
+
21
+ # This is a fix for static files
22
+ require 'fileutils'
23
+ Jekyll::Hooks.register :pages, :post_write do |page|
24
+ if (page.path == 'robots.txt') || (page.path == 'sitemap.xml')
25
+ File.write(page.site.in_dest_dir(page.path), page.content,
26
+ mode: 'wb')
27
+ end
28
+ end
data/lib/rop/_plugins ADDED
@@ -0,0 +1 @@
1
+ _plugins/
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/md5'
4
+
5
+ module Rop
6
+ #
7
+ # Adds a variable holding the array of posts of open hub blog
8
+ # and from each individual project blog, combined and sorted by date.
9
+ #
10
+ # It also does some processing on the posts
11
+ # as required by the Open Project theme.
12
+ #
13
+ class CombinedPostArrayGenerator < ::Jekyll::Generator
14
+ safe true
15
+
16
+ def generate(site)
17
+ site_posts = site.posts.docs
18
+
19
+ if site.config['is_hub']
20
+ # Get documents representing projects
21
+ projects = site.collections['projects'].docs.select do |item|
22
+ pieces = item.url.split('/')
23
+ pieces.length == 4 && pieces[-1] == 'index' && pieces[1] == 'projects'
24
+ end
25
+ # Add project name (matches directory name, may differ from title)
26
+ projects = projects.map do |project|
27
+ project.data['name'] = project.url.split('/')[2]
28
+ project
29
+ end
30
+
31
+ # Get documents representnig posts from each project’s blog
32
+ project_posts = site.collections['projects'].docs.select { |item| item.url.include? '_posts' }
33
+
34
+ # Add parent project’s data hash onto each
35
+ project_posts = project_posts.map do |post|
36
+ project_name = post.url.split('/')[2]
37
+ post.data['parent_project'] = projects.detect { |p| p.data['name'] == project_name }
38
+ post.content = ''
39
+ post
40
+ end
41
+
42
+ posts_combined = (project_posts + site_posts)
43
+
44
+ else
45
+ posts_combined = site_posts
46
+
47
+ end
48
+
49
+ # On each post, replace authors’ emails with corresponding md5 hashes
50
+ # suitable for hotlinking authors’ Gravatar profile pictures.
51
+ posts_combined = posts_combined.sort_by(&:date).reverse.map do |post|
52
+ process_author(post.data['author']) if post.data.key? 'author'
53
+
54
+ if post.data.key? 'authors'
55
+ post.data['authors'].map do |author|
56
+ process_author(author)
57
+ end
58
+ end
59
+
60
+ post
61
+ end
62
+
63
+ # Make combined blog post array available site-wide
64
+ site.config['posts_combined'] = posts_combined
65
+ site.config['num_posts_combined'] = posts_combined.size
66
+ end
67
+
68
+ private
69
+
70
+ def process_author(author)
71
+ email = author['email']
72
+ hash = Digest::MD5.hexdigest(email)
73
+ author['email'] = hash
74
+ author['plaintext_email'] = email
75
+ author
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rop
4
+ # On an open hub site, Jekyll Open Project theme assumes the existence of two types
5
+ # of item indexes: software and specs, where items are gathered
6
+ # from across open projects in the hub.
7
+ #
8
+ # The need for :item_test arises from our data structure (see Jekyll Open Project theme docs)
9
+ # and the fact that Jekyll doesn’t intuitively handle nested collections.
10
+ INDEXES = {
11
+ 'software' => {
12
+ item_test: ->(item) { item.path.include? '/_software' and !item.path.include? '/docs' }
13
+ },
14
+ 'specs' => {
15
+ item_test: ->(item) { item.path.include? '/_specs' and !item.path.include? '/docs' }
16
+ }
17
+ }.freeze
18
+
19
+ # Below passes the `items` variable to normal (unfiltered)
20
+ # index page layout.
21
+
22
+ class IndexPageGenerator < ::Jekyll::Generator
23
+ safe true
24
+
25
+ def generate(site)
26
+ site.config['max_featured_software'] = 3
27
+ site.config['max_featured_specs'] = 3
28
+ site.config['max_featured_posts'] = 3
29
+
30
+ INDEXES.each do |index_name, params|
31
+ collection_name = if site.config['is_hub']
32
+ 'projects'
33
+ else
34
+ index_name
35
+ end
36
+
37
+ next unless site.collections.key? collection_name
38
+
39
+ # Filters items from given collection_name through item_test function
40
+ # and makes items available in templates via e.g. site.all_specs, site.all_software
41
+
42
+ items = get_all_items(site, collection_name, params[:item_test])
43
+
44
+ site.config["one_#{index_name}"] = items[0] if items.length == 1
45
+
46
+ site.config["all_#{index_name}"] = items
47
+ site.config["num_all_#{index_name}"] = items.size
48
+
49
+ featured_items = items.reject { |item| item.data['feature_with_priority'].nil? }
50
+ site.config["featured_#{index_name}"] = featured_items.sort_by { |item| item.data['feature_with_priority'] }
51
+ site.config["num_featured_#{index_name}"] = featured_items.size
52
+
53
+ non_featured_items = items.select { |item| item.data['feature_with_priority'].nil? }
54
+ site.config["non_featured_#{index_name}"] = non_featured_items
55
+ site.config["num_non_featured_#{index_name}"] = non_featured_items.size
56
+ end
57
+ end
58
+
59
+ def get_all_items(site, collection_name, filter_func)
60
+ # Fetches items of specified type, ordered and prepared for usage in index templates
61
+
62
+ collection = site.collections[collection_name]
63
+
64
+ raise "Collection does not exist: #{collection_name}" if collection.nil?
65
+
66
+ items = collection.docs.select do |item|
67
+ filter_func.call(item)
68
+ end
69
+
70
+ default_time = Time.new(1989, 12, 31, 0, 0, 0, '+00:00')
71
+
72
+ items.sort! do |i1, i2|
73
+ val1 = i1.data.fetch('last_update', default_time) || default_time
74
+ val2 = i2.data.fetch('last_update', default_time) || default_time
75
+ (val2 <=> val1) || 0
76
+ end
77
+
78
+ if site.config['is_hub']
79
+ items.map! do |item|
80
+ project_name = item.url.split('/')[2]
81
+ project_path = "_projects/#{project_name}/index.md"
82
+
83
+ item.data['project_name'] = project_name
84
+ item.data['project_data'] = site.collections['projects'].docs.select do |proj|
85
+ proj.path.end_with? project_path
86
+ end [0]
87
+
88
+ item
89
+ end
90
+ end
91
+
92
+ items
93
+ end
94
+ end
95
+ # Each software or spec item can have its tags,
96
+ # and the theme allows to filter each index by a tag.
97
+ # The below generates an additional index page
98
+ # for each tag in an index, like software/Ruby.
99
+ #
100
+ # Note: this expects "_pages/<index page>.html" to be present in site source,
101
+ # so it would fail if theme setup instructions were not followed fully.
102
+
103
+ class FilteredIndexPage < ::Jekyll::Page
104
+ def initialize(site, base, dir, tag, items, index_page)
105
+ @site = site
106
+ @base = base
107
+ @dir = dir
108
+ @name = 'index.html'
109
+
110
+ process(@name)
111
+ read_yaml(File.join(base, '_pages'), "#{index_page}.html")
112
+ data['tag'] = tag
113
+ data['items'] = items
114
+ end
115
+ end
116
+
117
+ class FilteredIndexPageGenerator < IndexPageGenerator
118
+ safe true
119
+
120
+ def generate(site)
121
+ INDEXES.each do |index_name, params|
122
+ collection_name = if site.config['is_hub']
123
+ 'projects'
124
+ else
125
+ index_name
126
+ end
127
+
128
+ items = get_all_items(site, collection_name, params[:item_test])
129
+
130
+ # Creates a data structure like { tag1: [item1, item2], tag2: [item2, item3] }
131
+ tags = {}
132
+ items.each do |item|
133
+ (item.data['tags'] or []).each do |tag|
134
+ tags[tag] = [] unless tags.key? tag
135
+ tags[tag].push(item)
136
+ end
137
+ end
138
+
139
+ # Creates a filtered index page for each tag
140
+ tags.each do |tag, tagged_items|
141
+ site.pages << FilteredIndexPage.new(
142
+ site,
143
+ site.source,
144
+ # The filtered page will be nested under /<index page>/<tag>.html
145
+ File.join(index_name, tag),
146
+ tag,
147
+ tagged_items,
148
+ index_name
149
+ )
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,31 @@
1
+ {% comment %}Page stub.{% endcomment %}
2
+
3
+ <div id="diagramContainer" style="height: 400px"></div>
4
+
5
+ <style>
6
+ .leaflet-container {
7
+ background-color: transparent;
8
+ }
9
+ .documentation.with-expandable-toc > article {
10
+ max-width: none;
11
+ }
12
+ </style>
13
+
14
+ <script>
15
+ (function () {
16
+ var map = L.map('diagramContainer', {
17
+ crs: L.CRS.Simple,
18
+ attributionControl: false,
19
+ scrollWheelZoom: false,
20
+ zoomControl: false,
21
+ minZoom: -10,
22
+ }).
23
+ setView([{{ page.image_height }}/2, {{ page.image_width }}/2]);
24
+
25
+ map.addControl(L.control.zoom({ position: 'bottomleft' }));
26
+
27
+ var bounds = [[0,0], [{{ page.image_height }}, {{ page.image_width }}]];
28
+ var image = L.imageOverlay('{{ page.image_path }}', bounds).addTo(map);
29
+ map.fitBounds(bounds);
30
+ }());
31
+ </script>
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rop
4
+ class PngDiagramPage < ::Jekyll::Page
5
+ EXTRA_STYLESHEETS = [{
6
+ 'href' => 'https://unpkg.com/leaflet@1.3.4/dist/leaflet.css',
7
+ 'integrity' => 'sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA==',
8
+ 'crossorigin' => ''
9
+ }].freeze
10
+
11
+ EXTRA_SCRIPTS = [{
12
+ 'src' => 'https://unpkg.com/leaflet@1.3.4/dist/leaflet.js',
13
+ 'integrity' => 'sha512-nMMmRyTVoLYqjP9hrbed9S+FzjZHW5gY1TWCHA5ckwXZBadntCNs8kEqAWdrb9O7rxbCaA4lKTIWjDXZxflOcA==',
14
+ 'crossorigin' => ''
15
+ }].freeze
16
+
17
+ def initialize(site, base, dir, data)
18
+ @site = site
19
+ @base = base
20
+ @dir = dir
21
+ @name = 'index.html'
22
+
23
+ process(@name)
24
+ self.data ||= data
25
+
26
+ self.data['extra_stylesheets'] = EXTRA_STYLESHEETS
27
+ self.data['extra_scripts'] = EXTRA_SCRIPTS
28
+ self.data['layout'] = 'spec'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,373 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'git'
5
+ require 'jekyll-data/reader'
6
+
7
+ module Rop
8
+ DEFAULT_DOCS_SUBTREE = 'docs'
9
+ DEFAULT_REPO_REMOTE_NAME = 'origin'
10
+ DEFAULT_REPO_BRANCH = 'main'
11
+ # Can be overridden by default_repo_branch in site config.
12
+ # Used by shallow_git_checkout.
13
+
14
+ class NonLiquidDocument < ::Jekyll::Document
15
+ def render_with_liquid?
16
+ false
17
+ end
18
+ end
19
+
20
+ class CollectionDocReader < ::Jekyll::DataReader
21
+ def read(dir, collection)
22
+ read_project_subdir(dir, collection)
23
+ end
24
+
25
+ def read_project_subdir(dir, collection, nested: false)
26
+ return unless File.directory?(dir) && !@entry_filter.symlink?(dir)
27
+
28
+ entries = Dir.chdir(dir) do
29
+ Dir['*.{adoc,md,markdown,html,svg,png}'] + Dir['*'].select { |fn| File.directory?(fn) }
30
+ end
31
+
32
+ entries.each do |entry|
33
+ path = File.join(dir, entry)
34
+
35
+ Jekyll.logger.debug('OPF:', "Reading entry #{path}")
36
+
37
+ if File.directory?(path)
38
+ read_project_subdir(path, collection, nested: true)
39
+
40
+ elsif nested || (File.basename(entry, '.*') != 'index')
41
+ ext = File.extname(path)
42
+ if ['.adoc', '.md', '.markdown'].include? ext
43
+ doc = NonLiquidDocument.new(path, site: @site, collection: collection)
44
+ doc.read
45
+
46
+ # Add document to Jekyll document database if it refers to software or spec
47
+ # (as opposed to be some random nested document within repository source, like a README)
48
+ doc_url_parts = doc.url.split('/')
49
+ Jekyll.logger.debug('OPF:',
50
+ "Reading document in collection #{collection.label} with URL #{doc.url} (#{doc_url_parts.size} parts)")
51
+ if (collection.label != 'projects') || (doc_url_parts.size == 5)
52
+ Jekyll.logger.debug('OPF:', "Adding document with URL: #{doc.url}")
53
+ collection.docs << doc
54
+ else
55
+ Jekyll.logger.debug('OPF:',
56
+ "Did NOT add document with URL (possibly nesting level doesn’t match): #{doc.url}")
57
+ end
58
+ else
59
+ Jekyll.logger.debug('OPF:', "Adding static file: #{path}")
60
+ collection.files << ::Jekyll::StaticFile.new(
61
+ @site,
62
+ @site.source,
63
+ Pathname.new(File.dirname(path)).relative_path_from(Pathname.new(@site.source)).to_s,
64
+ File.basename(path),
65
+ collection
66
+ )
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ #
74
+ # Below deals with fetching each open project’s data from its site’s repo
75
+ # (such as posts, template includes, software and specs)
76
+ # and reading it into 'projects' collection docs.
77
+ #
78
+
79
+ class ProjectReader < ::JekyllData::Reader
80
+ # TODO: Switch to @site.config?
81
+ @@siteconfig = Jekyll.configuration({})
82
+
83
+ def read
84
+ super
85
+ if @site.config['is_hub']
86
+ fetch_and_read_projects
87
+ else
88
+ fetch_and_read_software('software')
89
+ fetch_and_read_specs('specs', build_pages: true)
90
+ fetch_hub_logo
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def fetch_hub_logo
97
+ return unless @site.config.key?('parent_hub') && @site.config['parent_hub'].key?('git_repo_url')
98
+
99
+ git_shallow_checkout(
100
+ File.join(@site.source, 'parent-hub'),
101
+ @site.config['parent_hub']['git_repo_url'],
102
+ ['assets', 'title.html'],
103
+ @site.config['parent_hub']['git_repo_branch']
104
+ )
105
+ end
106
+
107
+ def fetch_and_read_projects
108
+ project_indexes = @site.collections['projects'].docs.select do |doc|
109
+ pieces = doc.id.split('/')
110
+ pieces.length == 4 and pieces[1] == 'projects' and pieces[3] == 'index'
111
+ end
112
+ project_indexes.each do |project|
113
+ project_path = project.path.split('/')[0..-2].join('/')
114
+
115
+ git_shallow_checkout(
116
+ project_path,
117
+ project['site']['git_repo_url'],
118
+ %w[assets _posts _software _specs],
119
+ project['site']['git_repo_branch']
120
+ )
121
+
122
+ Jekyll.logger.debug('OPF:', "Reading files in project #{project_path}")
123
+
124
+ CollectionDocReader.new(site).read(
125
+ project_path,
126
+ @site.collections['projects']
127
+ )
128
+
129
+ fetch_and_read_software('projects')
130
+ fetch_and_read_specs('projects')
131
+ end
132
+ end
133
+
134
+ def build_and_read_spec_pages(collection_name, index_doc, build_pages: false)
135
+ spec_config = extract_spec_config(index_doc)
136
+ repo_checkout = git_shallow_checkout(
137
+ spec_config[:checkout_path],
138
+ spec_config[:repo_url],
139
+ [spec_config[:repo_subtree]],
140
+ spec_config[:repo_branch]
141
+ )
142
+
143
+ return unless repo_checkout[:success]
144
+
145
+ build_spec_pages(collection_name, index_doc, spec_config) if build_pages
146
+
147
+ index_doc.merge_data!({ 'last_update' => repo_checkout[:modified_at] })
148
+ end
149
+
150
+ def extract_spec_config(index_doc)
151
+ item_name = index_doc.id.split('/')[-1]
152
+ src = index_doc.data['spec_source']
153
+ build = src['build']
154
+
155
+ spec_checkout_path = "#{index_doc.path.split('/')[0..-2].join('/')}/#{item_name}"
156
+ spec_root = src['git_repo_subtree'] ? "#{spec_checkout_path}/#{src['git_repo_subtree']}" : spec_checkout_path
157
+
158
+ {
159
+ item_name: item_name,
160
+ repo_url: src['git_repo_url'],
161
+ repo_subtree: src['git_repo_subtree'],
162
+ repo_branch: src['git_repo_branch'],
163
+ engine: build['engine'],
164
+ engine_opts: build['options'] || {},
165
+ checkout_path: spec_checkout_path,
166
+ spec_root: spec_root
167
+ }
168
+ end
169
+
170
+ def build_spec_pages(collection_name, index_doc, spec_config)
171
+ builder = Rop::SpecBuilder.new(
172
+ @site,
173
+ index_doc,
174
+ spec_config[:spec_root],
175
+ "specs/#{spec_config[:item_name]}",
176
+ spec_config[:engine],
177
+ spec_config[:engine_opts]
178
+ )
179
+
180
+ builder.build
181
+ builder.built_pages.each do |page|
182
+ @site.pages << page
183
+ end
184
+
185
+ CollectionDocReader.new(site).read(
186
+ spec_config[:checkout_path],
187
+ @site.collections[collection_name]
188
+ )
189
+ end
190
+
191
+ def fetch_and_read_specs(collection_name, build_pages: false)
192
+ # collection_name would be either specs or (for hub site) projects
193
+
194
+ Jekyll.logger.debug('OPF:', "Fetching specs for items in collection #{collection_name} (if it exists)")
195
+
196
+ return unless @site.collections.key?(collection_name)
197
+
198
+ Jekyll.logger.debug('OPF:', "Fetching specs for items in collection #{collection_name}")
199
+
200
+ # Get spec entry points
201
+ entry_points = @site.collections[collection_name].docs.select do |doc|
202
+ doc.data['spec_source']
203
+ end
204
+
205
+ if entry_points.empty?
206
+ Jekyll.logger.info('OPF:',
207
+ "Fetching specs for items in collection #{collection_name}: No entry points")
208
+ end
209
+
210
+ entry_points.each do |index_doc|
211
+ Jekyll.logger.debug('OPF:', "Fetching specs: entry point #{index_doc.id} in collection #{collection_name}")
212
+ build_and_read_spec_pages(collection_name, index_doc, build_pages: build_pages)
213
+ end
214
+ end
215
+
216
+ def fetch_and_read_software(collection_name)
217
+ # collection_name would be either software or (for hub site) projects
218
+
219
+ Jekyll.logger.debug('OPF:', "Fetching software for items in collection #{collection_name} (if it exists)")
220
+
221
+ return unless @site.collections.key?(collection_name)
222
+
223
+ Jekyll.logger.debug('OPF:', "Fetching software for items in collection #{collection_name}")
224
+
225
+ entry_points = @site.collections[collection_name].docs.select do |doc|
226
+ doc.data['repo_url']
227
+ end
228
+
229
+ if entry_points.empty?
230
+ Jekyll.logger.info('OPF:',
231
+ "Fetching software for items in collection #{collection_name}: No entry points")
232
+ end
233
+
234
+ entry_points.each do |index_doc|
235
+ item_name = index_doc.id.split('/')[-1]
236
+ Jekyll.logger.debug('OPF:', "Fetching software: entry point #{index_doc.id} in collection #{collection_name}")
237
+
238
+ docs = index_doc.data['docs']
239
+ main_repo = index_doc.data['repo_url']
240
+ main_repo_branch = index_doc.data['repo_branch']
241
+
242
+ sw_docs_repo = (docs['git_repo_url'] if docs) || main_repo
243
+ sw_docs_subtree = (docs['git_repo_subtree'] if docs) || DEFAULT_DOCS_SUBTREE
244
+ sw_docs_branch = (docs['git_repo_branch'] if docs) || main_repo_branch
245
+
246
+ docs_path = "#{index_doc.path.split('/')[0..-2].join('/')}/#{item_name}"
247
+
248
+ sw_docs_checkout = git_shallow_checkout(docs_path, sw_docs_repo, [sw_docs_subtree], sw_docs_branch)
249
+
250
+ if sw_docs_checkout[:success]
251
+ CollectionDocReader.new(site).read(
252
+ docs_path,
253
+ @site.collections[collection_name]
254
+ )
255
+ end
256
+
257
+ # Get last repository modification timestamp.
258
+ # Fetch the repository for that purpose,
259
+ # unless it’s the same as the repo where docs are.
260
+ if !sw_docs_checkout[:success] || (sw_docs_repo != main_repo)
261
+ repo_path = "#{index_doc.path.split('/')[0..-2].join('/')}/_#{item_name}_repo"
262
+ repo_checkout = git_shallow_checkout(repo_path, main_repo, [], main_repo_branch)
263
+ index_doc.merge_data!({ 'last_update' => repo_checkout[:modified_at] })
264
+ else
265
+ index_doc.merge_data!({ 'last_update' => sw_docs_checkout[:modified_at] })
266
+ end
267
+ end
268
+ end
269
+
270
+ def git_shallow_checkout(repo_path, remote_url, sparse_subtrees, branch_name)
271
+ # Returns hash with timestamp of latest repo commit
272
+ # and boolean signifying whether new repo has been initialized
273
+ # in the process of pulling the data.
274
+
275
+ newly_initialized = false
276
+ repo = nil
277
+
278
+ git_dir = File.join(repo_path, '.git')
279
+ git_info_dir = File.join(git_dir, 'info')
280
+ git_sparse_checkout_file = File.join(git_dir, 'info', 'sparse-checkout')
281
+ if File.exist? git_dir
282
+ repo = Git.open(repo_path)
283
+
284
+ else
285
+ newly_initialized = true
286
+
287
+ repo = Git.init(repo_path)
288
+
289
+ repo.config(
290
+ 'core.sshCommand',
291
+ 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
292
+ )
293
+
294
+ repo.add_remote(DEFAULT_REPO_REMOTE_NAME, remote_url)
295
+
296
+ if sparse_subtrees.size.positive?
297
+ repo.config('core.sparseCheckout', true)
298
+
299
+ FileUtils.mkdir_p git_info_dir
300
+ File.open(git_sparse_checkout_file, 'a') do |f|
301
+ sparse_subtrees.each { |path| f << "#{path}\n" }
302
+ end
303
+ end
304
+
305
+ end
306
+
307
+ refresh_condition = @@siteconfig['refresh_remote_data'] || 'last-resort'
308
+ repo_branch = branch_name || @@siteconfig['default_repo_branch'] || DEFAULT_REPO_BRANCH
309
+
310
+ raise 'Invalid refresh_remote_data value in site’s _config.yml!' unless %w[always last-resort
311
+ skip].include?(refresh_condition)
312
+
313
+ if refresh_condition == 'always'
314
+ repo.fetch(DEFAULT_REPO_REMOTE_NAME, { depth: 1 })
315
+ repo.reset_hard
316
+ repo.checkout("#{DEFAULT_REPO_REMOTE_NAME}/#{repo_branch}", { f: true })
317
+
318
+ elsif refresh_condition == 'last-resort'
319
+ # This is the default case.
320
+
321
+ begin
322
+ # Let’s try in case this repo has been fetched before (this would never be the case on CI though)
323
+ repo.checkout("#{DEFAULT_REPO_REMOTE_NAME}/#{repo_branch}", { f: true })
324
+ rescue StandardError => e
325
+ if is_sparse_checkout_error(e, sparse_subtrees)
326
+ # Silence errors caused by nonexistent sparse checkout directories
327
+ return {
328
+ success: false,
329
+ newly_initialized: nil,
330
+ modified_at: nil
331
+ }
332
+ else
333
+ # In case of any other error, presume repo has not been fetched and do that now.
334
+ Jekyll.logger.debug('OPF:', "Fetching & checking out #{remote_url} for #{repo_path}")
335
+ repo.fetch(DEFAULT_REPO_REMOTE_NAME, { depth: 1 })
336
+ begin
337
+ # Try checkout again
338
+ repo.checkout("#{DEFAULT_REPO_REMOTE_NAME}/#{repo_branch}", { f: true })
339
+ rescue StandardError => e
340
+ raise e unless is_sparse_checkout_error(e, sparse_subtrees)
341
+
342
+ # Again, silence an error caused by nonexistent sparse checkout directories…
343
+ return {
344
+ success: false,
345
+ newly_initialized: nil,
346
+ modified_at: nil
347
+ }
348
+
349
+ # but this time throw any other error.
350
+ end
351
+ end
352
+ end
353
+ end
354
+
355
+ latest_commit = repo.gcommit('HEAD')
356
+
357
+ {
358
+ success: true,
359
+ newly_initialized: newly_initialized,
360
+ modified_at: latest_commit.date
361
+ }
362
+ end
363
+
364
+ def is_sparse_checkout_error(err, subtrees)
365
+ if err.message.include? 'Sparse checkout leaves no entry on working directory'
366
+ Jekyll.logger.debug('OPF: It looks like sparse checkout of these directories failed:', subtrees.to_s)
367
+ true
368
+ else
369
+ false
370
+ end
371
+ end
372
+ end
373
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rop
4
+ #
5
+ # Infers from available content whether the site is a hub
6
+ # or individual project site, and adds site-wide config variable
7
+ # accessible as {{ site.is_hub }} in Liquid.
8
+ #
9
+ class SiteTypeVariableGenerator < Jekyll::Generator
10
+ def generate(site)
11
+ site.config['is_hub'] = hub_site?(site)
12
+ end
13
+
14
+ private
15
+
16
+ # If there’re projects defined, we assume it is indeed
17
+ # a Jekyll Open Project hub site.
18
+ def hub_site?(site)
19
+ projects = site.collections['projects']
20
+ projects&.docs&.any?
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'fastimage'
5
+ require_relative 'png_diagram_page'
6
+
7
+ module Rop
8
+ class SpecBuilder
9
+ attr_reader :built_pages
10
+
11
+ def initialize(site, spec_index_doc, spec_source_base, spec_out_base, engine, opts)
12
+ require_relative engine
13
+
14
+ @site = site
15
+ @spec_index_doc = spec_index_doc
16
+ @spec_source_base = spec_source_base
17
+ @spec_out_base = spec_out_base
18
+ @opts = opts
19
+
20
+ @built_pages = []
21
+ end
22
+
23
+ def build
24
+ @built_pages = build_spec_pages(
25
+ @site,
26
+ @spec_index_doc,
27
+ @spec_source_base,
28
+ @spec_out_base,
29
+ @opts
30
+ )
31
+ end
32
+
33
+ def build_spec_pages(site, spec_info, source, dest, _opts)
34
+ nav_items = get_nav_items_with_path(
35
+ spec_info.data['navigation']['items']
36
+ )
37
+
38
+ pages, not_found_items = process_spec_images(site, source, nav_items,
39
+ dest, spec_info)
40
+
41
+ not_found_items.each do |item|
42
+ warn "SPECIFIED PNG NOT FOUND: #{item['title']}.png not found " \
43
+ "at source as specified at (#{dest})."
44
+ end
45
+
46
+ pages
47
+ end
48
+
49
+ # Recursively go through given list of nav_items, including any nested items,
50
+ # and return a flat array containing navigation items with path specified.
51
+ def get_nav_items_with_path(nav_items)
52
+ items_with_path = []
53
+
54
+ nav_items.each do |item|
55
+ items_with_path.push(item) if item['path']
56
+
57
+ items_with_path.concat(get_nav_items_with_path(item['items'])) if item['items']
58
+ end
59
+
60
+ items_with_path
61
+ end
62
+
63
+ def process_spec_images(site, source, nav_items, dest, spec_info)
64
+ pages = []
65
+ not_found_items = nav_items.dup
66
+
67
+ Dir.glob("#{source}/*.png") do |pngfile|
68
+ png_name = File.basename(pngfile)
69
+ png_name_noext = File.basename(png_name, File.extname(png_name))
70
+
71
+ nav_item = find_nav_items(nav_items, png_name_noext)[0].clone
72
+
73
+ if nav_item.nil?
74
+ warn "UNUSED PNG: #{File.basename(pngfile)} detected at source " \
75
+ "without a corresponding navigation item at (#{dest})."
76
+ next
77
+ end
78
+
79
+ not_found_items.delete_if { |item| item['title'] == nav_item['title'] }
80
+
81
+ data = build_spec_page_data(pngfile, dest, png_name, nav_item,
82
+ spec_info)
83
+
84
+ pages << build_spec_page(site, dest, png_name_noext, data)
85
+ end
86
+
87
+ [pages, not_found_items]
88
+ end
89
+
90
+ def find_nav_items(diagram_nav_items, png_name_noext)
91
+ diagram_nav_items.select do |item|
92
+ item['path'].start_with?(png_name_noext)
93
+ end
94
+ end
95
+
96
+ def build_spec_page(site, spec_root, png_name_noext, data)
97
+ page = PngDiagramPage.new(
98
+ site,
99
+ site.source,
100
+ File.join(spec_root, png_name_noext),
101
+ data
102
+ )
103
+
104
+ stub_path = "#{File.dirname(__FILE__)}/png_diagram.html"
105
+ page.content = File.read(stub_path)
106
+
107
+ page
108
+ end
109
+
110
+ def build_spec_page_data(pngfile, spec_root, png_name, nav_item, spec_info)
111
+ data = fill_image_data(pngfile, spec_info, spec_root, png_name)
112
+ .merge(nav_item)
113
+
114
+ data['title'] = "#{spec_info['title']}: #{nav_item['title']}"
115
+ data['article_header_title'] = nav_item['title'].to_s
116
+
117
+ data
118
+ end
119
+
120
+ def fill_image_data(pngfile, spec_info, spec_root, png_name)
121
+ png_dimensions = FastImage.size(pngfile)
122
+ data = spec_info.data.clone
123
+ data['image_path'] = "/#{spec_root}/images/#{png_name}"
124
+ data['image_width'] = png_dimensions[0]
125
+ data['image_height'] = png_dimensions[1]
126
+ data
127
+ end
128
+ end
129
+ end
data/lib/rop.rb ADDED
@@ -0,0 +1,5 @@
1
+ require_relative 'rop/site_type'
2
+ require_relative 'rop/project_reader'
3
+ require_relative 'rop/filterable_index'
4
+ require_relative 'rop/blog_index'
5
+ require_relative 'rop/spec_builder'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-theme-rop
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.15
4
+ version: 2.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -187,6 +187,7 @@ extra_rdoc_files: []
187
187
  files:
188
188
  - LICENSE.txt
189
189
  - README.md
190
+ - Rakefile
190
191
  - _config.yml
191
192
  - _data/placeholder.yml
192
193
  - _includes/_nav-item.html
@@ -250,6 +251,18 @@ files:
250
251
  - assets/js/headroom.min.js
251
252
  - assets/js/opf.js
252
253
  - assets/listing-widget.js
254
+ - lib/jekyll-theme-open-project.rb
255
+ - lib/jekyll-theme-rop.rb
256
+ - lib/jekyll/theme/rop/version.rb
257
+ - lib/rop.rb
258
+ - lib/rop/_plugins
259
+ - lib/rop/blog_index.rb
260
+ - lib/rop/filterable_index.rb
261
+ - lib/rop/png_diagram.html
262
+ - lib/rop/png_diagram_page.rb
263
+ - lib/rop/project_reader.rb
264
+ - lib/rop/site_type.rb
265
+ - lib/rop/spec_builder.rb
253
266
  homepage: https://github.com/riboseinc/jekyll-theme-rop/
254
267
  licenses:
255
268
  - MIT