geb 0.1.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +37 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE +21 -0
- data/README.md +384 -0
- data/Rakefile +21 -0
- data/bin/geb +21 -0
- data/lib/geb/cli.rb +40 -0
- data/lib/geb/commands/build.rb +59 -0
- data/lib/geb/commands/init.rb +70 -0
- data/lib/geb/commands/release.rb +54 -0
- data/lib/geb/commands/remote.rb +48 -0
- data/lib/geb/commands/server.rb +77 -0
- data/lib/geb/commands/upload.rb +48 -0
- data/lib/geb/commands/version.rb +36 -0
- data/lib/geb/config.rb +103 -0
- data/lib/geb/defaults.rb +44 -0
- data/lib/geb/git.rb +112 -0
- data/lib/geb/page.rb +217 -0
- data/lib/geb/partial.rb +150 -0
- data/lib/geb/samples/basic/assets/css/site.css +7 -0
- data/lib/geb/samples/basic/assets/images/android-chrome-192x192.png +0 -0
- data/lib/geb/samples/basic/assets/images/android-chrome-512x512.png +0 -0
- data/lib/geb/samples/basic/assets/images/apple-touch-icon.png +0 -0
- data/lib/geb/samples/basic/assets/images/favicon-16x16.png +0 -0
- data/lib/geb/samples/basic/assets/images/favicon-32x32.png +0 -0
- data/lib/geb/samples/basic/assets/images/favicon.ico +0 -0
- data/lib/geb/samples/basic/assets/images/hero.png +0 -0
- data/lib/geb/samples/basic/assets/images/og-thumb.png +0 -0
- data/lib/geb/samples/basic/assets/images/twitter-thumb.png +0 -0
- data/lib/geb/samples/basic/assets/js/site.js +5 -0
- data/lib/geb/samples/basic/geb.config.yml +70 -0
- data/lib/geb/samples/basic/index.html +11 -0
- data/lib/geb/samples/basic/page.html +11 -0
- data/lib/geb/samples/basic/shared/partials/_analytics.html +9 -0
- data/lib/geb/samples/basic/shared/partials/_footer.html +3 -0
- data/lib/geb/samples/basic/shared/partials/_global_assets.html +19 -0
- data/lib/geb/samples/basic/shared/partials/_header.html +0 -0
- data/lib/geb/samples/basic/shared/partials/_meta_tags.html +34 -0
- data/lib/geb/samples/basic/shared/templates/_blog_post.html +0 -0
- data/lib/geb/samples/basic/shared/templates/_site.html +19 -0
- data/lib/geb/samples/basic/site.webmanifest +1 -0
- data/lib/geb/samples/bootstrap_jquery/assets/css/site.css +7 -0
- data/lib/geb/samples/bootstrap_jquery/assets/images/android-chrome-192x192.png +0 -0
- data/lib/geb/samples/bootstrap_jquery/assets/images/android-chrome-512x512.png +0 -0
- data/lib/geb/samples/bootstrap_jquery/assets/images/apple-touch-icon.png +0 -0
- data/lib/geb/samples/bootstrap_jquery/assets/images/favicon-16x16.png +0 -0
- data/lib/geb/samples/bootstrap_jquery/assets/images/favicon-32x32.png +0 -0
- data/lib/geb/samples/bootstrap_jquery/assets/images/favicon.ico +0 -0
- data/lib/geb/samples/bootstrap_jquery/assets/images/hero.png +0 -0
- data/lib/geb/samples/bootstrap_jquery/assets/images/og-thumb.png +0 -0
- data/lib/geb/samples/bootstrap_jquery/assets/images/twitter-thumb.png +0 -0
- data/lib/geb/samples/bootstrap_jquery/assets/js/site.js +5 -0
- data/lib/geb/samples/bootstrap_jquery/blog/blog_post_1.html +35 -0
- data/lib/geb/samples/bootstrap_jquery/blog/blog_post_2.html +35 -0
- data/lib/geb/samples/bootstrap_jquery/blog/blog_post_3.html +35 -0
- data/lib/geb/samples/bootstrap_jquery/blog/index.html +17 -0
- data/lib/geb/samples/bootstrap_jquery/geb.config.yml +69 -0
- data/lib/geb/samples/bootstrap_jquery/index.html +11 -0
- data/lib/geb/samples/bootstrap_jquery/page.html +11 -0
- data/lib/geb/samples/bootstrap_jquery/shared/partials/_analytics.html +9 -0
- data/lib/geb/samples/bootstrap_jquery/shared/partials/_footer.html +3 -0
- data/lib/geb/samples/bootstrap_jquery/shared/partials/_global_assets.html +19 -0
- data/lib/geb/samples/bootstrap_jquery/shared/partials/_header.html +0 -0
- data/lib/geb/samples/bootstrap_jquery/shared/partials/_meta_tags.html +34 -0
- data/lib/geb/samples/bootstrap_jquery/shared/templates/_blog_post.html +0 -0
- data/lib/geb/samples/bootstrap_jquery/shared/templates/_site.html +19 -0
- data/lib/geb/samples/bootstrap_jquery/site.webmanifest +1 -0
- data/lib/geb/samples/geb.config.yml +70 -0
- data/lib/geb/server.rb +138 -0
- data/lib/geb/site/build.rb +189 -0
- data/lib/geb/site/core.rb +229 -0
- data/lib/geb/site/release.rb +70 -0
- data/lib/geb/site/remote.rb +142 -0
- data/lib/geb/site/template.rb +208 -0
- data/lib/geb/site.rb +83 -0
- data/lib/geb/template.rb +166 -0
- data/lib/geb/utilities.rb +110 -0
- data/lib/geb.rb +36 -0
- data/lib/seth.rb +50 -0
- data/sig/geb.rbs +4 -0
- data/test/api tests/test_cli.rb +200 -0
- data/test/api tests/test_config.rb +330 -0
- data/test/api tests/test_defaults.rb +62 -0
- data/test/api tests/test_git.rb +105 -0
- data/test/api tests/test_page.rb +320 -0
- data/test/api tests/test_partial.rb +152 -0
- data/test/api tests/test_server.rb +416 -0
- data/test/api tests/test_site.rb +1315 -0
- data/test/api tests/test_template.rb +249 -0
- data/test/api tests/test_utilities.rb +162 -0
- data/test/command tests/test_geb_build.rb +199 -0
- data/test/command tests/test_geb_init.rb +312 -0
- data/test/command tests/test_geb_release.rb +166 -0
- data/test/command tests/test_geb_remote.rb +66 -0
- data/test/command tests/test_geb_server.rb +122 -0
- data/test/command tests/test_geb_upload.rb +96 -0
- data/test/command tests/test_geb_version.rb +58 -0
- data/test/support/geb_api_test.rb +37 -0
- data/test/support/geb_cli_test.rb +275 -0
- data/test/support/geb_minitest_ext.rb +35 -0
- data/test/support/geb_test_helpers.rb +84 -0
- data/test/support/geb_web_server_proxy.rb +128 -0
- data/test/test_helper.rb +61 -0
- metadata +301 -0
@@ -0,0 +1,208 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This module is responsible for handling site templates, including downloading
|
4
|
+
# and extracting templates from remote URLs.
|
5
|
+
#
|
6
|
+
# @title Geb - Site - Template Module
|
7
|
+
# @author Edin Mustajbegovic <edin@actiontwelve.com>
|
8
|
+
# @copyright 2024 Edin Mustajbegovic
|
9
|
+
# @license MIT
|
10
|
+
#
|
11
|
+
# @see https://github.com/mainfram-work/geb for more information
|
12
|
+
|
13
|
+
module Geb
|
14
|
+
class Site
|
15
|
+
module Template
|
16
|
+
|
17
|
+
class InvalidTemplateURL < Geb::Error
|
18
|
+
MESSAGE = "Invalid template URL specified. Ensure the template URL is properly accessible and packaged Gab site using geb release --with_template".freeze
|
19
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
20
|
+
end # class InvalidTemplateURL < Geb::Error
|
21
|
+
|
22
|
+
class InvalidTemplateSpecification < Geb::Error
|
23
|
+
MESSAGE = "Template has no template paths defined in geb.config.yml".freeze
|
24
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
25
|
+
end # class InvalidTemplateSpecification < Geb::Error
|
26
|
+
|
27
|
+
# copy the template from the specified path to the site path. It uses template_paths from the configuration
|
28
|
+
# file to find the template. If the template is not found, it raises an error.
|
29
|
+
# @raise UnvalidatedSiteAndTemplate if the site has not been validated
|
30
|
+
# @raise InvalidTemplateSpecification if the template path is not a directory or has no geb.config.yml file
|
31
|
+
# @raise InvalidTemplateSpecification if the resolved template paths are empty
|
32
|
+
# @raise InvalidTemplateSpecification if the site cannot be loaded from the template path
|
33
|
+
# @raise InvalidTemplateSpecification if the resolved template paths cannot be copied to the site path
|
34
|
+
def copy_template_from_path
|
35
|
+
|
36
|
+
# raise an error if the site has not been validated
|
37
|
+
raise UnvalidatedSiteAndTemplate.new unless @validated
|
38
|
+
|
39
|
+
# check if the template path is a directory
|
40
|
+
raise InvalidTemplateSpecification.new("Template path [#{@template_path}] is not a directory") unless File.directory?(@template_path)
|
41
|
+
|
42
|
+
# check if the template path has a geb.config.yml file
|
43
|
+
raise InvalidTemplateSpecification.new("Template path [#{@template_path}] has no geb.config.yml file") unless Geb::Config.site_directory_has_config?(@template_path)
|
44
|
+
|
45
|
+
# create a site for the template, load it and get the site configuration
|
46
|
+
Geb.log_start "Loading template site from path #{@template_path} ... "
|
47
|
+
template_site = Geb::Site.new
|
48
|
+
Geb.no_log { template_site.load(@template_path) } # suppress logging for loading the template site
|
49
|
+
Geb.log "done."
|
50
|
+
|
51
|
+
# resolve template paths to directories and files to be copied
|
52
|
+
Geb.log_start "Resolving directories and files from template site to copy ... "
|
53
|
+
resolved_template_paths = template_site.site_config.template_paths.flat_map do |template_file_path|
|
54
|
+
Dir.glob(File.join(template_site.site_path, template_file_path))
|
55
|
+
end
|
56
|
+
Geb.log "done. Found #{resolved_template_paths.count} directories and files."
|
57
|
+
|
58
|
+
# if the resolved template paths are empty, raise an error
|
59
|
+
raise InvalidTemplateSpecification.new("Failed to load site from [#{template_site.site_path}]") if resolved_template_paths.empty?
|
60
|
+
|
61
|
+
# copy the resolved template paths to the site path
|
62
|
+
Geb.copy_paths_to_directory(template_site.site_path, resolved_template_paths, @site_path)
|
63
|
+
|
64
|
+
end # def copy_template_from_path
|
65
|
+
|
66
|
+
# bundle the site as a template archive
|
67
|
+
# @raise SiteNotFoundError if the site is not loaded
|
68
|
+
# @raise InvalidTemplateSpecification if the template paths are not specified
|
69
|
+
# @raise InvalidTemplateSpecification if the resolved template paths are empty
|
70
|
+
# @raise InvalidTemplateSpecification if the template archive cannot be created
|
71
|
+
def bundle_template
|
72
|
+
|
73
|
+
# raise an error if the site is not loaded
|
74
|
+
raise Geb::Site::SiteNotFoundError.new("Site not loaded") unless @loaded
|
75
|
+
|
76
|
+
# resolve template paths to directories and files to be copied
|
77
|
+
Geb.log_start "Resolving directories and files to include in the template archive ... "
|
78
|
+
resolved_template_paths = @site_config.template_paths.flat_map do |template_file_path|
|
79
|
+
Dir.glob(File.join(@site_path, template_file_path))
|
80
|
+
end
|
81
|
+
Geb.log "done. Found #{resolved_template_paths.count} directories and files."
|
82
|
+
|
83
|
+
# if the resolved template paths are empty, raise an error
|
84
|
+
raise InvalidTemplateSpecification.new("Config template_paths not specified.") if resolved_template_paths.empty?
|
85
|
+
|
86
|
+
# create a temporary directory for the site template
|
87
|
+
tmp_archive_directory = Dir.mktmpdir
|
88
|
+
|
89
|
+
# copy the resolved paths to the temporary directory
|
90
|
+
Geb.log "Copying directories and files to the template archive directory #{tmp_archive_directory}"
|
91
|
+
Geb.copy_paths_to_directory(@site_path, resolved_template_paths, tmp_archive_directory)
|
92
|
+
Geb.log "Done copying directories and files to the template archive directory."
|
93
|
+
|
94
|
+
# create a template archive with files from the temporary directory into the release directory
|
95
|
+
output_archive_filename = File.join(@site_path, @site_config.output_dir, Geb::Defaults::RELEASE_OUTPUT_DIR, Geb::Defaults::TEMPLATE_ARCHIVE_FILENAME)
|
96
|
+
Geb.log_start "Creating template archive in [#{output_archive_filename}] ... "
|
97
|
+
Open3.capture3("tar", "-czvf", output_archive_filename, "-C", tmp_archive_directory, ".")
|
98
|
+
Geb.log "done."
|
99
|
+
|
100
|
+
end # def bundle_template
|
101
|
+
|
102
|
+
# validate the template URL. It checks if the URL is accessible and is a tar.gz file.
|
103
|
+
# if the URL is not accessible, it tries to find the template by appending TEMPLATE_ARCHIVE_FILENAME
|
104
|
+
# this is to facilitate specifiying a top level URL. The method returns the URL if it is valid.
|
105
|
+
# @param template_url [String] the URL to the template
|
106
|
+
# @raise InvalidTemplateURL if the URL is not accessible or is not a tar.gz file
|
107
|
+
# @return [String] the validated template URL
|
108
|
+
def validate_template_url(template_url)
|
109
|
+
|
110
|
+
# get the HTTP response for the template URL
|
111
|
+
Geb.log_start "Validating template URL #{template_url} ... "
|
112
|
+
response = fetch_http_response(template_url)
|
113
|
+
Geb.log "done."
|
114
|
+
|
115
|
+
# check if the URL is accessible and is a tar.gz file, if not, try to find by appending TEMPLATE_ARCHIVE_FILENAME
|
116
|
+
unless response.is_a?(Net::HTTPSuccess) && Geb::Defaults::HTTP_TEMPLATE_CONTENT_TYPES.include?(response['Content-Type'])
|
117
|
+
|
118
|
+
# check if the URL already has the TEMPLATE_ARCHIVE_FILENAME appended, if not, append it and try again
|
119
|
+
unless template_url.end_with?(Geb::Defaults::TEMPLATE_ARCHIVE_FILENAME)
|
120
|
+
|
121
|
+
# add TEMPLATE_ARCHIVE_FILENAME to the URL (handle trailing slashes)
|
122
|
+
template_url += '/' unless template_url.end_with?('/')
|
123
|
+
template_url += Geb::Defaults::TEMPLATE_ARCHIVE_FILENAME
|
124
|
+
|
125
|
+
Geb.log ("Failed. Web server returned #{response.code}, trying to re-try with url #{template_url}") unless response.is_a?(Net::HTTPSuccess)
|
126
|
+
Geb.log ("Specified template is not a gzip archive, trying to re-try with url #{template_url}") unless Geb::Defaults::HTTP_TEMPLATE_CONTENT_TYPES.include?(response['Content-Type'])
|
127
|
+
Geb.log_start ("Trying to find geb template using URL #{template_url} ... ");
|
128
|
+
|
129
|
+
# get the HTTP response for the template URL (now modified to include the archive filename)
|
130
|
+
response = fetch_http_response(template_url)
|
131
|
+
|
132
|
+
end # unless
|
133
|
+
|
134
|
+
end # unless
|
135
|
+
|
136
|
+
# raise an error if the URL is not accessible and is not a tar.gz file
|
137
|
+
raise InvalidTemplateURL.new("Web server returned #{response.code}") unless response.is_a?(Net::HTTPSuccess)
|
138
|
+
raise InvalidTemplateURL.new("Specified template is not a gzip archive") unless Geb::Defaults::HTTP_TEMPLATE_CONTENT_TYPES.include?(response['Content-Type'])
|
139
|
+
|
140
|
+
Geb::log("Found a gzip archive at template url #{template_url}.");
|
141
|
+
|
142
|
+
return template_url
|
143
|
+
|
144
|
+
end # validate_template_url
|
145
|
+
|
146
|
+
# download the template into a temporary directory from the URl and extract it,
|
147
|
+
# return the path to the extracted template
|
148
|
+
# @param template_url [String] the URL to the template
|
149
|
+
# @raise InvalidTemplateURL if the template extraction fails
|
150
|
+
# @return [String] the path to the extracted template
|
151
|
+
def download_template_from_url(template_url)
|
152
|
+
|
153
|
+
# create a temporary directory
|
154
|
+
tmp_dir = Dir.mktmpdir
|
155
|
+
|
156
|
+
Geb.log_start "Downloading template from URL #{template_url} ... "
|
157
|
+
|
158
|
+
# download the template archive
|
159
|
+
File.open("#{tmp_dir}/#{Geb::Defaults::TEMPLATE_ARCHIVE_FILENAME}", "wb") do |file|
|
160
|
+
file.write(Net::HTTP.get(URI.parse(template_url)))
|
161
|
+
end
|
162
|
+
|
163
|
+
# extract the template archive using Open3
|
164
|
+
_, stderr, status = Open3.capture3("tar", "-xzf", "#{tmp_dir}/#{Geb::Defaults::TEMPLATE_ARCHIVE_FILENAME}", "-C", tmp_dir)
|
165
|
+
|
166
|
+
# raise an error if the template extraction failed or the geb config file is not present
|
167
|
+
raise InvalidTemplateURL.new("Failed to extract template archive: #{stderr}") unless status.success?
|
168
|
+
raise InvalidTemplateURL.new("Invalid template archive") unless File.exist?("#{tmp_dir}/geb.config.yml")
|
169
|
+
|
170
|
+
Geb.log "done. Extracted template to #{tmp_dir}."
|
171
|
+
|
172
|
+
# return the path to the extracted template
|
173
|
+
return tmp_dir
|
174
|
+
|
175
|
+
end # def ownload_template_from_url
|
176
|
+
|
177
|
+
# fetch the HTTP response for the URL
|
178
|
+
# @param url [String] the URL to fetch
|
179
|
+
# @raise InvalidTemplateURL if the URL is not accessible
|
180
|
+
# @return [Net::HTTPResponse] the HTTP response
|
181
|
+
def fetch_http_response(url)
|
182
|
+
return_response = nil
|
183
|
+
begin
|
184
|
+
return_response = Net::HTTP.get_response(URI.parse(url))
|
185
|
+
rescue StandardError => e
|
186
|
+
raise InvalidTemplateURL.new("HTTP error: #{e.message}")
|
187
|
+
end # begin
|
188
|
+
return return_response
|
189
|
+
end # def fetch_http_response
|
190
|
+
|
191
|
+
# check if the template directory exists
|
192
|
+
def template_directory_exists?(template_path)
|
193
|
+
File.directory?(template_path)
|
194
|
+
end # def template_directory_exists?
|
195
|
+
|
196
|
+
# check if the URL is a valid URL
|
197
|
+
def is_url?(url)
|
198
|
+
url =~ URI::DEFAULT_PARSER.make_regexp
|
199
|
+
end # def is_url?
|
200
|
+
|
201
|
+
# check if the template is a bundled template
|
202
|
+
def is_bundled_template?(template)
|
203
|
+
Geb::Defaults::AVAILABLE_TEMPLATES.include?(template)
|
204
|
+
end # def is_bundled_template?
|
205
|
+
|
206
|
+
end # module Template
|
207
|
+
end # class Site
|
208
|
+
end # module Geb
|
data/lib/geb/site.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Represents a site object, handles creation, building and management of sites.
|
4
|
+
#
|
5
|
+
# @title Geb - Site
|
6
|
+
# @author Edin Mustajbegovic <edin@actiontwelve.com>
|
7
|
+
# @copyright 2024 Edin Mustajbegovic
|
8
|
+
# @license MIT
|
9
|
+
#
|
10
|
+
# @see https://github.com/mainfram-work/geb for more information
|
11
|
+
|
12
|
+
# include the required libraries
|
13
|
+
require 'uri'
|
14
|
+
require 'net/http'
|
15
|
+
require 'tmpdir'
|
16
|
+
require 'open3'
|
17
|
+
require 'shellwords'
|
18
|
+
|
19
|
+
# include site modules
|
20
|
+
require_relative 'site/core'
|
21
|
+
require_relative 'site/build'
|
22
|
+
require_relative 'site/release'
|
23
|
+
require_relative 'site/template'
|
24
|
+
require_relative 'site/remote'
|
25
|
+
|
26
|
+
module Geb
|
27
|
+
class Site
|
28
|
+
|
29
|
+
# include site sub-modules
|
30
|
+
include Geb::Site::Core # core site functionality, loading, validation and creation
|
31
|
+
include Geb::Site::Build # building the site, pages, page templates, partials and assets
|
32
|
+
include Geb::Site::Release # releasing the site, pages and assets
|
33
|
+
include Geb::Site::Template # mostly remote template handling
|
34
|
+
include Geb::Site::Remote # remote site functionality, ssh, scp, rsync, etc.
|
35
|
+
|
36
|
+
# @!attribute [r] site_path
|
37
|
+
# @return [String] the site path
|
38
|
+
attr_reader :site_path
|
39
|
+
|
40
|
+
# @!attribute [r] site_config
|
41
|
+
# @return [Geb::Config] the site configuration
|
42
|
+
attr_reader :site_config
|
43
|
+
|
44
|
+
# @!attribute [r] template_path
|
45
|
+
# @return [String] the path template the site is based on
|
46
|
+
attr_reader :template_path
|
47
|
+
|
48
|
+
# @!attribute [r] validated
|
49
|
+
# @return [Boolean] true if the site is validated
|
50
|
+
attr_reader :validated
|
51
|
+
|
52
|
+
# @!attribute [r] loaded
|
53
|
+
# @return [Boolean] true if the site is loaded
|
54
|
+
attr_reader :loaded
|
55
|
+
|
56
|
+
# @!attribute [r] pages
|
57
|
+
# @return [Hash] the site pages to process
|
58
|
+
attr_reader :pages
|
59
|
+
|
60
|
+
# site constructor
|
61
|
+
# initializes the site object and attributes
|
62
|
+
def initialize
|
63
|
+
|
64
|
+
@validated = false
|
65
|
+
@loaded = false
|
66
|
+
@site_path = nil
|
67
|
+
@template_path = nil
|
68
|
+
@pages = {}
|
69
|
+
|
70
|
+
end # def initialize
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# get the site name. It either uses the configured name or the last folder in
|
75
|
+
# the site_path as site name.
|
76
|
+
# @return [String] the site name
|
77
|
+
def site_name
|
78
|
+
return @site_config.site_name
|
79
|
+
end # def site_name
|
80
|
+
|
81
|
+
end # class Site
|
82
|
+
|
83
|
+
end # module Geb
|
data/lib/geb/template.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Represents a page template. A page template is a page file that contains
|
4
|
+
# template definitions and insert sections. The class keeps a cache of
|
5
|
+
# already loaded page templates.
|
6
|
+
#
|
7
|
+
# @title Geb - Template
|
8
|
+
# @author Edin Mustajbegovic <edin@actiontwelve.com>
|
9
|
+
# @copyright 2024 Edin Mustajbegovic
|
10
|
+
# @license MIT
|
11
|
+
#
|
12
|
+
# @todo Make template, section and insert patterns configurable
|
13
|
+
#
|
14
|
+
# @see https://github.com/mainfram-work/geb for more information
|
15
|
+
|
16
|
+
require 'fileutils'
|
17
|
+
|
18
|
+
module Geb
|
19
|
+
class Template
|
20
|
+
|
21
|
+
TEMPLATE_PATTERN = /<% template: (.*?) %>/
|
22
|
+
SECTION_PATTERN = /<% start: (.*?) %>(.*?)<% end: \1 %>/m
|
23
|
+
INSERT_PATTERN = /<%= insert: (.*?) %>/
|
24
|
+
|
25
|
+
class TemplateFileNotFound < Geb::Error
|
26
|
+
MESSAGE = "Template file not found".freeze
|
27
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
28
|
+
end # class TemplateFileNotFound < Geb::Error
|
29
|
+
|
30
|
+
class TemplateFileReadFailure < Geb::Error
|
31
|
+
MESSAGE = "Failed to read the template file.".freeze
|
32
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
33
|
+
end # class TemplateFileReadFailure < Geb::Error
|
34
|
+
|
35
|
+
# define a class level cache for loaded template objects
|
36
|
+
@@loaded_templates = {}
|
37
|
+
|
38
|
+
# class method to expire the template cache
|
39
|
+
def self.expire_cache
|
40
|
+
@@loaded_templates = {}
|
41
|
+
end # def self.expire_cache
|
42
|
+
|
43
|
+
# class method to initialise a template if it is not already loaded, otherwise, return the cached template
|
44
|
+
# @param template_path [String] the path to the template file
|
45
|
+
# @return [Geb::Template] the template object
|
46
|
+
def self.load(template_path)
|
47
|
+
|
48
|
+
# initialise a return template object
|
49
|
+
return_template = nil
|
50
|
+
|
51
|
+
# check if the template is already loaded
|
52
|
+
if @@loaded_templates.key?(template_path)
|
53
|
+
|
54
|
+
# return the cached template
|
55
|
+
Geb.log " - using cached template: #{template_path}"
|
56
|
+
return_template = @@loaded_templates[template_path]
|
57
|
+
|
58
|
+
else
|
59
|
+
|
60
|
+
# create a new template object
|
61
|
+
return_template = Template.new(template_path)
|
62
|
+
|
63
|
+
# add the template to the cache
|
64
|
+
@@loaded_templates[template_path] = return_template
|
65
|
+
|
66
|
+
end # if else
|
67
|
+
|
68
|
+
# return the new template object
|
69
|
+
return return_template
|
70
|
+
|
71
|
+
end # def self.load
|
72
|
+
|
73
|
+
# extract the template path from the page content
|
74
|
+
# @param page_content [String] the page content
|
75
|
+
# @return [String] the template path, or nil if no template path is found
|
76
|
+
# @note the function looks for tags like this <% template: shared/templates/_site.html %> in the page content
|
77
|
+
def self.extract_template_path(page_content)
|
78
|
+
|
79
|
+
# match the template pattern and return the template path
|
80
|
+
match = page_content.match(TEMPLATE_PATTERN)
|
81
|
+
|
82
|
+
# return the template path or nil if no match
|
83
|
+
return match ? match[1].strip : nil
|
84
|
+
|
85
|
+
end # def self.extract_template_path
|
86
|
+
|
87
|
+
# extract the sections for the template from the page content
|
88
|
+
# @param page_content [String] the page content
|
89
|
+
# @return [Hash] the sections for the template, key is the section name, value is the section content
|
90
|
+
# @note the function looks for tags like this <% start: header %> ... <% end: header %> in the page content and returns whats in between
|
91
|
+
def self.extract_sections_for_template(page_content)
|
92
|
+
|
93
|
+
# initialise the sections for the template hash
|
94
|
+
sections_for_template = {}
|
95
|
+
|
96
|
+
# scan the page content for sections and add them to the hash
|
97
|
+
page_content.scan(SECTION_PATTERN).each do |section|
|
98
|
+
sections_for_template[section[0].strip] = section[1].strip
|
99
|
+
end # scan
|
100
|
+
|
101
|
+
# return the sections for the template
|
102
|
+
return sections_for_template
|
103
|
+
|
104
|
+
end # def self.extract_sections_for_template
|
105
|
+
|
106
|
+
# @!attribute [r] path
|
107
|
+
# @return [String] the path to the template file
|
108
|
+
attr_reader :path
|
109
|
+
|
110
|
+
# @!attribute [r] content
|
111
|
+
# @return [String] the content of the template file
|
112
|
+
attr_reader :content
|
113
|
+
|
114
|
+
# Template class constructor
|
115
|
+
# @param template_path [String] the path to the template file
|
116
|
+
# @return [Geb::Template] the template object
|
117
|
+
def initialize(template_path)
|
118
|
+
|
119
|
+
# set the specified template path and initialise the content
|
120
|
+
@path = template_path
|
121
|
+
@content = nil
|
122
|
+
|
123
|
+
# check if the template file exists
|
124
|
+
raise TemplateFileNotFound.new(template_path) unless template_file_exists?()
|
125
|
+
|
126
|
+
Geb.log " - loading template: #{@path}"
|
127
|
+
|
128
|
+
# read the template file, raise an error if the file could not be read
|
129
|
+
begin
|
130
|
+
@content = File.read(template_path)
|
131
|
+
rescue => e
|
132
|
+
raise TemplateFileReadFailure.new(e.message)
|
133
|
+
end # begin
|
134
|
+
|
135
|
+
end # def initialize
|
136
|
+
|
137
|
+
# parse the page content sections and replace the sections in the template with the page section content
|
138
|
+
# @param page_content_sections [Hash] the page content sections, key is the section name, value is the section content
|
139
|
+
# @return [String] the parsed template content
|
140
|
+
# @note the function looks for tags like this <%= insert: header %> in the template content
|
141
|
+
def parse(page_content_sections)
|
142
|
+
|
143
|
+
# create a duplicate of the template content
|
144
|
+
return_content = @content.dup
|
145
|
+
|
146
|
+
# step through the page content sections and replace the insert sections in the template with the page content
|
147
|
+
return_content.gsub!(INSERT_PATTERN) do |match|
|
148
|
+
section_name = match.match(INSERT_PATTERN)[1].strip
|
149
|
+
page_content_sections[section_name] || match
|
150
|
+
end # return_content.gsub!
|
151
|
+
|
152
|
+
# return the content
|
153
|
+
return return_content
|
154
|
+
|
155
|
+
end # def parse
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
# check if the template file exists
|
160
|
+
# @return [Boolean] true if the template file exists, otherwise false
|
161
|
+
def template_file_exists?
|
162
|
+
return File.exist?(@path)
|
163
|
+
end # def template_file_exists?
|
164
|
+
|
165
|
+
end # class Template
|
166
|
+
end # module Geb
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# A simple list of Geb utilities for the Geb gem
|
4
|
+
#
|
5
|
+
# @title Geb - Utilities
|
6
|
+
# @author Edin Mustajbegovic <edin@actiontwelve.com>
|
7
|
+
# @copyright 2024 Edin Mustajbegovic
|
8
|
+
# @license MIT
|
9
|
+
#
|
10
|
+
# @see https://github.com/mainfram-work/geb for more information
|
11
|
+
|
12
|
+
module Geb
|
13
|
+
|
14
|
+
# define the main error class
|
15
|
+
class Error < StandardError
|
16
|
+
|
17
|
+
# initialize the error class
|
18
|
+
# @param custom_error [String] the custom error message
|
19
|
+
# @param default_message [String] the default error message
|
20
|
+
# @return [Geb::Error] the error object
|
21
|
+
# @raise [Geb::Error] the error object
|
22
|
+
def initialize(custom_error = "", default_message = "")
|
23
|
+
|
24
|
+
# set the error message
|
25
|
+
message = custom_error.empty? ? default_message : "#{custom_error} #{default_message}"
|
26
|
+
|
27
|
+
# call the parent class constructor
|
28
|
+
super(message)
|
29
|
+
|
30
|
+
end # def initialize
|
31
|
+
|
32
|
+
end # class Error < StandardError
|
33
|
+
|
34
|
+
# initialise a flag for suppressing output
|
35
|
+
@@suppress_log_output = false
|
36
|
+
|
37
|
+
# log method for printing messages to the console
|
38
|
+
# @param message [String] the message to print
|
39
|
+
# @note this method will print a newline character at the end of the message
|
40
|
+
# @note this method will print the message to the console if the suppress log output flag is not set
|
41
|
+
def self.log(message)
|
42
|
+
puts message unless @@suppress_log_output
|
43
|
+
end # def self.log
|
44
|
+
|
45
|
+
# log method for printing messages to the console
|
46
|
+
# @param message [String] the message to print
|
47
|
+
# @note this method will not print a newline character at the end of the message
|
48
|
+
# @note this method will print the message to the console if the suppress log output flag is not set
|
49
|
+
def self.log_start(message)
|
50
|
+
print message unless @@suppress_log_output
|
51
|
+
end # def self.log
|
52
|
+
|
53
|
+
# method to suppress log output within a block
|
54
|
+
# @return [Object] the return value of the block
|
55
|
+
# @yield the block to suppress log output
|
56
|
+
def self.no_log
|
57
|
+
|
58
|
+
# store the original value of the suppress log output flag (just in case)
|
59
|
+
original_value = @@suppress_log_output
|
60
|
+
|
61
|
+
# set the suppress log output flag
|
62
|
+
@@suppress_log_output = true
|
63
|
+
|
64
|
+
# yield the block
|
65
|
+
yield
|
66
|
+
|
67
|
+
ensure
|
68
|
+
|
69
|
+
# reset the suppress log output flag
|
70
|
+
@@suppress_log_output = original_value
|
71
|
+
|
72
|
+
end # def self.no_log
|
73
|
+
|
74
|
+
# copy the specified file and directory paths to the destination directory
|
75
|
+
# @param source_path [String] the source directory
|
76
|
+
# @param paths [Array] the paths to copy (still full paths, but from source directory)
|
77
|
+
# @param destination_path [String] the destination directory
|
78
|
+
# @param quiet [Boolean] the flag to suppress output
|
79
|
+
# @raise [Geb::Error] if the any of the file operations fail
|
80
|
+
def self.copy_paths_to_directory(source_path, paths, destination_path, quiet = false)
|
81
|
+
|
82
|
+
# step through the resolved template paths and copy them to the site path, taking care of files vs directories
|
83
|
+
paths.each do |path|
|
84
|
+
|
85
|
+
# get the relative path of the resolved template path and build the destination path
|
86
|
+
relative_template_path = path.gsub(source_path, "")
|
87
|
+
destination_file_path = File.join(destination_path, relative_template_path)
|
88
|
+
|
89
|
+
# ensure the destination directory exists
|
90
|
+
FileUtils.mkdir_p(File.dirname(destination_path))
|
91
|
+
|
92
|
+
# copy the resolved template path to the destination path
|
93
|
+
if File.directory?(path)
|
94
|
+
Geb.log_start " - copying directory and all sub-directories from #{path} to #{destination_file_path} ... " unless quiet
|
95
|
+
FileUtils.cp_r(path, destination_file_path)
|
96
|
+
else
|
97
|
+
Geb.log_start " - copying file from #{path} to #{destination_file_path} ... " unless quiet
|
98
|
+
FileUtils.cp(path, destination_file_path)
|
99
|
+
end # if else
|
100
|
+
Geb.log "done." unless quiet
|
101
|
+
|
102
|
+
end # each
|
103
|
+
|
104
|
+
rescue Exception => e
|
105
|
+
raise Geb::Error.new("Failed to copy paths to directory: #{e.message}")
|
106
|
+
|
107
|
+
end # def self.copy_paths_to_directory
|
108
|
+
|
109
|
+
|
110
|
+
end # module Geb
|
data/lib/geb.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Main module definition for the Geb gem, it includes all the functionality
|
4
|
+
# and modules for the Geb gem.
|
5
|
+
#
|
6
|
+
# @title Geb
|
7
|
+
# @author Edin Mustajbegovic <edin@actiontwelve.com>
|
8
|
+
# @copyright 2024 Edin Mustajbegovic
|
9
|
+
# @license MIT
|
10
|
+
#
|
11
|
+
# @see https://github.com/mainfram-work/geb for more information
|
12
|
+
|
13
|
+
# Define the main module, version and main error class
|
14
|
+
module Geb
|
15
|
+
|
16
|
+
# define the version of the gem
|
17
|
+
VERSION = "0.1.11"
|
18
|
+
|
19
|
+
end # module Geb
|
20
|
+
|
21
|
+
# include external libraries
|
22
|
+
require "dry/cli"
|
23
|
+
|
24
|
+
# include geb libraries
|
25
|
+
require_relative "geb/defaults"
|
26
|
+
require_relative "geb/utilities"
|
27
|
+
require_relative "geb/config"
|
28
|
+
require_relative "geb/git"
|
29
|
+
require_relative "geb/site"
|
30
|
+
require_relative "geb/page"
|
31
|
+
require_relative "geb/template"
|
32
|
+
require_relative "geb/partial"
|
33
|
+
require_relative "geb/server"
|
34
|
+
|
35
|
+
# make sure geb/cli is loaded last
|
36
|
+
require_relative "geb/cli"
|
data/lib/seth.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Module with some pretty crazy things.
|
4
|
+
# Seth is an egyption god of chaos and mischief. This code is a tribute to him.
|
5
|
+
# Some of the thigns here run at the very bassis of the Ruby language and change
|
6
|
+
# how standard things work. This is not for the faint of heart.
|
7
|
+
#
|
8
|
+
# @title Geb
|
9
|
+
# @author Edin Mustajbegovic <edin@actiontwelve.com>
|
10
|
+
# @copyright 2024 Edin Mustajbegovic
|
11
|
+
# @license MIT
|
12
|
+
#
|
13
|
+
# @see https://github.com/mainfram-work/geb for more information
|
14
|
+
|
15
|
+
module Seth
|
16
|
+
|
17
|
+
# Suppress warnings within the block being executed. This is useful for testing.
|
18
|
+
# @example
|
19
|
+
#
|
20
|
+
# suppress_warnings do
|
21
|
+
# # code that generates warnings
|
22
|
+
# end
|
23
|
+
# # no warnings will be printed
|
24
|
+
#
|
25
|
+
# @yield []
|
26
|
+
# @return [void]
|
27
|
+
def suppress_warnings
|
28
|
+
|
29
|
+
# save the original verbose setting
|
30
|
+
original_verbose = $VERBOSE
|
31
|
+
|
32
|
+
# suppress warnings
|
33
|
+
$VERBOSE = nil
|
34
|
+
|
35
|
+
# execute the block
|
36
|
+
yield
|
37
|
+
|
38
|
+
ensure
|
39
|
+
|
40
|
+
# restore the original verbose setting
|
41
|
+
$VERBOSE = original_verbose
|
42
|
+
|
43
|
+
end # def suppress_warnings
|
44
|
+
|
45
|
+
# can you imagine what this does? ;)
|
46
|
+
module_function :suppress_warnings
|
47
|
+
|
48
|
+
end # module Seth
|
49
|
+
|
50
|
+
puts "Seth has been loaded. Hang on to your hats!. Seth is here: #{__FILE__}"
|
data/sig/geb.rbs
ADDED