geb 0.1.11
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/.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