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.
@@ -0,0 +1,5 @@
1
+ module Kentico
2
+ module Kontent
3
+ GEM_NAME = 'kontent-jekyll'
4
+ end
5
+ end
@@ -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