kontent-jekyll 0.11.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 +7 -0
- data/.gitignore +54 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +133 -0
- data/LICENSE +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/kontent-jekyll.gemspec +73 -0
- data/lib/kontent-jekyll.rb +15 -0
- data/lib/kontent-jekyll/constants/item_element_type.rb +17 -0
- data/lib/kontent-jekyll/constants/kentico_config_keys.rb +10 -0
- data/lib/kontent-jekyll/constants/page_type.rb +14 -0
- data/lib/kontent-jekyll/gem_name.rb +5 -0
- data/lib/kontent-jekyll/generator.rb +106 -0
- data/lib/kontent-jekyll/resolvers/content_link_resolver.rb +15 -0
- data/lib/kontent-jekyll/resolvers/content_resolver.rb +47 -0
- data/lib/kontent-jekyll/resolvers/data_resolver.rb +46 -0
- data/lib/kontent-jekyll/resolvers/filename_resolver.rb +52 -0
- data/lib/kontent-jekyll/resolvers/front_matter_resolver.rb +163 -0
- data/lib/kontent-jekyll/resolvers/inline_content_item_resolver.rb +15 -0
- data/lib/kontent-jekyll/site_processing/custom_site_processor.rb +12 -0
- data/lib/kontent-jekyll/site_processing/kentico_kontent_importer.rb +88 -0
- data/lib/kontent-jekyll/site_processing/site_processor.rb +249 -0
- data/lib/kontent-jekyll/utils/normalize_object.rb +88 -0
- data/lib/kontent-jekyll/version.rb +5 -0
- metadata +170 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'kontent-jekyll/site_processing/custom_site_processor'
|
2
|
+
require 'kontent-jekyll/site_processing/kentico_kontent_importer'
|
3
|
+
require 'kontent-jekyll/site_processing/site_processor'
|
4
|
+
|
5
|
+
module Kentico
|
6
|
+
module Kontent
|
7
|
+
##
|
8
|
+
# This class generates content stored in Kentico Cloud CMS and populute
|
9
|
+
# particular Jekyll structures so the website is correctly outputted
|
10
|
+
# during the build process.
|
11
|
+
|
12
|
+
class ContentGenerator < Jekyll::Generator
|
13
|
+
include SiteProcessing
|
14
|
+
DEFAULT_LANGUAGE = nil
|
15
|
+
|
16
|
+
safe true
|
17
|
+
priority :highest
|
18
|
+
|
19
|
+
def generate(site)
|
20
|
+
Jekyll::logger.info 'Importing from Kentico Kontent...'
|
21
|
+
|
22
|
+
config = parse_config(site)
|
23
|
+
|
24
|
+
load_custom_processors!(config)
|
25
|
+
process_site(site, config)
|
26
|
+
|
27
|
+
Jekyll::logger.info 'Import finished'
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
##
|
33
|
+
# Parses Jekyll configuration into OpenStruct structure.
|
34
|
+
|
35
|
+
def parse_config(site)
|
36
|
+
JSON.parse(
|
37
|
+
JSON.generate(site.config),
|
38
|
+
object_class: OpenStruct
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Load custom resolvers from the _plugins/kentico directory.
|
44
|
+
|
45
|
+
def load_custom_processors!(config)
|
46
|
+
mapper_search_path = File.join(config.source, config.plugins_dir, 'kentico')
|
47
|
+
mapper_files = Jekyll::Utils.safe_glob(mapper_search_path, File.join('**', '*.rb'))
|
48
|
+
|
49
|
+
Jekyll::External.require_with_graceful_fail(mapper_files)
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Processed the site.
|
54
|
+
# It imports KC content for every language from the config file.
|
55
|
+
# Then it pass the content to the site processor to populate Jekyll structures.
|
56
|
+
|
57
|
+
def process_site(site, config)
|
58
|
+
kentico_config = config.kentico
|
59
|
+
importer = create_importer(kentico_config)
|
60
|
+
|
61
|
+
processor = SiteProcessor.new(site, kentico_config)
|
62
|
+
|
63
|
+
all_items_by_type = {}
|
64
|
+
|
65
|
+
languages = kentico_config.languages || [DEFAULT_LANGUAGE]
|
66
|
+
languages.each do |language|
|
67
|
+
items_by_type = importer.items_by_type(language)
|
68
|
+
|
69
|
+
all_items_by_type.merge!(items_by_type) do |key, currentItems, newItems|
|
70
|
+
currentItems || newItems
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
taxonomies = importer.taxonomies
|
75
|
+
|
76
|
+
processor.process_pages(all_items_by_type)
|
77
|
+
processor.process_posts(all_items_by_type)
|
78
|
+
processor.process_data(all_items_by_type)
|
79
|
+
processor.process_taxonomies(taxonomies)
|
80
|
+
|
81
|
+
unique_items = all_items_by_type
|
82
|
+
.values
|
83
|
+
.flatten(1)
|
84
|
+
.uniq{ |i| "#{i.system.language};#{i.system.id}" }
|
85
|
+
|
86
|
+
kentico_data = OpenStruct.new(
|
87
|
+
items: unique_items,
|
88
|
+
taxonomy_groups: taxonomies,
|
89
|
+
)
|
90
|
+
|
91
|
+
custom_site_processor = CustomSiteProcessor.for(kentico_config)
|
92
|
+
custom_site_processor&.generate(site, kentico_data)
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
##
|
97
|
+
# Creates a desired content importer. RACK_TEST_IMPORTER class name and implementation
|
98
|
+
# is specified in RSpec tests.
|
99
|
+
|
100
|
+
def create_importer(kentico_config)
|
101
|
+
importer_name = ENV['RACK_TEST_IMPORTER'] || KenticoKontentImporter.to_s
|
102
|
+
Module.const_get(importer_name).new(kentico_config)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Kentico
|
2
|
+
module Kontent
|
3
|
+
module Resolvers
|
4
|
+
##
|
5
|
+
# This class instantiate the resolver based on the name from configuration.
|
6
|
+
|
7
|
+
class ContentLinkResolver
|
8
|
+
def self.for(config)
|
9
|
+
class_name = config.content_link_resolver
|
10
|
+
class_name && Module.const_get(class_name).new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Kentico
|
2
|
+
module Kontent
|
3
|
+
module Resolvers
|
4
|
+
##
|
5
|
+
# This class resolve the content of the content item to be injected
|
6
|
+
# under the front matter part of the page.
|
7
|
+
# If no user-defined resolver was provided or it returned nil
|
8
|
+
# then content will be resolved in a default way.
|
9
|
+
|
10
|
+
class ContentResolver
|
11
|
+
def initialize(global_config)
|
12
|
+
@global_config = global_config
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(content_item, config)
|
16
|
+
content = custom_resolver && custom_resolver.resolve(content_item)
|
17
|
+
content || resolve_internal(content_item, config)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
##
|
23
|
+
# User-provided provided resolver is instantiated based on the name from configuration.
|
24
|
+
|
25
|
+
def custom_resolver
|
26
|
+
return @custom_resolver if @custom_resolver
|
27
|
+
|
28
|
+
resolver_name = @global_config.content_resolver
|
29
|
+
return unless resolver_name
|
30
|
+
|
31
|
+
@custom_resolver = Module.const_get(resolver_name).new
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Resolves content in a default way, it looks up element with a codename 'content'
|
36
|
+
# or codename specified in the config and takes its string value. This also resolves
|
37
|
+
# content components and linked items.
|
38
|
+
|
39
|
+
def resolve_internal(content_item, config)
|
40
|
+
element_name = config.content || 'content'
|
41
|
+
element = content_item.elements[element_name]
|
42
|
+
element && content_item.get_string(element_name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Kentico
|
2
|
+
module Kontent
|
3
|
+
module Resolvers
|
4
|
+
##
|
5
|
+
# This class resolve the data that will be injected into 'site.data' object.
|
6
|
+
# If no user-defined resolver was provided or it returned nil
|
7
|
+
# then content will be resolved in a default way.
|
8
|
+
|
9
|
+
class DataResolver
|
10
|
+
def initialize(global_config)
|
11
|
+
@global_config = global_config
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(content_item)
|
15
|
+
data = custom_resolver && custom_resolver.resolve(content_item)
|
16
|
+
data || resolve_internal(content_item)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
##
|
22
|
+
# User-provided provided resolver is instantiated based on the name from configuration.
|
23
|
+
|
24
|
+
def custom_resolver
|
25
|
+
return @custom_resolver if @custom_resolver
|
26
|
+
|
27
|
+
resolver_name = @global_config.data_resolver
|
28
|
+
return unless resolver_name
|
29
|
+
|
30
|
+
@custom_resolver = Module.const_get(resolver_name).new
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# It resolves the content item and outputs only system and element fields as the original
|
35
|
+
# item also contains methods, etc.
|
36
|
+
|
37
|
+
def resolve_internal(item)
|
38
|
+
OpenStruct.new(
|
39
|
+
system: item.system,
|
40
|
+
elements: item.elements,
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Kentico
|
2
|
+
module Kontent
|
3
|
+
module Resolvers
|
4
|
+
##
|
5
|
+
# This class resolve the filename of the page.
|
6
|
+
# If no user-defined resolver was provided or it returned nil
|
7
|
+
# then content will be resolved in a default way.
|
8
|
+
|
9
|
+
class FilenameResolver
|
10
|
+
def initialize(global_config)
|
11
|
+
@global_config = global_config
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(content_item)
|
15
|
+
filename = custom_resolver && custom_resolver.resolve(content_item)
|
16
|
+
filename || resolve_internal(content_item)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
##
|
22
|
+
# User-provided provided resolver is instantiated based on the name from configuration.
|
23
|
+
|
24
|
+
def custom_resolver
|
25
|
+
return @custom_resolver if @custom_resolver
|
26
|
+
|
27
|
+
resolver_name = @global_config.filename_resolver
|
28
|
+
return unless resolver_name
|
29
|
+
|
30
|
+
@custom_resolver = Module.const_get(resolver_name).new
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Internal resolver will try to locate the url slug element and return its value.
|
35
|
+
# If no slug was present then the item's codename will be used as the filename.
|
36
|
+
|
37
|
+
def resolve_internal(content_item)
|
38
|
+
url_slug = get_url_slug(content_item)
|
39
|
+
url_slug&.value || content_item.system.codename
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_url_slug(item)
|
43
|
+
item.elements.each_pair { |_codename, element| return element if slug?(element) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def slug?(element)
|
47
|
+
element.type == Constants::ItemElementType::URL_SLUG
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
class FrontMatterResolverBase
|
2
|
+
def initialize(global_config, type_config, content_item)
|
3
|
+
@global_config = global_config
|
4
|
+
@type_config = type_config
|
5
|
+
@content_item = content_item
|
6
|
+
end
|
7
|
+
|
8
|
+
def item
|
9
|
+
{
|
10
|
+
system: @content_item.system,
|
11
|
+
elements: @content_item.elements,
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Title is resolved from the element with 'title' codename or codename
|
17
|
+
# specified in the config.
|
18
|
+
|
19
|
+
def title
|
20
|
+
element = get_element(@type_config&.title || 'title')
|
21
|
+
element&.value
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Layout is either specified in the config for this type or global config.
|
26
|
+
|
27
|
+
def layout
|
28
|
+
@type_config&.layout || @global_config.default_layout
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_element(codename)
|
32
|
+
@content_item.elements[codename]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class PageFrontMatterResolver < FrontMatterResolverBase
|
37
|
+
def resolve
|
38
|
+
{
|
39
|
+
item: item,
|
40
|
+
title: title,
|
41
|
+
layout: layout
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class PostFrontMatterResolver < FrontMatterResolverBase
|
47
|
+
def resolve
|
48
|
+
{
|
49
|
+
item: item,
|
50
|
+
title: title,
|
51
|
+
layout: layout,
|
52
|
+
date: date,
|
53
|
+
categories: categories,
|
54
|
+
tags: tags
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Date is resolved from the element with 'date' codename or codename
|
60
|
+
# specified in the config.
|
61
|
+
|
62
|
+
def date
|
63
|
+
element = get_element(@type_config.date || 'date')
|
64
|
+
element && Time.parse(element.value)
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Categories are resolved from the element with 'categories' codename or codename
|
69
|
+
# specified in the config.
|
70
|
+
|
71
|
+
def categories
|
72
|
+
element = get_element(@type_config.categories || 'categories')
|
73
|
+
return unless element
|
74
|
+
|
75
|
+
element.value.map(&:codename)
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Tags are resolved from the element with 'tags' codename or codename
|
80
|
+
# specified in the config.
|
81
|
+
|
82
|
+
def tags
|
83
|
+
element = get_element(@type_config.tags || 'tags')
|
84
|
+
return unless element
|
85
|
+
|
86
|
+
element.value.map(&:codename)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
module Kentico
|
91
|
+
module Kontent
|
92
|
+
module Resolvers
|
93
|
+
##
|
94
|
+
# This class resolve the front matter of the page.
|
95
|
+
# It will be merged with internally generated front matter.
|
96
|
+
|
97
|
+
class FrontMatterResolver
|
98
|
+
def initialize(global_config)
|
99
|
+
@global_config = global_config
|
100
|
+
end
|
101
|
+
|
102
|
+
def execute(content_item, page_type)
|
103
|
+
front_matter = resolve_internal(content_item, page_type)
|
104
|
+
|
105
|
+
if custom_resolver
|
106
|
+
extra_data = custom_resolver.resolve(content_item, page_type)
|
107
|
+
front_matter.merge!(extra_data) if extra_data
|
108
|
+
end
|
109
|
+
|
110
|
+
front_matter
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
##
|
116
|
+
# User-provided provided resolver is instantiated based on the name from configuration.
|
117
|
+
|
118
|
+
def custom_resolver
|
119
|
+
return @custom_resolver if @custom_resolver
|
120
|
+
|
121
|
+
resolver_name = @global_config.front_matter_resolver
|
122
|
+
return unless resolver_name
|
123
|
+
|
124
|
+
@custom_resolver = Module.const_get(resolver_name).new
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Posts and pages have different default front matter (tags, categories etc)
|
129
|
+
# so we need to determine the correct internal resolver based on the page type.
|
130
|
+
|
131
|
+
def resolve_internal(content_item, page_type)
|
132
|
+
@content_item = content_item
|
133
|
+
@page_type = page_type
|
134
|
+
|
135
|
+
resolver_factory
|
136
|
+
.new(@global_config, type_config, content_item)
|
137
|
+
.resolve
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Determines default front matter resolver based on the page type.
|
142
|
+
|
143
|
+
def resolver_factory
|
144
|
+
return PostFrontMatterResolver if post?
|
145
|
+
PageFrontMatterResolver if page?
|
146
|
+
end
|
147
|
+
|
148
|
+
def type_config
|
149
|
+
return @global_config.posts if post?
|
150
|
+
@global_config.pages[@content_item.system.type] if page?
|
151
|
+
end
|
152
|
+
|
153
|
+
def page?
|
154
|
+
@page_type == Constants::PageType::PAGE
|
155
|
+
end
|
156
|
+
|
157
|
+
def post?
|
158
|
+
@page_type == Constants::PageType::POST
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Kentico
|
2
|
+
module Kontent
|
3
|
+
module Resolvers
|
4
|
+
##
|
5
|
+
# This class instantiate the resolver based on the name from configuration.
|
6
|
+
|
7
|
+
class InlineContentItemResolver
|
8
|
+
def self.for(config)
|
9
|
+
class_name = config.inline_content_item_resolver
|
10
|
+
class_name && Module.const_get(class_name).new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|