frontman-ssg 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +42 -0
  3. data/.github/CODE_OF_CONDUCT.md +9 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +25 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
  6. data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
  7. data/.gitignore +5 -0
  8. data/.rubocop.yml +88 -0
  9. data/CHANGELOG.md +11 -0
  10. data/CONTRIBUTING.md +42 -0
  11. data/Gemfile +5 -0
  12. data/LICENSE.md +21 -0
  13. data/Rakefile +94 -0
  14. data/SECURITY.md +6 -0
  15. data/bin/frontman +6 -0
  16. data/frontman-ssg.gemspec +48 -0
  17. data/frontman.svg +2 -0
  18. data/lib/frontman.rb +15 -0
  19. data/lib/frontman/app.rb +175 -0
  20. data/lib/frontman/bootstrapper.rb +70 -0
  21. data/lib/frontman/builder/asset_pipeline.rb +55 -0
  22. data/lib/frontman/builder/builder.rb +193 -0
  23. data/lib/frontman/builder/file.rb +55 -0
  24. data/lib/frontman/builder/mapping.rb +54 -0
  25. data/lib/frontman/builder/statistics_collector.rb +37 -0
  26. data/lib/frontman/cli.rb +6 -0
  27. data/lib/frontman/commands/build.rb +76 -0
  28. data/lib/frontman/commands/init.rb +58 -0
  29. data/lib/frontman/commands/serve.rb +110 -0
  30. data/lib/frontman/concerns/dispatch_events.rb +56 -0
  31. data/lib/frontman/concerns/forward_calls_to_app.rb +28 -0
  32. data/lib/frontman/config.rb +52 -0
  33. data/lib/frontman/context.rb +125 -0
  34. data/lib/frontman/custom_struct.rb +44 -0
  35. data/lib/frontman/data_store.rb +106 -0
  36. data/lib/frontman/data_store_file.rb +60 -0
  37. data/lib/frontman/helpers/app_helper.rb +18 -0
  38. data/lib/frontman/helpers/link_helper.rb +35 -0
  39. data/lib/frontman/helpers/render_helper.rb +76 -0
  40. data/lib/frontman/helpers/url_helper.rb +11 -0
  41. data/lib/frontman/iterator.rb +48 -0
  42. data/lib/frontman/process/chain.rb +43 -0
  43. data/lib/frontman/process/processor.rb +11 -0
  44. data/lib/frontman/renderers/erb_renderer.rb +21 -0
  45. data/lib/frontman/renderers/haml_renderer.rb +22 -0
  46. data/lib/frontman/renderers/markdown_renderer.rb +26 -0
  47. data/lib/frontman/renderers/renderer.rb +26 -0
  48. data/lib/frontman/renderers/renderer_resolver.rb +26 -0
  49. data/lib/frontman/resource.rb +279 -0
  50. data/lib/frontman/sitemap_tree.rb +211 -0
  51. data/lib/frontman/toolbox/timer.rb +49 -0
  52. data/lib/frontman/version.rb +6 -0
  53. data/project-templates/default/.gitignore +2 -0
  54. data/project-templates/default/Gemfile +3 -0
  55. data/project-templates/default/config.rb +17 -0
  56. data/project-templates/default/data/site.yml +4 -0
  57. data/project-templates/default/helpers/site_helper.rb +7 -0
  58. data/project-templates/default/public/code.css +77 -0
  59. data/project-templates/default/public/frontman-logo.svg +2 -0
  60. data/project-templates/default/public/main.css +27 -0
  61. data/project-templates/default/public/main.js +1 -0
  62. data/project-templates/default/source/index.html.md.erb +7 -0
  63. data/project-templates/default/source/sitemap.xml.erb +11 -0
  64. data/project-templates/default/views/layouts/main.erb +19 -0
  65. data/project-templates/default/views/layouts/main.haml +15 -0
  66. data/project-templates/default/views/partials/menu.erb +7 -0
  67. data/project-templates/webpack/.gitignore +4 -0
  68. data/project-templates/webpack/Gemfile +3 -0
  69. data/project-templates/webpack/README.md +54 -0
  70. data/project-templates/webpack/assets/css/code.css +77 -0
  71. data/project-templates/webpack/assets/css/style.css +27 -0
  72. data/project-templates/webpack/assets/images/.gitkeep +0 -0
  73. data/project-templates/webpack/assets/images/frontman_logo.svg +2 -0
  74. data/project-templates/webpack/assets/js/index.js +1 -0
  75. data/project-templates/webpack/config.rb +24 -0
  76. data/project-templates/webpack/data/site.yml +4 -0
  77. data/project-templates/webpack/helpers/assets_helper.rb +24 -0
  78. data/project-templates/webpack/helpers/site_helper.rb +7 -0
  79. data/project-templates/webpack/package-lock.json +7603 -0
  80. data/project-templates/webpack/package.json +34 -0
  81. data/project-templates/webpack/source/index.html.md.erb +7 -0
  82. data/project-templates/webpack/source/sitemap.xml.erb +11 -0
  83. data/project-templates/webpack/views/layouts/main.erb +20 -0
  84. data/project-templates/webpack/views/layouts/main.haml +14 -0
  85. data/project-templates/webpack/views/partials/menu.erb +7 -0
  86. data/project-templates/webpack/views/partials/script_with_vendors.haml +5 -0
  87. data/project-templates/webpack/webpack/base.config.js +51 -0
  88. data/project-templates/webpack/webpack/dev.config.js +6 -0
  89. data/project-templates/webpack/webpack/prod.config.js +30 -0
  90. data/readme.md +80 -0
  91. data/sorbet/config +2 -0
  92. data/sorbet/rbi/hidden-definitions/errors.txt +27259 -0
  93. data/sorbet/rbi/hidden-definitions/hidden.rbi +45122 -0
  94. data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +276 -0
  95. data/sorbet/rbi/todo.rbi +6 -0
  96. data/spec/frontman/app_spec.rb +48 -0
  97. data/spec/frontman/bootstrapper_spec.rb +26 -0
  98. data/spec/frontman/builder/builder_spec.rb +79 -0
  99. data/spec/frontman/builder/file_spec.rb +45 -0
  100. data/spec/frontman/builder/mapping_spec.rb +8 -0
  101. data/spec/frontman/concerns/dispatch_events_spec.rb +70 -0
  102. data/spec/frontman/concerns/forward_calls_to_app_spec.rb +21 -0
  103. data/spec/frontman/config_spec.rb +54 -0
  104. data/spec/frontman/context_spec.rb +48 -0
  105. data/spec/frontman/custom_struct_spec.rb +51 -0
  106. data/spec/frontman/data_store_file_spec.rb +9 -0
  107. data/spec/frontman/data_store_spec.rb +36 -0
  108. data/spec/frontman/frontman_ssg_spec.rb +7 -0
  109. data/spec/frontman/helpers/app_helper_spec.rb +24 -0
  110. data/spec/frontman/helpers/link_helper_spec.rb +37 -0
  111. data/spec/frontman/helpers/render_helper_spec.rb +55 -0
  112. data/spec/frontman/helpers/url_helper_spec.rb +21 -0
  113. data/spec/frontman/iterator_spec.rb +47 -0
  114. data/spec/frontman/mocks/asset.css +3 -0
  115. data/spec/frontman/mocks/config.rb +0 -0
  116. data/spec/frontman/mocks/helpers/formatting_helper.rb +5 -0
  117. data/spec/frontman/mocks/helpers/language_helper.rb +5 -0
  118. data/spec/frontman/mocks/helpers/link_helper.rb +5 -0
  119. data/spec/frontman/mocks/helpers/test_command.rb +5 -0
  120. data/spec/frontman/mocks/html_file.html +8 -0
  121. data/spec/frontman/mocks/html_file.html.md.erb +9 -0
  122. data/spec/frontman/mocks/html_file.md.html +8 -0
  123. data/spec/frontman/mocks/info.yml +4 -0
  124. data/spec/frontman/mocks/layouts/raw_without_body.haml +1 -0
  125. data/spec/frontman/mocks/nested/data.yml +4 -0
  126. data/spec/frontman/mocks/nested/more_data.yml +4 -0
  127. data/spec/frontman/mocks/partials/paragraph.haml +2 -0
  128. data/spec/frontman/mocks/snippet/html_file.html +8 -0
  129. data/spec/frontman/mocks/snippet/html_file.yml +670 -0
  130. data/spec/frontman/mocks/test.html +8 -0
  131. data/spec/frontman/mocks/wrap.haml +3 -0
  132. data/spec/frontman/process/chain_spec.rb +56 -0
  133. data/spec/frontman/renderers/erb_renderer_spec.rb +22 -0
  134. data/spec/frontman/renderers/haml_renderer_spec.rb +12 -0
  135. data/spec/frontman/renderers/markdown_renderer_spec.rb +12 -0
  136. data/spec/frontman/renderers/renderer_spec.rb +16 -0
  137. data/spec/frontman/resource_spec.rb +151 -0
  138. data/spec/frontman/sitemap_tree_spec.rb +128 -0
  139. data/spec/frontman/toolbox/timer_spec.rb +34 -0
  140. data/spec/spec_setup.rb +19 -0
  141. metadata +507 -0
@@ -0,0 +1,76 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'frontman/config'
5
+ require 'frontman/context'
6
+ require 'frontman/renderers/markdown_renderer'
7
+ require 'frontman/renderers/erb_renderer'
8
+ require 'frontman/resource'
9
+ require 'sorbet-runtime'
10
+
11
+ module RenderHelper
12
+ extend T::Sig
13
+
14
+ sig do
15
+ params(template: String, data: T.any(Hash, CustomStruct))
16
+ .returns(String)
17
+ end
18
+ def partial(template, data = {})
19
+ partial_dir = Frontman::Config.get(
20
+ :partial_dir, fallback: 'views/partials'
21
+ )
22
+ r = Frontman::Resource.from_path(
23
+ File.join(partial_dir, template), nil, false
24
+ )
25
+ r.render(nil, data)
26
+ end
27
+
28
+ sig do
29
+ params(
30
+ page: Frontman::Resource, options: T.any(Hash, CustomStruct)
31
+ ).returns(String)
32
+ end
33
+ def render_page(page, options = {})
34
+ # We force not to render any layout
35
+ options[:layout] = nil
36
+ options[:ignore_page] = true
37
+
38
+ # We don't need to cache here since it already done in the render function
39
+ # of the resource
40
+ page.render(nil, options)
41
+ end
42
+
43
+ sig do
44
+ params(options: T.any(Hash, CustomStruct)).returns(String)
45
+ end
46
+ def render_current_page(options = {})
47
+ render_page(Frontman::App.instance.current_page, options)
48
+ end
49
+
50
+ sig { params(content: String).returns(String) }
51
+ def render_markdown(content)
52
+ compiled = Frontman::MarkdownRenderer.instance.compile(content)
53
+ Frontman::MarkdownRenderer.instance.render(
54
+ compiled, nil, Frontman::Context.new, {}
55
+ )
56
+ end
57
+
58
+ sig do
59
+ params(
60
+ content: String,
61
+ template_data: T.nilable(Hash)
62
+ ).returns(String)
63
+ end
64
+ def render_erb(content, template_data = nil)
65
+ context = Frontman::Context.new
66
+
67
+ if !template_data.nil? && template_data
68
+ template_data.each do |key, value|
69
+ context.singleton_class.send(:define_method, key) { value }
70
+ end
71
+ end
72
+
73
+ compiled = Frontman::ErbRenderer.instance.compile(content)
74
+ Frontman::ErbRenderer.instance.render(compiled, nil, context, {})
75
+ end
76
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UrlHelper
4
+ def format_url(url)
5
+ formatted = url.gsub('index.html', '')
6
+ .sub(%r{^/}, '')
7
+ .chomp('/')
8
+
9
+ "/#{formatted}/".gsub('//', '/')
10
+ end
11
+ end
@@ -0,0 +1,48 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require 'parallel'
5
+
6
+ module Frontman
7
+ class Iterator
8
+ class << self
9
+ extend T::Sig
10
+
11
+ def map(collection, *options, &block)
12
+ forward(:map, collection, *options, &block)
13
+ end
14
+
15
+ def each(collection, *options, &block)
16
+ forward(:each, collection, *options, &block)
17
+ end
18
+
19
+ def map_with_index(collection, *options, &block)
20
+ forward(:map_with_index, collection, *options, &block)
21
+ end
22
+
23
+ def each_with_index(collection, *options, &block)
24
+ forward(:each_with_index, collection, *options, &block)
25
+ end
26
+
27
+ def processor_count
28
+ return Parallel.processor_count if parallel?
29
+
30
+ Frontman::Config.get(:processor_count, fallback: 1)
31
+ end
32
+
33
+ private
34
+
35
+ def parallel?
36
+ Frontman::Config.get(:parallel, fallback: true)
37
+ end
38
+
39
+ def forward(method, collection, *options, &block)
40
+ if parallel?
41
+ return ::Parallel.public_send(method, collection, *options, &block)
42
+ end
43
+
44
+ collection.public_send(method, &block)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,43 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ module Frontman
7
+ module Process
8
+ class Chain
9
+ extend T::Sig
10
+
11
+ sig do
12
+ params(
13
+ processors: T::Array[Frontman::Process::Processor]
14
+ ).returns(T.self_type)
15
+ end
16
+ def initialize(processors = [])
17
+ @processors = []
18
+
19
+ add_processors(processors)
20
+ end
21
+
22
+ sig do
23
+ params(
24
+ processors: T.any(
25
+ Frontman::Process::Processor,
26
+ T::Array[Frontman::Process::Processor]
27
+ )
28
+ ).returns(T.self_type)
29
+ end
30
+ def add_processors(processors)
31
+ @processors.push(*Array(processors))
32
+ self
33
+ end
34
+
35
+ sig { params(arguments: T.untyped).returns(T::Array[T.untyped]) }
36
+ def process(*arguments)
37
+ @processors.map do |processor|
38
+ processor.process(*arguments)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Frontman
4
+ module Process
5
+ class Processor
6
+ def process(*_args)
7
+ raise NoMethodError 'process method is not implemented'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require 'erubis'
5
+ require 'frontman/renderers/renderer'
6
+
7
+ module Frontman
8
+ class ErbRenderer < Frontman::Renderer
9
+ def compile(layout)
10
+ Erubis::Eruby.new(layout, bufvar: '@_erbout')
11
+ end
12
+
13
+ def render_content(compiled, content, scope, data)
14
+ data.each do |key, value|
15
+ scope.singleton_class.send(:define_method, key) { value }
16
+ end
17
+
18
+ compiled.result(scope.get_binding { content })
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require 'haml'
5
+ require 'frontman/renderers/renderer'
6
+
7
+ module Frontman
8
+ class HamlRenderer < Renderer
9
+ def initialize
10
+ Haml::Options.defaults[:format] = :html5
11
+ super
12
+ end
13
+
14
+ def compile(layout)
15
+ Haml::Engine.new(layout)
16
+ end
17
+
18
+ def render_content(compiled, content, scope, _data)
19
+ compiled.render(scope.get_binding) { content }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require 'kramdown'
5
+ require 'frontman/renderers/renderer'
6
+
7
+ module Frontman
8
+ class MarkdownRenderer < Frontman::Renderer
9
+ def compile(layout)
10
+ Kramdown::Document.new(
11
+ layout,
12
+ syntax_highlighter: 'rouge',
13
+ parse_block_html: true,
14
+ fenced_code_blocks: true,
15
+ input: 'GFM',
16
+ with_toc_data: true,
17
+ smartypants: true,
18
+ hard_wrap: false
19
+ )
20
+ end
21
+
22
+ def render_content(compiled, _content, _scope, _data)
23
+ compiled.to_html
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require 'singleton'
5
+
6
+ module Frontman
7
+ class Renderer
8
+ include Singleton
9
+
10
+ def compile(_layout)
11
+ raise('Should be implemented in child classes')
12
+ end
13
+
14
+ def render(compiled, content, scope, data)
15
+ Frontman::App.instance.view_data.push(data)
16
+ content = render_content(compiled, content, scope, data)
17
+ Frontman::App.instance.view_data.pop
18
+
19
+ content
20
+ end
21
+
22
+ def render_content(_compiled, _content, _scope, _data)
23
+ raise('Should be implemented in child classes')
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'frontman/renderers/erb_renderer'
5
+ require 'frontman/renderers/haml_renderer'
6
+ require 'frontman/renderers/markdown_renderer'
7
+ require 'singleton'
8
+ require 'sorbet-runtime'
9
+
10
+ module Frontman
11
+ class RendererResolver
12
+ extend T::Sig
13
+ include Singleton
14
+
15
+ sig { params(extension: String).returns(T.nilable(Frontman::Renderer)) }
16
+ def get_renderer(extension)
17
+ renderers = {
18
+ 'erb': Frontman::ErbRenderer.instance,
19
+ 'md': Frontman::MarkdownRenderer.instance,
20
+ 'haml': Frontman::HamlRenderer.instance
21
+ }
22
+
23
+ renderers[extension.to_sym]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,279 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'frontman/renderers/renderer_resolver'
5
+ require 'frontman/custom_struct'
6
+ require 'frontman/context'
7
+ require 'frontman/app'
8
+ require 'frontman/sitemap_tree'
9
+ require 'sorbet-runtime'
10
+ require 'yaml/front-matter'
11
+
12
+ module Frontman
13
+ class Resource
14
+ extend T::Sig
15
+
16
+ attr_reader :dir, :file, :extension, :path
17
+ attr_accessor :destination_path, :data, :renderers, :compiled, :file_path
18
+
19
+ class << self
20
+ extend T::Sig
21
+
22
+ sig { returns(T::Hash[String, Resource]) }
23
+ def resources
24
+ @@resources || {}
25
+ end
26
+
27
+ sig do
28
+ params(
29
+ file_path: String,
30
+ destination_path: T.nilable(String),
31
+ is_page: T::Boolean
32
+ ).returns(Resource)
33
+ end
34
+ def from_path(file_path, destination_path = nil, is_page = true)
35
+ destination_path ||= file_path
36
+ file_path = file_path.gsub(%r{^/}, '')
37
+ destination_path = destination_path.gsub(%r{^/}, '')
38
+ .gsub(%r{/[0-9]+?-}, '/')
39
+ .sub(%r{^source/}, '')
40
+
41
+ # We cache the newly created resource so we avoid loosing the cache
42
+ # if from_path is called again with the same file
43
+ # This is especially important for perf in case of layouts and templates
44
+ @@resources ||= {}
45
+ @@resources[destination_path] ||= new(
46
+ file_path, destination_path, is_page
47
+ )
48
+ end
49
+ end
50
+
51
+ sig { params(path: String).returns(Array) }
52
+ def strip_extensions(path)
53
+ split = path.split('/')
54
+ without_extension = T.must(split.last).split('.')[0]
55
+ path_without_extensions = split.first(split.length - 1)
56
+ .push(T.must(without_extension))
57
+ .join('/')
58
+ extensions = T.must(split.last).split('.')[1..-1]
59
+ [path_without_extensions, extensions]
60
+ end
61
+
62
+ sig { void }
63
+ def setup_destination
64
+ destination_without_extension, dest_file_extensions = strip_extensions(
65
+ @destination_path
66
+ )
67
+
68
+ if destination_without_extension == 'index'
69
+ destination_without_extension = ''
70
+ end
71
+
72
+ is_index_page = destination_without_extension.end_with?('/index')
73
+ @extension = dest_file_extensions.first
74
+
75
+ if (@extension == 'html' || extension.nil?) && !is_index_page
76
+ @destination_path = destination_without_extension + '/index.html'
77
+ else
78
+ @destination_path = destination_without_extension + '.' + @extension
79
+ end
80
+ @path = "/#{@destination_path.chomp('index.html')}"
81
+ .gsub('//', '/')
82
+ end
83
+
84
+ sig { void }
85
+ def parse_data_file
86
+ data_file = @file_path_without_extension + '.yml'
87
+ return unless File.exist?(data_file)
88
+
89
+ begin
90
+ data = YAML.safe_load(File.read(data_file)).to_ostruct
91
+ rescue Psych::SyntaxError => e
92
+ raise("#{e} - #{data_file}")
93
+ end
94
+ @data[:yml] = data
95
+ end
96
+
97
+ sig do
98
+ params(
99
+ parse_parent: T::Boolean,
100
+ data: T.any(NilClass, Hash, CustomStruct, OpenStruct)
101
+ ).void
102
+ end
103
+ def parse_resource(parse_parent = false, data = nil)
104
+ @rendered_content = nil
105
+
106
+ @content = File.read(@file_path)
107
+ @data = {}.to_ostruct
108
+
109
+ if %w[erb html haml md txt].include?(@extension)
110
+ @data, @content = YAML::FrontMatter.extract(@content).to_ostruct
111
+ end
112
+
113
+ @data = @data.to_h.merge(data.to_h).to_ostruct unless data.nil?
114
+
115
+ @compiled = nil
116
+
117
+ # If there is only one renderer we can precompile the file because there
118
+ # is no dependency. This is critical to get good performances, especially
119
+ # for templates and layouts that we also considered as Resources
120
+ if @renderers.size == 1
121
+ @renderer = @renderers[0]
122
+ @compiled = @renderer.compile(@content)
123
+ end
124
+
125
+ if parse_parent
126
+ proxy_files = Frontman::SitemapTree.proxy_resources_from_template(
127
+ @file_path
128
+ )
129
+
130
+ if proxy_files.length
131
+ proxy_files.each do |r|
132
+ r.parse_resource(false, r.data) unless r == self
133
+ end
134
+ end
135
+ end
136
+
137
+ # We need to do that after parsing the resource so @data exists
138
+ parse_data_file if @is_page
139
+ end
140
+
141
+ sig do
142
+ params(
143
+ content_for_layout: T.nilable(String),
144
+ extra_data: T.any(Hash, CustomStruct)
145
+ ).returns(String)
146
+ end
147
+ def render(content_for_layout = nil, extra_data = {})
148
+ view_data = data.to_h.merge(extra_data).to_ostruct
149
+ layout_path = layout
150
+ layout_from_extra_data = false
151
+
152
+ layout_from_extra_data = extra_data[:layout] if extra_data.key?(:layout)
153
+ if view_data.layout
154
+ layout_path = File.join(
155
+ Frontman::Config.get(:layout_dir, fallback: 'views/layouts'),
156
+ view_data.layout
157
+ )
158
+ end
159
+
160
+ if @is_page && !view_data[:ignore_page]
161
+ # We store that in App so it can be accessed from any view
162
+ Frontman::App.instance.current_page = self
163
+ Frontman::App.instance.reset_ids_generation
164
+
165
+ sitemap_tree = Frontman::App.instance.sitemap_tree.from_resource(self)
166
+ Frontman::App.instance.current_tree = sitemap_tree
167
+ end
168
+
169
+ # If we have no layout to render and already cache the rendered content
170
+ # we can return it directly
171
+ if layout_from_extra_data.nil? && !@rendered_content.nil?
172
+ return @rendered_content
173
+ end
174
+
175
+ content = @content
176
+
177
+ # We create a new context for each file getting rendered
178
+ context = Frontman::Context.new
179
+
180
+ if @compiled
181
+ # If we manage to compile the template (only 1 renderer)
182
+ # we render from the compiled template
183
+ content = @renderer.render(
184
+ @compiled, content_for_layout, context, view_data
185
+ )
186
+ else
187
+ # We loop over each renderer (infered from extensions of the file)
188
+ # and render. The result of the 1st rendered is passed as a argument of
189
+ # the second renderer and so on until we have the final content
190
+ @renderers.each do |renderer|
191
+ compiled = renderer.compile(content)
192
+ content = renderer.render(
193
+ compiled, content_for_layout, context, view_data
194
+ )
195
+ end
196
+ end
197
+
198
+ # Cache the rendered content to avoid non necessary processing
199
+ # We do this before rendering the layout because it's usually
200
+ # what's common to every rendering
201
+ @rendered_content = content
202
+
203
+ # If a layout is specified we take the rendered content a inject it
204
+ # inside the layout.
205
+ # If layout:nil was specified when triggering the render we don't
206
+ # render the layout and override the layout that can be set in
207
+ # frontmatter or config.rb
208
+ if layout_path && !layout_from_extra_data.nil?
209
+ content = Resource.from_path(layout_path, nil, false).render(content)
210
+ end
211
+
212
+ content
213
+ end
214
+
215
+ sig { returns(T.nilable(String)) }
216
+ def layout
217
+ return nil unless @is_page
218
+
219
+ Frontman::App.instance.layouts.each do |(glob, layout)|
220
+ return layout if File.fnmatch(glob, @destination_path)
221
+ end
222
+
223
+ nil
224
+ end
225
+
226
+ sig { returns(Hash) }
227
+ def content_blocks
228
+ @content_blocks ||= {}
229
+ end
230
+
231
+ sig { returns(Time) }
232
+ def mtime
233
+ @mtime ||= File.mtime(@file_path)
234
+ end
235
+
236
+ sig { returns(String) }
237
+ def inspect
238
+ "Resource: #{@file_path}"
239
+ end
240
+
241
+ sig { returns(T::Boolean) }
242
+ def indexable?
243
+ data.key?(:indexable) ? data[:indexable] : true
244
+ end
245
+
246
+ private
247
+
248
+ sig do
249
+ params(
250
+ file_path: String,
251
+ destination_path: String,
252
+ is_page: T::Boolean
253
+ ).void
254
+ end
255
+ def initialize(file_path, destination_path, is_page)
256
+ raise "File does not exists: #{file_path}" unless File.exist?(file_path)
257
+
258
+ @file_path = file_path
259
+ @destination_path = destination_path
260
+ @is_page = is_page
261
+
262
+ setup_extension_renderers
263
+ setup_destination
264
+
265
+ parse_resource
266
+ end
267
+
268
+ sig { void }
269
+ def setup_extension_renderers
270
+ @file_path_without_extension, rendering_extensions = strip_extensions(
271
+ @file_path
272
+ )
273
+ rendering_extensions.reverse!
274
+ @renderers = rendering_extensions.map do |ext|
275
+ Frontman::RendererResolver.instance.get_renderer(ext)
276
+ end.compact
277
+ end
278
+ end
279
+ end