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,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# Site building functionality, building assets and pages
|
|
4
|
+
#
|
|
5
|
+
# @title Geb - Site - Build Module
|
|
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
|
+
class Site
|
|
14
|
+
module Build
|
|
15
|
+
|
|
16
|
+
class SiteNotLoadedError < Geb::Error
|
|
17
|
+
MESSAGE = "Site not loaded.".freeze
|
|
18
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
|
19
|
+
end # class SiteNotLoadedError < Geb::Error
|
|
20
|
+
|
|
21
|
+
class FailedToOutputSite < Geb::Error
|
|
22
|
+
MESSAGE = "Failed to output site.".freeze
|
|
23
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
|
24
|
+
end # class FailedToOutputSite < Geb::Error
|
|
25
|
+
|
|
26
|
+
# build the site
|
|
27
|
+
def build
|
|
28
|
+
|
|
29
|
+
# make sure the site is laoded, if not, raise an error
|
|
30
|
+
raise SiteNotLoadedError.new("Could not build the site.") unless @loaded
|
|
31
|
+
|
|
32
|
+
# build the assets and pages
|
|
33
|
+
# it is important to build pages first as there may be pages in the assets directory
|
|
34
|
+
build_pages()
|
|
35
|
+
build_assets()
|
|
36
|
+
|
|
37
|
+
end # def build
|
|
38
|
+
|
|
39
|
+
# build the assets for the site
|
|
40
|
+
# @raise SiteNotLoaded if the site is not loaded
|
|
41
|
+
def build_assets
|
|
42
|
+
|
|
43
|
+
# make sure the site is laoded, if not, raise an error
|
|
44
|
+
raise SiteNotLoadedError.new("Could not build assets.") unless @loaded
|
|
45
|
+
|
|
46
|
+
# get the site asset and output assets directory
|
|
47
|
+
site_assets_dir = get_site_assets_directory()
|
|
48
|
+
outout_assets_dir = File.join(get_site_output_directory(), site_assets_dir.gsub(@site_path, ""))
|
|
49
|
+
|
|
50
|
+
Geb.log "Building assets for #{site_name}\n\n"
|
|
51
|
+
|
|
52
|
+
# step through all the asset files and copy them to the output directory
|
|
53
|
+
Dir.glob("#{site_assets_dir}/**/*").each do |asset_file|
|
|
54
|
+
|
|
55
|
+
# skip directories
|
|
56
|
+
next if File.directory?(asset_file)
|
|
57
|
+
|
|
58
|
+
# get the relative path of the asset file and the destination path
|
|
59
|
+
asset_relative_path = asset_file.gsub(site_assets_dir, "")
|
|
60
|
+
asset_full_destination_path = File.join(outout_assets_dir, asset_relative_path)
|
|
61
|
+
|
|
62
|
+
# check if the destination asset file exists
|
|
63
|
+
if File.exist?(asset_full_destination_path)
|
|
64
|
+
|
|
65
|
+
Geb.log " - skipping asset: #{asset_relative_path}"
|
|
66
|
+
|
|
67
|
+
else
|
|
68
|
+
|
|
69
|
+
Geb.log " - processing asset: #{asset_relative_path}"
|
|
70
|
+
|
|
71
|
+
# create the output directory for the asset file
|
|
72
|
+
output_dir = File.join(outout_assets_dir, File.dirname(asset_relative_path))
|
|
73
|
+
FileUtils.mkdir_p(output_dir)
|
|
74
|
+
|
|
75
|
+
# copy the asset file to the output directory
|
|
76
|
+
FileUtils.cp(asset_file, output_dir)
|
|
77
|
+
|
|
78
|
+
end # if else
|
|
79
|
+
|
|
80
|
+
end # Dir.glob
|
|
81
|
+
Geb.log "\nDone building assets for #{site_name}"
|
|
82
|
+
|
|
83
|
+
end # def build_assets
|
|
84
|
+
|
|
85
|
+
# build the pages for the site
|
|
86
|
+
# @raise SiteNotLoaded if the site is not loaded
|
|
87
|
+
def build_pages
|
|
88
|
+
|
|
89
|
+
# make sure the site is laoded, if not, raise an error
|
|
90
|
+
raise SiteNotLoadedError.new("Could not build pages.") unless @loaded
|
|
91
|
+
|
|
92
|
+
# expire page template and partial caches
|
|
93
|
+
Geb::Template.expire_cache
|
|
94
|
+
Geb::Partial.expire_cache
|
|
95
|
+
|
|
96
|
+
# find all HTML files in the site path that don't start with an underscore, with extention .html and .htm
|
|
97
|
+
page_files = get_page_files(@site_path,
|
|
98
|
+
@site_config.page_extensions(),
|
|
99
|
+
@site_config.template_and_partial_identifier(),
|
|
100
|
+
[get_site_output_directory(), get_site_release_directory()])
|
|
101
|
+
|
|
102
|
+
Geb.log "Building #{page_files.length} pages for #{site_name}"
|
|
103
|
+
|
|
104
|
+
# create a temporary directory
|
|
105
|
+
Dir.mktmpdir do |tmp_dir|
|
|
106
|
+
|
|
107
|
+
# iterate over the HTML files and build the pages
|
|
108
|
+
page_files.each do |page_file|
|
|
109
|
+
|
|
110
|
+
# create a new page object and buid the page into the temporary directory
|
|
111
|
+
page = Geb::Page.new(self, page_file)
|
|
112
|
+
page.build(tmp_dir)
|
|
113
|
+
|
|
114
|
+
end # html_files.each
|
|
115
|
+
Geb.log "\nDone building #{page_files.length} pages for #{site_name}"
|
|
116
|
+
|
|
117
|
+
# attempt to write the site to the output directory
|
|
118
|
+
begin
|
|
119
|
+
|
|
120
|
+
# clear the output directory
|
|
121
|
+
Geb.log_start "Clearing site output folder #{get_site_output_directory()} ... "
|
|
122
|
+
clear_site_output_directory()
|
|
123
|
+
Geb.log "done."
|
|
124
|
+
|
|
125
|
+
# copy the files to the output directory
|
|
126
|
+
Geb.log_start "Outputting site to #{get_site_output_directory()} ... "
|
|
127
|
+
output_site(tmp_dir)
|
|
128
|
+
Geb.log "done."
|
|
129
|
+
|
|
130
|
+
rescue => e
|
|
131
|
+
raise FailedToOutputSite.new(e.message)
|
|
132
|
+
end # begin rescue
|
|
133
|
+
|
|
134
|
+
end # Dir.mktmpdir
|
|
135
|
+
|
|
136
|
+
end # def build_pages
|
|
137
|
+
|
|
138
|
+
# get the site output directory
|
|
139
|
+
# @return [String] the site output directory
|
|
140
|
+
def get_site_output_directory
|
|
141
|
+
return File.join(@site_path, @site_config.output_dir, Geb::Defaults::LOCAL_OUTPUT_DIR)
|
|
142
|
+
end # def get_site_output_directory
|
|
143
|
+
|
|
144
|
+
# get the page files in the specified path, with specified extentions and ignoring files that match the pattern
|
|
145
|
+
# @param path [String] the path to the files
|
|
146
|
+
# @param exts [Array] the extentions to look for, default is Geb::Defaults.PAGE_EXTENSIONS
|
|
147
|
+
# @param ignore_files_exp [Regexp] the pattern to ignore files, default is Geb::Defaults.TEMPLATE_AND_PARTIAL_IDENTIFIER
|
|
148
|
+
# @param ignore_directories [Array] the directories to ignore, default is []
|
|
149
|
+
# @return [Array] the array of matched file paths
|
|
150
|
+
# @note the ignore_files_exp and ignore_directories are used to ignore files that are not pages
|
|
151
|
+
def get_page_files(path, exts = Geb::Defaults::PAGE_EXTENSIONS, ignore_files_exp = Geb::Defaults::TEMPLATE_AND_PARTIAL_IDENTIFIER, ignore_directories = [])
|
|
152
|
+
|
|
153
|
+
# get all files in the path with the specified extentions
|
|
154
|
+
files = Dir.glob("#{path}/**/*{#{exts.join(',')}}")
|
|
155
|
+
|
|
156
|
+
# reject files that match the ignore pattern and that are within the output or release directories
|
|
157
|
+
files.reject! do |file|
|
|
158
|
+
File.basename(file) =~ ignore_files_exp ||
|
|
159
|
+
ignore_directories.any? { |dir| file.start_with?(dir) }
|
|
160
|
+
end # files.reject!
|
|
161
|
+
|
|
162
|
+
# return the array of matched file paths
|
|
163
|
+
return files
|
|
164
|
+
|
|
165
|
+
end # def get_page_files
|
|
166
|
+
|
|
167
|
+
# clear the site output directory
|
|
168
|
+
# @return [Nil]
|
|
169
|
+
def clear_site_output_directory
|
|
170
|
+
FileUtils.rm_rf(Dir.glob("#{get_site_output_directory()}/*"))
|
|
171
|
+
end # def clear_site_output_directory
|
|
172
|
+
|
|
173
|
+
# output the site from specified directory to the output directory. The specified directory is typically
|
|
174
|
+
# a temporary directory where the site has been built.
|
|
175
|
+
# @param output_dir [String] the directory to output
|
|
176
|
+
# @return [Nil]
|
|
177
|
+
def output_site(output_dir)
|
|
178
|
+
FileUtils.cp_r("#{output_dir}/.", get_site_output_directory())
|
|
179
|
+
end # def output_site
|
|
180
|
+
|
|
181
|
+
# get the site assets directory
|
|
182
|
+
# @return [String] the site assets directory
|
|
183
|
+
def get_site_assets_directory
|
|
184
|
+
return File.join(@site_path, @site_config.assets_dir)
|
|
185
|
+
end # def get_site_assets_directory
|
|
186
|
+
|
|
187
|
+
end # module Build
|
|
188
|
+
end # class Site
|
|
189
|
+
end # module Geb
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# Site core functionality, loading, validation and creation
|
|
4
|
+
#
|
|
5
|
+
# @title Geb - Site - Core Module
|
|
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
|
+
class Site
|
|
14
|
+
module Core
|
|
15
|
+
|
|
16
|
+
class DirectoryExistsError < Geb::Error
|
|
17
|
+
MESSAGE = "Site folder already exists, please choose a different name or location.\nIf you want to use the existing site folder, use the --force option.".freeze
|
|
18
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
|
19
|
+
end # class DirectoryExistsError < Geb::Error
|
|
20
|
+
|
|
21
|
+
class SiteAlreadyValidated < Geb::Error
|
|
22
|
+
MESSAGE = "Proposed site and template have not been validated. This is an internal error".freeze
|
|
23
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
|
24
|
+
end # class SiteAlreadyValidated < Geb::Error
|
|
25
|
+
|
|
26
|
+
class InvalidTemplate < Geb::Error
|
|
27
|
+
MESSAGE = "Invalid template site. Make sure the specified path is a directory and contains a valid geb.config.yml file.".freeze
|
|
28
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
|
29
|
+
end # class InvalidTemplate < Geb::Error
|
|
30
|
+
|
|
31
|
+
class UnvalidatedSiteAndTemplate < Geb::Error
|
|
32
|
+
MESSAGE = "You are trying to create an unvalidated site. This is an internal error".freeze
|
|
33
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
|
34
|
+
end # class UnvalidatedSiteAndTemplate < Geb::Error
|
|
35
|
+
|
|
36
|
+
class SiteNotFoundError < Geb::Error
|
|
37
|
+
MESSAGE = "Could not find geb config file.".freeze
|
|
38
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
|
39
|
+
end # class SiteNotFoundError < Geb::Error
|
|
40
|
+
|
|
41
|
+
# validate the site path and template path, and set the validated flag
|
|
42
|
+
# it executes the following validations:
|
|
43
|
+
# - make sure the site is not already validated
|
|
44
|
+
# - make sure the site path is valid, consider force option
|
|
45
|
+
# - if template path is nil, use the default template
|
|
46
|
+
# - if template path is a URL, validate the URL and download the template
|
|
47
|
+
# - if template path is a directory, check if it has a geb.config.yml file
|
|
48
|
+
# @param site_path [String] the path to the site folder
|
|
49
|
+
# @param template_path [String] the path to the site template, default is nil, can be a URL, directory or a bundled template identifier
|
|
50
|
+
# @param skip_template [Boolean] skip the template validation, default is false
|
|
51
|
+
# @param force [Boolean] force the site creation, default is false
|
|
52
|
+
# @raise SiteAlreadyValidated if the site has already been validated
|
|
53
|
+
# @raise DirectoryExistsError if the site folder already exists and force option is not set
|
|
54
|
+
# @raise InvalidTemplate if the template path is invalid
|
|
55
|
+
# @raise InvalidTemplateURL if the template URL is invalid
|
|
56
|
+
# @return [Nil]
|
|
57
|
+
def validate(site_path, template_path = nil, skip_template = false, force = false)
|
|
58
|
+
|
|
59
|
+
# raise an error if the site has already been validated
|
|
60
|
+
raise SiteAlreadyValidated.new if @validated
|
|
61
|
+
|
|
62
|
+
Geb.log_start "Validating site path #{site_path} ... "
|
|
63
|
+
# raise error if site folder already exists and force option is not set
|
|
64
|
+
raise DirectoryExistsError.new if site_directory_exists?(site_path) && !force
|
|
65
|
+
@site_path = site_path
|
|
66
|
+
Geb.log('done.')
|
|
67
|
+
|
|
68
|
+
Geb.log("Skipping template validation as told.") if skip_template
|
|
69
|
+
|
|
70
|
+
# check if we are skipping the template
|
|
71
|
+
unless skip_template
|
|
72
|
+
|
|
73
|
+
# initialize the template directory path.
|
|
74
|
+
template_dir = nil
|
|
75
|
+
|
|
76
|
+
# if the template path is nil, use the first bundled template name
|
|
77
|
+
if template_path.nil? || template_path.empty?
|
|
78
|
+
|
|
79
|
+
Geb.log "No template specified, using default: #{Geb::Defaults::DEFAULT_TEMPLATE}."
|
|
80
|
+
template_dir = Geb::Defaults::DEFAULT_TEMPLATE_DIR
|
|
81
|
+
|
|
82
|
+
end # if
|
|
83
|
+
|
|
84
|
+
# check if the template path is a URL
|
|
85
|
+
if is_url?(template_path) && template_dir.nil?
|
|
86
|
+
|
|
87
|
+
# check if the template URL is valid and download it if it is
|
|
88
|
+
valid_template_url = validate_template_url(template_path)
|
|
89
|
+
template_dir = download_template_from_url(valid_template_url)
|
|
90
|
+
|
|
91
|
+
end # if
|
|
92
|
+
|
|
93
|
+
# check if the template path is a bundled template
|
|
94
|
+
if template_dir.nil? && is_bundled_template?(template_path)
|
|
95
|
+
|
|
96
|
+
template_dir = File.join(Geb::Defaults::BUNDLED_TEMPLATES_DIR, template_path)
|
|
97
|
+
Geb.log "Specified template is a Geb sample: #{template_path}, using it as site template."
|
|
98
|
+
|
|
99
|
+
end # if
|
|
100
|
+
|
|
101
|
+
# set the template dir to specified template path if template dir is still nil
|
|
102
|
+
template_dir = template_path if template_dir.nil? # this is the case when the template is a local directory
|
|
103
|
+
|
|
104
|
+
# check if the template path is a directory and ontains a geb.config.yml file
|
|
105
|
+
Geb.log_start "Validating template path #{template_dir.to_s} ... "
|
|
106
|
+
raise InvalidTemplate.new if template_dir.nil?
|
|
107
|
+
raise InvalidTemplate.new unless template_directory_exists?(template_dir)
|
|
108
|
+
raise InvalidTemplate.new unless Geb::Config.site_directory_has_config?(template_dir)
|
|
109
|
+
Geb.log "done."
|
|
110
|
+
|
|
111
|
+
# set the template path
|
|
112
|
+
@template_path = template_dir
|
|
113
|
+
|
|
114
|
+
end # unless skip_template
|
|
115
|
+
|
|
116
|
+
# set the validated flag
|
|
117
|
+
@validated = true
|
|
118
|
+
|
|
119
|
+
end # def validate
|
|
120
|
+
|
|
121
|
+
# create the site. It assumes and checks that the site has been validated first.
|
|
122
|
+
# the reason we don't just call validate from here is that we want to separate
|
|
123
|
+
# the validation from the creation for CLI UI purposes.
|
|
124
|
+
# performs the following steps
|
|
125
|
+
# - raise an error if the site has not been validated
|
|
126
|
+
# - create the site folder, if it exists, just skip it
|
|
127
|
+
# - copy the template files to the site folder if the template path is set
|
|
128
|
+
# - create the output folders
|
|
129
|
+
# @raise UnvalidatedSiteAndTemplate if the site has not been validated
|
|
130
|
+
# @return [Nil]
|
|
131
|
+
def create
|
|
132
|
+
|
|
133
|
+
# raise an error if the site has not been validated
|
|
134
|
+
raise UnvalidatedSiteAndTemplate.new unless @validated
|
|
135
|
+
|
|
136
|
+
# check if the folder already exists, if we are here and it does, just skip it, validation would have considered a force option
|
|
137
|
+
Geb.log_start "Creating site folder: #{@site_path} ... "
|
|
138
|
+
if site_directory_exists?(@site_path)
|
|
139
|
+
Geb.log "skipped, folder already exists."
|
|
140
|
+
else
|
|
141
|
+
Dir.mkdir(@site_path)
|
|
142
|
+
Geb.log "done."
|
|
143
|
+
end # if
|
|
144
|
+
|
|
145
|
+
Geb.log("Skipping template creation as told.") if @template_path.nil?
|
|
146
|
+
|
|
147
|
+
# check if we are skipping the template, if not copy the template files
|
|
148
|
+
copy_template_from_path unless @template_path.nil?
|
|
149
|
+
|
|
150
|
+
# check if the site has a geb config file, if not, copy the default one
|
|
151
|
+
if Geb::Config.site_directory_has_config?(@site_path)
|
|
152
|
+
Geb.log "Config file already exists, no need to create it."
|
|
153
|
+
else
|
|
154
|
+
Geb.log_start "Creating default geb config file ... "
|
|
155
|
+
FileUtils.cp(File.join(Geb::Defaults::BUNDLED_TEMPLATES_DIR, Geb::Defaults::SITE_CONFIG_FILENAME), @site_path)
|
|
156
|
+
Geb.log "done."
|
|
157
|
+
end # if else
|
|
158
|
+
|
|
159
|
+
# load the site config
|
|
160
|
+
@site_config = Geb::Config.new(self)
|
|
161
|
+
|
|
162
|
+
# create the assets folder if it does not exist
|
|
163
|
+
if File.directory?(File.join(@site_path, @site_config.assets_dir))
|
|
164
|
+
Geb.log "Assets folder already exists, no need to create it."
|
|
165
|
+
else
|
|
166
|
+
Geb.log_start "Creating assets folder since it wasn't created by the template ... "
|
|
167
|
+
FileUtils.mkdir_p(File.join(@site_path, @site_config.assets_dir))
|
|
168
|
+
Geb.log "done."
|
|
169
|
+
end # if else
|
|
170
|
+
|
|
171
|
+
# create the output folders
|
|
172
|
+
Geb.log_start "Creating: local and release output folders ..."
|
|
173
|
+
FileUtils.mkdir_p(File.join(@site_path, @site_config.output_dir, Geb::Defaults::LOCAL_OUTPUT_DIR))
|
|
174
|
+
FileUtils.mkdir_p(File.join(@site_path, @site_config.output_dir, Geb::Defaults::RELEASE_OUTPUT_DIR))
|
|
175
|
+
Geb.log "done."
|
|
176
|
+
|
|
177
|
+
end # def create
|
|
178
|
+
|
|
179
|
+
# load a site from a site path
|
|
180
|
+
# it checks if the site path has a geb config file, if not, it goes up the chain to find it
|
|
181
|
+
# @param site_path [String] the path to the site folder
|
|
182
|
+
# @raise SiteNotFoundError if the site path is not found
|
|
183
|
+
# @return [Nil]
|
|
184
|
+
def load(site_path)
|
|
185
|
+
|
|
186
|
+
Geb.log_start "Loading site from path #{site_path} ... "
|
|
187
|
+
|
|
188
|
+
# set the site path candidate
|
|
189
|
+
site_path_candidate = site_path
|
|
190
|
+
|
|
191
|
+
# check if the site has a geb config file, if not go up the chain to find it
|
|
192
|
+
until site_path_candidate == '/'
|
|
193
|
+
|
|
194
|
+
# check if the site path has a geb config file
|
|
195
|
+
if Geb::Config.site_directory_has_config?(site_path_candidate)
|
|
196
|
+
|
|
197
|
+
# set the site path
|
|
198
|
+
@site_path = site_path_candidate
|
|
199
|
+
|
|
200
|
+
# load the site configuration
|
|
201
|
+
@site_config = Geb::Config.new(self)
|
|
202
|
+
|
|
203
|
+
# set the loaded flag and break the loop
|
|
204
|
+
@loaded = true
|
|
205
|
+
break
|
|
206
|
+
|
|
207
|
+
end # if
|
|
208
|
+
|
|
209
|
+
# go up the chain
|
|
210
|
+
site_path_candidate = File.expand_path('..', site_path_candidate)
|
|
211
|
+
|
|
212
|
+
end # until
|
|
213
|
+
|
|
214
|
+
# raise an error if the site path is not found
|
|
215
|
+
raise SiteNotFoundError.new("#{site_path} is not and is not in a geb site.") unless @loaded
|
|
216
|
+
|
|
217
|
+
Geb.log "done."
|
|
218
|
+
Geb.log "Found geb site at path #{@site_path} as #{site_name}."
|
|
219
|
+
|
|
220
|
+
end # def load
|
|
221
|
+
|
|
222
|
+
# check if the site directory exists
|
|
223
|
+
def site_directory_exists?(site_path)
|
|
224
|
+
File.directory?(site_path)
|
|
225
|
+
end # def site_directory_exists?
|
|
226
|
+
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# Site release functionality, releasing the site, packaging site templates and assets
|
|
4
|
+
#
|
|
5
|
+
# @title Geb - Site - Release Module
|
|
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
|
+
class Site
|
|
14
|
+
module Release
|
|
15
|
+
|
|
16
|
+
# release the site
|
|
17
|
+
def release
|
|
18
|
+
|
|
19
|
+
# build the site first
|
|
20
|
+
build();
|
|
21
|
+
|
|
22
|
+
# get the site local and release directory
|
|
23
|
+
site_release_directory = get_site_release_directory()
|
|
24
|
+
|
|
25
|
+
# clear the output directory
|
|
26
|
+
Geb.log_start "Clearing site release folder #{site_release_directory} ... "
|
|
27
|
+
clear_site_release_directory()
|
|
28
|
+
Geb.log "done."
|
|
29
|
+
|
|
30
|
+
# copy the files to the output directory
|
|
31
|
+
Geb.log_start "Releasing site to #{site_release_directory} ... "
|
|
32
|
+
copy_site_to_release_directory()
|
|
33
|
+
Geb.log "done."
|
|
34
|
+
|
|
35
|
+
end # def release
|
|
36
|
+
|
|
37
|
+
# get the template archive path
|
|
38
|
+
# @return [String] the template archive path within the release directory
|
|
39
|
+
def get_template_archive_release_path
|
|
40
|
+
return File.join(@site_path, @site_config.output_dir, Geb::Defaults::RELEASE_OUTPUT_DIR, Geb::Defaults::TEMPLATE_ARCHIVE_FILENAME)
|
|
41
|
+
end # def get_template_archive_release_path
|
|
42
|
+
|
|
43
|
+
# get the site release directory
|
|
44
|
+
# @return [String] the site release directory
|
|
45
|
+
def get_site_release_directory
|
|
46
|
+
return File.join(@site_path, @site_config.output_dir, Geb::Defaults::RELEASE_OUTPUT_DIR)
|
|
47
|
+
end # def get_site_release_directory
|
|
48
|
+
|
|
49
|
+
# clear the site release directory
|
|
50
|
+
# @return [Nil]
|
|
51
|
+
def clear_site_release_directory
|
|
52
|
+
FileUtils.rm_rf(Dir.glob("#{get_site_release_directory()}/*"))
|
|
53
|
+
end # def clear_site_release_directory
|
|
54
|
+
|
|
55
|
+
# output the site from local output to release directory.
|
|
56
|
+
# @return [Nil]
|
|
57
|
+
def copy_site_to_release_directory
|
|
58
|
+
FileUtils.cp_r("#{get_site_output_directory()}/.", get_site_release_directory())
|
|
59
|
+
end # def output_site
|
|
60
|
+
|
|
61
|
+
# check if the site has been released.
|
|
62
|
+
# The site is considered released if the release directory exists and is not empty.
|
|
63
|
+
# @return [Boolean] true if the site has been released, false otherwise
|
|
64
|
+
def released?
|
|
65
|
+
return Dir.exist?(get_site_release_directory()) && !Dir.empty?(get_site_release_directory())
|
|
66
|
+
end # def released?
|
|
67
|
+
|
|
68
|
+
end # module Build
|
|
69
|
+
end # class Site
|
|
70
|
+
end # module Geb
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# Site remote functionality, things like ssh, scp, rsync, etc.
|
|
4
|
+
#
|
|
5
|
+
# @title Geb - Site - Remote
|
|
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
|
+
class Site
|
|
14
|
+
module Remote
|
|
15
|
+
|
|
16
|
+
class RemoteURINotConfigured < Geb::Error
|
|
17
|
+
MESSAGE = "Remote URI not configured in geb.config.yml".freeze
|
|
18
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
|
19
|
+
end # class RemoteURINotConfigured < Geb::Error
|
|
20
|
+
|
|
21
|
+
class RemotePathNotConfigured < Geb::Error
|
|
22
|
+
MESSAGE = "Remote Path is not configured in geb.config.yml".freeze
|
|
23
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
|
24
|
+
end # class RemotePathNotConfigured < Geb::Error
|
|
25
|
+
|
|
26
|
+
class SiteNotReleasedError < Geb::Error
|
|
27
|
+
MESSAGE = "Site not released. Please run 'geb release' first.".freeze
|
|
28
|
+
def initialize(e = ""); super(e, MESSAGE); end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# launch a remote session
|
|
32
|
+
# @return [Nil]
|
|
33
|
+
# @raise SiteNotLoadedError if the site is not loaded
|
|
34
|
+
# @raise RemoteURINotConfigured if the remote uri is not configured
|
|
35
|
+
def launch_remote
|
|
36
|
+
|
|
37
|
+
# raise an error if the site is not loaded
|
|
38
|
+
raise Geb::Site::SiteNotFoundError.new("Site not loaded") unless @loaded
|
|
39
|
+
|
|
40
|
+
# make sure the remote uri is configured
|
|
41
|
+
raise RemoteURINotConfigured.new unless @site_config.remote_uri
|
|
42
|
+
|
|
43
|
+
# Temporarily disable reporting exceptions in threads
|
|
44
|
+
original_thread_report_on_exception_setting = Thread.report_on_exception
|
|
45
|
+
Thread.report_on_exception = false
|
|
46
|
+
|
|
47
|
+
Geb.log "About to start an ssh session with the remote server #{@site_config.remote_uri}."
|
|
48
|
+
|
|
49
|
+
# attempt to launch a remote session
|
|
50
|
+
begin
|
|
51
|
+
Open3.capture3("ssh", @site_config.remote_uri)
|
|
52
|
+
rescue Interrupt, IOError
|
|
53
|
+
Geb.log "Remote session interrupted."
|
|
54
|
+
rescue
|
|
55
|
+
Geb.log "Remote session interrupted."
|
|
56
|
+
ensure
|
|
57
|
+
# restore the original thread report on exception setting
|
|
58
|
+
Thread.report_on_exception = original_thread_report_on_exception_setting
|
|
59
|
+
end # begin ... rescue
|
|
60
|
+
|
|
61
|
+
end # def launch_remote
|
|
62
|
+
|
|
63
|
+
# upload the site release to the remote server
|
|
64
|
+
# @return [Nil]
|
|
65
|
+
# @raise SiteNotReleasedError if the site has not been released
|
|
66
|
+
# @raise RemoteURINotConfigured if the remote uri is not configured
|
|
67
|
+
# @raise RemotePathNotConfigured if the remote path is not configured
|
|
68
|
+
def upload_release_to_remote()
|
|
69
|
+
|
|
70
|
+
# check if the release directory is empty
|
|
71
|
+
raise SiteNotReleasedError.new unless released?
|
|
72
|
+
|
|
73
|
+
# make sure the remote uri and remote path are configured
|
|
74
|
+
raise RemoteURINotConfigured.new unless @site_config.remote_uri
|
|
75
|
+
raise RemotePathNotConfigured.new unless @site_config.remote_path
|
|
76
|
+
|
|
77
|
+
# Temporarily disable reporting exceptions in threads
|
|
78
|
+
original_thread_report_on_exception_setting = Thread.report_on_exception
|
|
79
|
+
Thread.report_on_exception = false
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
Geb.log "About to upload the site release to remote server2."
|
|
83
|
+
|
|
84
|
+
# attempt to upload the release to the remote server
|
|
85
|
+
begin
|
|
86
|
+
|
|
87
|
+
# build up the command arguments
|
|
88
|
+
command_exclude_pattern = '*.DS_Store'
|
|
89
|
+
command_source_directory = File.join(get_site_release_directory, '/')
|
|
90
|
+
command_remote_uri = @site_config.remote_uri + ":" + @site_config.remote_path
|
|
91
|
+
|
|
92
|
+
# build the rsync command
|
|
93
|
+
rsync_command = [
|
|
94
|
+
'rsync',
|
|
95
|
+
'-av',
|
|
96
|
+
'-e', 'ssh',
|
|
97
|
+
"--exclude=#{command_exclude_pattern}",
|
|
98
|
+
command_source_directory,
|
|
99
|
+
command_remote_uri
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
Geb.log " - upload command: #{rsync_command.join(' ')}"
|
|
103
|
+
|
|
104
|
+
# execute the rsync command
|
|
105
|
+
Open3.popen3(*rsync_command) do |stdin, stdout, stderr, wait_thr|
|
|
106
|
+
|
|
107
|
+
# create threads to read the stdout
|
|
108
|
+
stdout_thread = Thread.new do
|
|
109
|
+
stdout.each_line { |line| Geb.log " - #{line}" }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# create threads to read the stderr
|
|
113
|
+
stderr_thread = Thread.new do
|
|
114
|
+
stderr.each_line { |line| Geb.log " - (error) #{line}" }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# wait for the threads to finish
|
|
118
|
+
stdout_thread.join
|
|
119
|
+
stderr_thread.join
|
|
120
|
+
|
|
121
|
+
# get the status of the command
|
|
122
|
+
status = wait_thr.value
|
|
123
|
+
|
|
124
|
+
# log the status of the command
|
|
125
|
+
{ stdout: stdout, stderr: stderr, status: status }
|
|
126
|
+
|
|
127
|
+
end # Open3.popen3
|
|
128
|
+
|
|
129
|
+
rescue Interrupt, IOError
|
|
130
|
+
Geb.log "Upload interrupted."
|
|
131
|
+
rescue
|
|
132
|
+
Geb.log "Upload interrupted."
|
|
133
|
+
ensure
|
|
134
|
+
# restore the original thread report on exception setting
|
|
135
|
+
Thread.report_on_exception = original_thread_report_on_exception_setting
|
|
136
|
+
end # begin ... rescue
|
|
137
|
+
|
|
138
|
+
end # def upload_release_to_remote
|
|
139
|
+
|
|
140
|
+
end # module Remote
|
|
141
|
+
end # class Site
|
|
142
|
+
end # module Geb
|