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.
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,28 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Frontman
5
+ module ForwardCallsToApp
6
+ def self.included(klass)
7
+ klass.extend Frontman::ForwardCallsToApp::ClassMethods
8
+ end
9
+
10
+ def method_missing(method_id, *arguments, &block)
11
+ Frontman::App.instance.public_send(method_id, *arguments, &block)
12
+ end
13
+
14
+ def respond_to_missing?(method_name, _ = false)
15
+ Frontman::App.instance.respond_to?(method_name)
16
+ end
17
+
18
+ module ClassMethods
19
+ def method_missing(method_id, *arguments, &block)
20
+ Frontman::App.instance.public_send(method_id, *arguments, &block)
21
+ end
22
+
23
+ def respond_to_missing?(method_name, _ = false)
24
+ Frontman::App.instance.respond_to?(method_name)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,52 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ module Frontman
7
+ class Config
8
+ class << self
9
+ extend T::Sig
10
+
11
+ sig do
12
+ params(
13
+ key: T.any(String, Symbol), value: T.untyped
14
+ ).returns(T.self_type)
15
+ end
16
+ def set(key, value)
17
+ @@values ||= {}
18
+ @@values[key.to_sym] = value
19
+ self
20
+ end
21
+
22
+ sig do
23
+ params(
24
+ key: T.any(String, Symbol), fallback: T.untyped
25
+ ).returns(T.untyped)
26
+ end
27
+ def get(key, fallback: nil)
28
+ @@values ||= {}
29
+ @@values.key?(key.to_sym) ? @@values[key.to_sym] : fallback
30
+ end
31
+
32
+ sig { params(key: T.any(String, Symbol)).returns(T.self_type) }
33
+ def delete(key)
34
+ @@values ||= {}
35
+ @@values.delete(key)
36
+ self
37
+ end
38
+
39
+ sig { params(key: T.any(String, Symbol)).returns(T::Boolean) }
40
+ def has?(key)
41
+ @@values ||= {}
42
+ @@values.key?(key.to_sym)
43
+ end
44
+
45
+ sig { returns(Hash) }
46
+ def all
47
+ @@values ||= {}
48
+ @@values
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,125 @@
1
+ # typed: true
2
+ # frozen_string_literal: false
3
+
4
+ require 'frontman/app'
5
+ require 'frontman/concerns/forward_calls_to_app'
6
+ require 'frontman/config'
7
+ require 'sorbet-runtime'
8
+
9
+ module Frontman
10
+ class Context
11
+ extend T::Sig
12
+ include Frontman::ForwardCallsToApp
13
+
14
+ sig { params(layout: String).returns(String) }
15
+ def wrap_layout(layout)
16
+ layout_dir = Frontman::Config.get(:layout_dir, fallback: 'views/layouts')
17
+ layout_path = File.join(layout_dir, layout)
18
+
19
+ # Haml is not designed to do handle wrap_layout properly
20
+ # so we need to hack the buffer of haml that is set
21
+ # inside the context by haml
22
+ save_buffer
23
+
24
+ # We don't save the content of the yield, it will be saved in the buffer
25
+ yield
26
+
27
+ # The buffer now contains the content of the yield
28
+ content = load_buffer
29
+
30
+ # Restore the buffer so the rendering of the file can continue
31
+ restore_buffer
32
+
33
+ Resource.from_path(layout_path, nil, false).render(content)
34
+ end
35
+
36
+ sig do
37
+ params(key: T.any(String, Symbol), content: T.untyped)
38
+ .returns(T.untyped)
39
+ end
40
+ def content_for(key, content = nil)
41
+ # Haml is not designed to do handle content_for properly
42
+ # so we need to hack the buffer of haml that is set
43
+ # inside the context by haml
44
+ save_buffer
45
+
46
+ content ||= ''
47
+
48
+ if block_given?
49
+ # We don't save the content of the yield, it will be saved in the buffer
50
+ yield
51
+
52
+ # The buffer now contains the content of the yield
53
+ content = load_buffer
54
+ end
55
+
56
+ # Restore the buffer so the rendering of the file can continue
57
+ restore_buffer
58
+
59
+ # We store the the content block inside the current page
60
+ # because we don't know which renderer/layout/template will need it
61
+ current_page = Frontman::App.instance.current_page
62
+ current_page.content_blocks[key.to_sym] = content unless current_page.nil?
63
+ end
64
+
65
+ sig { params(key: T.any(String, Symbol)).returns(T::Boolean) }
66
+ def content_for?(key)
67
+ Frontman::App.instance.current_page.content_blocks.key?(key.to_sym)
68
+ end
69
+
70
+ sig do
71
+ params(key: T.any(String, Symbol), _args: T.untyped).returns(T.untyped)
72
+ end
73
+ def yield_content(key, *_args)
74
+ Frontman::App.instance.current_page.content_blocks[key.to_sym]
75
+ end
76
+
77
+ sig do
78
+ params(content: T.untyped).returns(T.untyped)
79
+ end
80
+ def get_binding(&content)
81
+ binding { content }
82
+ end
83
+
84
+ private
85
+
86
+ sig { void }
87
+ def save_buffer
88
+ haml_locals = instance_variable_get(:@_haml_locals)
89
+
90
+ if haml_locals
91
+ # save buffer
92
+ @buffer = haml_locals[:_hamlout].buffer
93
+ # empty the buffer so we can capture everything from the new render
94
+ haml_locals[:_hamlout].buffer = ''
95
+ else
96
+ # save buffer
97
+ @buffer = instance_variable_get(:@_erbout)
98
+ # empty the buffer so we can capture everything from the new render
99
+ instance_variable_set(:@_erbout, '')
100
+ end
101
+ end
102
+
103
+ sig { void }
104
+ def restore_buffer
105
+ haml_locals = instance_variable_get(:@_haml_locals)
106
+
107
+ if haml_locals
108
+ haml_locals[:_hamlout].buffer = @buffer
109
+ else
110
+ instance_variable_set(:@_erbout, @buffer)
111
+ end
112
+ end
113
+
114
+ sig { returns(T.untyped) }
115
+ def load_buffer
116
+ haml_locals = instance_variable_get(:@_haml_locals)
117
+
118
+ if haml_locals
119
+ haml_locals[:_hamlout].buffer
120
+ else
121
+ instance_variable_get(:@_erbout)
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,44 @@
1
+ # typed: false
2
+ # frozen_string_literal: false
3
+
4
+ require 'ostruct'
5
+
6
+ class CustomStruct < OpenStruct
7
+ def respond_to_missing?(method_id, *_arguments)
8
+ to_h.respond_to?(method_id) || super(method_id)
9
+ end
10
+
11
+ def key?(key)
12
+ to_h.key?(key)
13
+ end
14
+
15
+ def method_missing(method_id, *arguments, &block)
16
+ if to_h.respond_to?(method_id)
17
+ to_h.public_send(method_id, *arguments, &block)
18
+ else
19
+ super(method_id, *arguments)
20
+ end
21
+ end
22
+ end
23
+
24
+ class Hash
25
+ def to_ostruct
26
+ arr = map do |k, v|
27
+ if v.is_a? Array
28
+ [k, v.map { |el| el.respond_to?(:to_ostruct) ? el.to_ostruct : el }]
29
+ else
30
+ [k, v.respond_to?(:to_ostruct) ? v.to_ostruct : v]
31
+ end
32
+ end
33
+
34
+ CustomStruct.new(Hash[arr])
35
+ end
36
+ end
37
+
38
+ class Array
39
+ def to_ostruct
40
+ map do |item|
41
+ item.respond_to?(:to_ostruct) ? item.to_ostruct : item
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,106 @@
1
+ # typed: true
2
+ # frozen_string_literal: false
3
+
4
+ require 'pathname'
5
+ require 'frontman/data_store_file'
6
+ require 'sorbet-runtime'
7
+
8
+ module Frontman
9
+ class DataStore
10
+ extend T::Sig
11
+
12
+ attr_reader :base_file_name, :path, :parent
13
+
14
+ sig do
15
+ params(
16
+ path: String,
17
+ base_file_name: T.nilable(String),
18
+ parent: T.nilable(DataStore)
19
+ ).void
20
+ end
21
+ def initialize(path, base_file_name = nil, parent = nil)
22
+ @path = path
23
+ @cache = {}
24
+ @parent = parent
25
+ @base_file_name = base_file_name
26
+
27
+ load_files
28
+ end
29
+
30
+ sig { returns(Array) }
31
+ def flatten
32
+ elements = []
33
+
34
+ @cache.sort_by { |_k, v| v.path }.each do |_k, v|
35
+ if v.instance_of?(Frontman::DataStoreFile)
36
+ v.refresh
37
+ elements.push(v)
38
+ else
39
+ elements.push(*v.flatten)
40
+ end
41
+ end
42
+
43
+ elements
44
+ end
45
+
46
+ def method_missing(method_id, *arguments, &block)
47
+ method_name = method_id
48
+
49
+ # Make sure we are able to access data files with an array syntax
50
+ method_name = arguments[0].to_sym if method_id == :[]
51
+
52
+ if @cache.key?(method_name.to_s)
53
+ cached = @cache[method_name.to_s]
54
+
55
+ if Frontman::App.instance.refresh_data_files
56
+ cached.refresh if cached.is_a?(Frontman::DataStoreFile)
57
+ end
58
+
59
+ return cached
60
+ end
61
+
62
+ # Make sure we forward the access to the data
63
+ if @cache.respond_to?(method_name)
64
+ return @cache.public_send(method_name, &block)
65
+ end
66
+
67
+ nil
68
+ end
69
+
70
+ def respond_to_missing?(method_name, _)
71
+ @cache.key?(method_name.to_s) || @cache.respond_to?(method_name)
72
+ end
73
+
74
+ sig { returns(String) }
75
+ def to_s
76
+ "<DataStore #{@cache.keys.join(', ')} >"
77
+ end
78
+
79
+ private
80
+
81
+ sig { void }
82
+ def load_files
83
+ Dir.entries(@path).sort.each do |file|
84
+ next if (file == '.') || (file == '..')
85
+
86
+ file_path = @path + '/' + file
87
+
88
+ # remove numbers and first '-' from the filename so we can
89
+ # - generate nice urls
90
+ # - omit the number when accessing the object
91
+ base_file_name = file.gsub(/[0-9]*+-?(.+)/, '\1')
92
+
93
+ if File.file?(file_path) && (File.extname(file) == '.yml')
94
+ method_name = File.basename(base_file_name, File.extname(file))
95
+ @cache[method_name.to_s] = Frontman::DataStoreFile.new(
96
+ file_path, file, method_name, self
97
+ )
98
+ elsif File.directory?(file_path)
99
+ @cache[base_file_name] = DataStore.new(
100
+ file_path, base_file_name, self
101
+ )
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,60 @@
1
+ # typed: true
2
+ # frozen_string_literal: false
3
+
4
+ require 'pathname'
5
+ require 'sorbet-runtime'
6
+ require 'yaml'
7
+
8
+ module Frontman
9
+ class DataStoreFile
10
+ extend T::Sig
11
+ attr_accessor :path, :file_name, :base_file_name, :parent,
12
+ :position, :data
13
+
14
+ sig do
15
+ params(
16
+ path: String,
17
+ file_name: String,
18
+ base_file_name: String,
19
+ parent: DataStore
20
+ ).void
21
+ end
22
+ def initialize(path, file_name, base_file_name, parent)
23
+ @path = path
24
+ @file_name = file_name
25
+ @base_file_name = base_file_name
26
+ @parent = parent
27
+ @data = (YAML.load_file(@path) || {}).to_ostruct
28
+
29
+ @@i ||= 0
30
+ @position = @@i
31
+ @@i += 1
32
+ end
33
+
34
+ def method_missing(method_id, *arguments, &block)
35
+ @data.public_send(method_id, *arguments, &block)
36
+ end
37
+
38
+ def respond_to_missing?(method_id, _)
39
+ @data.respond_to? method_id
40
+ end
41
+
42
+ sig { returns(String) }
43
+ def current_path
44
+ @path
45
+ end
46
+
47
+ sig { void }
48
+ def refresh
49
+ return unless Frontman::App.instance.refresh_data_files
50
+
51
+ data = YAML.load_file(@path) || {}
52
+ @data = data.to_ostruct
53
+ end
54
+
55
+ sig { returns(String) }
56
+ def to_s
57
+ "<DataStoreFile #{@data.keys.join(', ')} >"
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,18 @@
1
+ # typed: true
2
+ # frozen_string_literal: false
3
+
4
+ require 'frontman/config'
5
+
6
+ module AppHelper
7
+ extend T::Sig
8
+
9
+ sig { returns(T::Boolean) }
10
+ def build?
11
+ Frontman::Config.get(:mode) == 'build'
12
+ end
13
+
14
+ sig { returns(T::Boolean) }
15
+ def serve?
16
+ !build?
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ # typed: true
2
+ # frozen_string_literal: false
3
+
4
+ require 'htmlentities'
5
+ require 'singleton'
6
+ require 'sorbet-runtime'
7
+
8
+ module LinkHelper
9
+ extend T::Sig
10
+
11
+ sig { params(str: String, salt: String).returns(String) }
12
+ def generate_id(str, salt = '')
13
+ id = slugify(str)
14
+
15
+ @ids ||= {}
16
+ @ids[salt.to_s + id] ||= 0
17
+ @ids[salt.to_s + id] += 1
18
+ @ids[salt.to_s + id] == 1 ? id : "#{id}-#{@ids[salt.to_s + id]}"
19
+ end
20
+
21
+ sig { void }
22
+ def reset_ids_generation
23
+ @ids = {}
24
+ end
25
+
26
+ sig { params(string: String).returns(String) }
27
+ def slugify(string)
28
+ HTMLEntities.new
29
+ .decode(string)
30
+ .gsub(%r{</?[^>]*>}, '')
31
+ .gsub(/\s/, '-')
32
+ .gsub(%r{[\[\]()/",`'&<>\.*]}, '')
33
+ .downcase
34
+ end
35
+ end