geb 0.1.11

Sign up to get free protection for your applications and to get access to all the features.
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