jekyll-theme-rop 2.1.16 → 2.1.18
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/Rakefile +9 -0
- data/lib/jekyll-theme-open-project.rb +4 -0
- data/lib/jekyll-theme-rop.rb +19 -0
- data/lib/rop/_plugins +1 -0
- data/lib/rop/blog_index.rb +78 -0
- data/lib/rop/filterable_index.rb +154 -0
- data/lib/rop/png_diagram.html +31 -0
- data/lib/rop/png_diagram_page.rb +31 -0
- data/lib/rop/project_reader.rb +373 -0
- data/lib/rop/site_type.rb +23 -0
- data/lib/rop/spec_builder.rb +129 -0
- data/lib/rop/version.rb +3 -0
- data/lib/rop.rb +5 -0
- metadata +14 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f3b4046ce7aac2a41e6143ce8fcc59d7c5ac8fe1e04a5b2a530f848ec8ea511
|
4
|
+
data.tar.gz: 0e7bf2129e42f44eb9afa6059c9c9f04d48ed7c2bb55bf04009420a08ce10423
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de88ee1833cc4d4438234b4e8f3cf28ece3a5449aeb14385be648d15d44346b94bea1dad7bc8f3529378f1f897450e5d9844f1949704144d61ff5e20f6f235e0
|
7
|
+
data.tar.gz: 071e47f509c25185e7ec639fadcbb838cc8ea80ac3991b138f3700fffff55c136e282ac1219fa6dde800976590bdc970e05748e98dcc4a0613921b0421faf921
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
puts '[jekyll-theme-rop] Loaded.'
|
4
|
+
|
5
|
+
require 'jekyll'
|
6
|
+
require_relative 'rop'
|
7
|
+
|
8
|
+
Jekyll::Hooks.register :site, :after_init do |site|
|
9
|
+
site.reader = Rop::ProjectReader.new(site) if site.theme # TODO: Check theme name
|
10
|
+
end
|
11
|
+
|
12
|
+
# This is a fix for static files
|
13
|
+
require 'fileutils'
|
14
|
+
Jekyll::Hooks.register :pages, :post_write do |page|
|
15
|
+
if (page.path == 'robots.txt') || (page.path == 'sitemap.xml')
|
16
|
+
File.write(page.site.in_dest_dir(page.path), page.content,
|
17
|
+
mode: 'wb')
|
18
|
+
end
|
19
|
+
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/version.rb
ADDED
data/lib/rop.rb
ADDED
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.
|
4
|
+
version: 2.1.18
|
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/rop.rb
|
257
|
+
- lib/rop/_plugins
|
258
|
+
- lib/rop/blog_index.rb
|
259
|
+
- lib/rop/filterable_index.rb
|
260
|
+
- lib/rop/png_diagram.html
|
261
|
+
- lib/rop/png_diagram_page.rb
|
262
|
+
- lib/rop/project_reader.rb
|
263
|
+
- lib/rop/site_type.rb
|
264
|
+
- lib/rop/spec_builder.rb
|
265
|
+
- lib/rop/version.rb
|
253
266
|
homepage: https://github.com/riboseinc/jekyll-theme-rop/
|
254
267
|
licenses:
|
255
268
|
- MIT
|