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.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +3 -0
  3. data/CHANGELOG.md +37 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/LICENSE +21 -0
  6. data/README.md +384 -0
  7. data/Rakefile +21 -0
  8. data/bin/geb +21 -0
  9. data/lib/geb/cli.rb +40 -0
  10. data/lib/geb/commands/build.rb +59 -0
  11. data/lib/geb/commands/init.rb +70 -0
  12. data/lib/geb/commands/release.rb +54 -0
  13. data/lib/geb/commands/remote.rb +48 -0
  14. data/lib/geb/commands/server.rb +77 -0
  15. data/lib/geb/commands/upload.rb +48 -0
  16. data/lib/geb/commands/version.rb +36 -0
  17. data/lib/geb/config.rb +103 -0
  18. data/lib/geb/defaults.rb +44 -0
  19. data/lib/geb/git.rb +112 -0
  20. data/lib/geb/page.rb +217 -0
  21. data/lib/geb/partial.rb +150 -0
  22. data/lib/geb/samples/basic/assets/css/site.css +7 -0
  23. data/lib/geb/samples/basic/assets/images/android-chrome-192x192.png +0 -0
  24. data/lib/geb/samples/basic/assets/images/android-chrome-512x512.png +0 -0
  25. data/lib/geb/samples/basic/assets/images/apple-touch-icon.png +0 -0
  26. data/lib/geb/samples/basic/assets/images/favicon-16x16.png +0 -0
  27. data/lib/geb/samples/basic/assets/images/favicon-32x32.png +0 -0
  28. data/lib/geb/samples/basic/assets/images/favicon.ico +0 -0
  29. data/lib/geb/samples/basic/assets/images/hero.png +0 -0
  30. data/lib/geb/samples/basic/assets/images/og-thumb.png +0 -0
  31. data/lib/geb/samples/basic/assets/images/twitter-thumb.png +0 -0
  32. data/lib/geb/samples/basic/assets/js/site.js +5 -0
  33. data/lib/geb/samples/basic/geb.config.yml +70 -0
  34. data/lib/geb/samples/basic/index.html +11 -0
  35. data/lib/geb/samples/basic/page.html +11 -0
  36. data/lib/geb/samples/basic/shared/partials/_analytics.html +9 -0
  37. data/lib/geb/samples/basic/shared/partials/_footer.html +3 -0
  38. data/lib/geb/samples/basic/shared/partials/_global_assets.html +19 -0
  39. data/lib/geb/samples/basic/shared/partials/_header.html +0 -0
  40. data/lib/geb/samples/basic/shared/partials/_meta_tags.html +34 -0
  41. data/lib/geb/samples/basic/shared/templates/_blog_post.html +0 -0
  42. data/lib/geb/samples/basic/shared/templates/_site.html +19 -0
  43. data/lib/geb/samples/basic/site.webmanifest +1 -0
  44. data/lib/geb/samples/bootstrap_jquery/assets/css/site.css +7 -0
  45. data/lib/geb/samples/bootstrap_jquery/assets/images/android-chrome-192x192.png +0 -0
  46. data/lib/geb/samples/bootstrap_jquery/assets/images/android-chrome-512x512.png +0 -0
  47. data/lib/geb/samples/bootstrap_jquery/assets/images/apple-touch-icon.png +0 -0
  48. data/lib/geb/samples/bootstrap_jquery/assets/images/favicon-16x16.png +0 -0
  49. data/lib/geb/samples/bootstrap_jquery/assets/images/favicon-32x32.png +0 -0
  50. data/lib/geb/samples/bootstrap_jquery/assets/images/favicon.ico +0 -0
  51. data/lib/geb/samples/bootstrap_jquery/assets/images/hero.png +0 -0
  52. data/lib/geb/samples/bootstrap_jquery/assets/images/og-thumb.png +0 -0
  53. data/lib/geb/samples/bootstrap_jquery/assets/images/twitter-thumb.png +0 -0
  54. data/lib/geb/samples/bootstrap_jquery/assets/js/site.js +5 -0
  55. data/lib/geb/samples/bootstrap_jquery/blog/blog_post_1.html +35 -0
  56. data/lib/geb/samples/bootstrap_jquery/blog/blog_post_2.html +35 -0
  57. data/lib/geb/samples/bootstrap_jquery/blog/blog_post_3.html +35 -0
  58. data/lib/geb/samples/bootstrap_jquery/blog/index.html +17 -0
  59. data/lib/geb/samples/bootstrap_jquery/geb.config.yml +69 -0
  60. data/lib/geb/samples/bootstrap_jquery/index.html +11 -0
  61. data/lib/geb/samples/bootstrap_jquery/page.html +11 -0
  62. data/lib/geb/samples/bootstrap_jquery/shared/partials/_analytics.html +9 -0
  63. data/lib/geb/samples/bootstrap_jquery/shared/partials/_footer.html +3 -0
  64. data/lib/geb/samples/bootstrap_jquery/shared/partials/_global_assets.html +19 -0
  65. data/lib/geb/samples/bootstrap_jquery/shared/partials/_header.html +0 -0
  66. data/lib/geb/samples/bootstrap_jquery/shared/partials/_meta_tags.html +34 -0
  67. data/lib/geb/samples/bootstrap_jquery/shared/templates/_blog_post.html +0 -0
  68. data/lib/geb/samples/bootstrap_jquery/shared/templates/_site.html +19 -0
  69. data/lib/geb/samples/bootstrap_jquery/site.webmanifest +1 -0
  70. data/lib/geb/samples/geb.config.yml +70 -0
  71. data/lib/geb/server.rb +138 -0
  72. data/lib/geb/site/build.rb +189 -0
  73. data/lib/geb/site/core.rb +229 -0
  74. data/lib/geb/site/release.rb +70 -0
  75. data/lib/geb/site/remote.rb +142 -0
  76. data/lib/geb/site/template.rb +208 -0
  77. data/lib/geb/site.rb +83 -0
  78. data/lib/geb/template.rb +166 -0
  79. data/lib/geb/utilities.rb +110 -0
  80. data/lib/geb.rb +36 -0
  81. data/lib/seth.rb +50 -0
  82. data/sig/geb.rbs +4 -0
  83. data/test/api tests/test_cli.rb +200 -0
  84. data/test/api tests/test_config.rb +330 -0
  85. data/test/api tests/test_defaults.rb +62 -0
  86. data/test/api tests/test_git.rb +105 -0
  87. data/test/api tests/test_page.rb +320 -0
  88. data/test/api tests/test_partial.rb +152 -0
  89. data/test/api tests/test_server.rb +416 -0
  90. data/test/api tests/test_site.rb +1315 -0
  91. data/test/api tests/test_template.rb +249 -0
  92. data/test/api tests/test_utilities.rb +162 -0
  93. data/test/command tests/test_geb_build.rb +199 -0
  94. data/test/command tests/test_geb_init.rb +312 -0
  95. data/test/command tests/test_geb_release.rb +166 -0
  96. data/test/command tests/test_geb_remote.rb +66 -0
  97. data/test/command tests/test_geb_server.rb +122 -0
  98. data/test/command tests/test_geb_upload.rb +96 -0
  99. data/test/command tests/test_geb_version.rb +58 -0
  100. data/test/support/geb_api_test.rb +37 -0
  101. data/test/support/geb_cli_test.rb +275 -0
  102. data/test/support/geb_minitest_ext.rb +35 -0
  103. data/test/support/geb_test_helpers.rb +84 -0
  104. data/test/support/geb_web_server_proxy.rb +128 -0
  105. data/test/test_helper.rb +61 -0
  106. 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