frontman-ssg 0.0.2
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/.circleci/config.yml +42 -0
- data/.github/CODE_OF_CONDUCT.md +9 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +25 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +88 -0
- data/CHANGELOG.md +11 -0
- data/CONTRIBUTING.md +42 -0
- data/Gemfile +5 -0
- data/LICENSE.md +21 -0
- data/Rakefile +94 -0
- data/SECURITY.md +6 -0
- data/bin/frontman +6 -0
- data/frontman-ssg.gemspec +48 -0
- data/frontman.svg +2 -0
- data/lib/frontman.rb +15 -0
- data/lib/frontman/app.rb +175 -0
- data/lib/frontman/bootstrapper.rb +70 -0
- data/lib/frontman/builder/asset_pipeline.rb +55 -0
- data/lib/frontman/builder/builder.rb +193 -0
- data/lib/frontman/builder/file.rb +55 -0
- data/lib/frontman/builder/mapping.rb +54 -0
- data/lib/frontman/builder/statistics_collector.rb +37 -0
- data/lib/frontman/cli.rb +6 -0
- data/lib/frontman/commands/build.rb +76 -0
- data/lib/frontman/commands/init.rb +58 -0
- data/lib/frontman/commands/serve.rb +110 -0
- data/lib/frontman/concerns/dispatch_events.rb +56 -0
- data/lib/frontman/concerns/forward_calls_to_app.rb +28 -0
- data/lib/frontman/config.rb +52 -0
- data/lib/frontman/context.rb +125 -0
- data/lib/frontman/custom_struct.rb +44 -0
- data/lib/frontman/data_store.rb +106 -0
- data/lib/frontman/data_store_file.rb +60 -0
- data/lib/frontman/helpers/app_helper.rb +18 -0
- data/lib/frontman/helpers/link_helper.rb +35 -0
- data/lib/frontman/helpers/render_helper.rb +76 -0
- data/lib/frontman/helpers/url_helper.rb +11 -0
- data/lib/frontman/iterator.rb +48 -0
- data/lib/frontman/process/chain.rb +43 -0
- data/lib/frontman/process/processor.rb +11 -0
- data/lib/frontman/renderers/erb_renderer.rb +21 -0
- data/lib/frontman/renderers/haml_renderer.rb +22 -0
- data/lib/frontman/renderers/markdown_renderer.rb +26 -0
- data/lib/frontman/renderers/renderer.rb +26 -0
- data/lib/frontman/renderers/renderer_resolver.rb +26 -0
- data/lib/frontman/resource.rb +279 -0
- data/lib/frontman/sitemap_tree.rb +211 -0
- data/lib/frontman/toolbox/timer.rb +49 -0
- data/lib/frontman/version.rb +6 -0
- data/project-templates/default/.gitignore +2 -0
- data/project-templates/default/Gemfile +3 -0
- data/project-templates/default/config.rb +17 -0
- data/project-templates/default/data/site.yml +4 -0
- data/project-templates/default/helpers/site_helper.rb +7 -0
- data/project-templates/default/public/code.css +77 -0
- data/project-templates/default/public/frontman-logo.svg +2 -0
- data/project-templates/default/public/main.css +27 -0
- data/project-templates/default/public/main.js +1 -0
- data/project-templates/default/source/index.html.md.erb +7 -0
- data/project-templates/default/source/sitemap.xml.erb +11 -0
- data/project-templates/default/views/layouts/main.erb +19 -0
- data/project-templates/default/views/layouts/main.haml +15 -0
- data/project-templates/default/views/partials/menu.erb +7 -0
- data/project-templates/webpack/.gitignore +4 -0
- data/project-templates/webpack/Gemfile +3 -0
- data/project-templates/webpack/README.md +54 -0
- data/project-templates/webpack/assets/css/code.css +77 -0
- data/project-templates/webpack/assets/css/style.css +27 -0
- data/project-templates/webpack/assets/images/.gitkeep +0 -0
- data/project-templates/webpack/assets/images/frontman_logo.svg +2 -0
- data/project-templates/webpack/assets/js/index.js +1 -0
- data/project-templates/webpack/config.rb +24 -0
- data/project-templates/webpack/data/site.yml +4 -0
- data/project-templates/webpack/helpers/assets_helper.rb +24 -0
- data/project-templates/webpack/helpers/site_helper.rb +7 -0
- data/project-templates/webpack/package-lock.json +7603 -0
- data/project-templates/webpack/package.json +34 -0
- data/project-templates/webpack/source/index.html.md.erb +7 -0
- data/project-templates/webpack/source/sitemap.xml.erb +11 -0
- data/project-templates/webpack/views/layouts/main.erb +20 -0
- data/project-templates/webpack/views/layouts/main.haml +14 -0
- data/project-templates/webpack/views/partials/menu.erb +7 -0
- data/project-templates/webpack/views/partials/script_with_vendors.haml +5 -0
- data/project-templates/webpack/webpack/base.config.js +51 -0
- data/project-templates/webpack/webpack/dev.config.js +6 -0
- data/project-templates/webpack/webpack/prod.config.js +30 -0
- data/readme.md +80 -0
- data/sorbet/config +2 -0
- data/sorbet/rbi/hidden-definitions/errors.txt +27259 -0
- data/sorbet/rbi/hidden-definitions/hidden.rbi +45122 -0
- data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +276 -0
- data/sorbet/rbi/todo.rbi +6 -0
- data/spec/frontman/app_spec.rb +48 -0
- data/spec/frontman/bootstrapper_spec.rb +26 -0
- data/spec/frontman/builder/builder_spec.rb +79 -0
- data/spec/frontman/builder/file_spec.rb +45 -0
- data/spec/frontman/builder/mapping_spec.rb +8 -0
- data/spec/frontman/concerns/dispatch_events_spec.rb +70 -0
- data/spec/frontman/concerns/forward_calls_to_app_spec.rb +21 -0
- data/spec/frontman/config_spec.rb +54 -0
- data/spec/frontman/context_spec.rb +48 -0
- data/spec/frontman/custom_struct_spec.rb +51 -0
- data/spec/frontman/data_store_file_spec.rb +9 -0
- data/spec/frontman/data_store_spec.rb +36 -0
- data/spec/frontman/frontman_ssg_spec.rb +7 -0
- data/spec/frontman/helpers/app_helper_spec.rb +24 -0
- data/spec/frontman/helpers/link_helper_spec.rb +37 -0
- data/spec/frontman/helpers/render_helper_spec.rb +55 -0
- data/spec/frontman/helpers/url_helper_spec.rb +21 -0
- data/spec/frontman/iterator_spec.rb +47 -0
- data/spec/frontman/mocks/asset.css +3 -0
- data/spec/frontman/mocks/config.rb +0 -0
- data/spec/frontman/mocks/helpers/formatting_helper.rb +5 -0
- data/spec/frontman/mocks/helpers/language_helper.rb +5 -0
- data/spec/frontman/mocks/helpers/link_helper.rb +5 -0
- data/spec/frontman/mocks/helpers/test_command.rb +5 -0
- data/spec/frontman/mocks/html_file.html +8 -0
- data/spec/frontman/mocks/html_file.html.md.erb +9 -0
- data/spec/frontman/mocks/html_file.md.html +8 -0
- data/spec/frontman/mocks/info.yml +4 -0
- data/spec/frontman/mocks/layouts/raw_without_body.haml +1 -0
- data/spec/frontman/mocks/nested/data.yml +4 -0
- data/spec/frontman/mocks/nested/more_data.yml +4 -0
- data/spec/frontman/mocks/partials/paragraph.haml +2 -0
- data/spec/frontman/mocks/snippet/html_file.html +8 -0
- data/spec/frontman/mocks/snippet/html_file.yml +670 -0
- data/spec/frontman/mocks/test.html +8 -0
- data/spec/frontman/mocks/wrap.haml +3 -0
- data/spec/frontman/process/chain_spec.rb +56 -0
- data/spec/frontman/renderers/erb_renderer_spec.rb +22 -0
- data/spec/frontman/renderers/haml_renderer_spec.rb +12 -0
- data/spec/frontman/renderers/markdown_renderer_spec.rb +12 -0
- data/spec/frontman/renderers/renderer_spec.rb +16 -0
- data/spec/frontman/resource_spec.rb +151 -0
- data/spec/frontman/sitemap_tree_spec.rb +128 -0
- data/spec/frontman/toolbox/timer_spec.rb +34 -0
- data/spec/spec_setup.rb +19 -0
- 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,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,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
|