rawfeed 0.3.1 → 1.0.1

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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +25 -0
  3. data/.gitignore +19 -0
  4. data/.gitlab/ci/gitlab-pages.yml +51 -0
  5. data/.hidden +6 -0
  6. data/404.html +6 -0
  7. data/Gemfile +51 -0
  8. data/README.md +102 -26
  9. data/_config.yml +77 -0
  10. data/_data/generic.yml +29 -0
  11. data/_data/screen/blog.yml +46 -0
  12. data/_data/screen/contact.yml +31 -0
  13. data/_data/screen/donate.yml +45 -0
  14. data/_data/screen/footer.yml +17 -0
  15. data/_data/screen/head.yml +49 -0
  16. data/_data/screen/home.yml +24 -0
  17. data/_data/screen/maintenance.yml +8 -0
  18. data/_data/screen/navbar.yml +41 -0
  19. data/_data/screen/page.yml +12 -0
  20. data/_data/screen/pixels.yml +7 -0
  21. data/_data/screen/pub.yml +4 -0
  22. data/_data/{resume.yml → screen/resume.yml} +8 -8
  23. data/_data/screen/socials.yml +20 -0
  24. data/_includes/assigned +36 -0
  25. data/_includes/layout/blog_search.html +22 -15
  26. data/_includes/layout/disqus.html +7 -8
  27. data/_includes/layout/footer.html +43 -28
  28. data/_includes/layout/giscus.html +15 -19
  29. data/_includes/layout/head.html +97 -75
  30. data/_includes/layout/maintenance.html +14 -11
  31. data/_includes/layout/navbar.html +253 -0
  32. data/_includes/layout/paginator.html +12 -13
  33. data/_includes/{alert → markdown/alert} +2 -2
  34. data/_includes/{chart → markdown/chart} +3 -3
  35. data/_includes/{image → markdown/image} +4 -4
  36. data/_includes/{socials → markdown/socials} +5 -5
  37. data/_includes/{video → markdown/video} +2 -2
  38. data/_layouts/blog/index.html +53 -0
  39. data/_layouts/{post.html → blog/post.html} +50 -36
  40. data/_layouts/blog/posts_by_tag.html +27 -0
  41. data/_layouts/blog/tags.html +32 -0
  42. data/_layouts/contact.html +156 -114
  43. data/_layouts/default.html +53 -48
  44. data/_layouts/donate.html +112 -0
  45. data/_layouts/error.html +9 -7
  46. data/_layouts/home.html +22 -19
  47. data/_layouts/licenses.html +2 -2
  48. data/_layouts/page.html +18 -17
  49. data/_layouts/pixels/index.html +75 -0
  50. data/_layouts/{pixel.html → pixels/post.html} +5 -5
  51. data/_layouts/pub.html +50 -44
  52. data/_layouts/resume.html +308 -265
  53. data/_pages/any-page.md +336 -0
  54. data/_pages/contact.md +16 -0
  55. data/_pages/donate.md +16 -0
  56. data/_pages/licenses.md +17 -0
  57. data/_pages/resume.md +14 -0
  58. data/_pixels/2025-10-15-first-my-pixel.md +34 -0
  59. data/_posts/2025-09-20-welcome-to-jekyll.md +36 -0
  60. data/_posts/2025-09-25-this-post-demonstrates-post-codeblocks.md +141 -0
  61. data/_posts/2025-09-25-this-post-demonstrates-post-content-styles.md +133 -0
  62. data/_posts/2025-10-04-content-styles-and-codeblocks.md +330 -0
  63. data/_sass/components/_markdown.scss +2 -2
  64. data/_sass/includes/_index.scss +2 -2
  65. data/_sass/includes/{_header.scss → _navbar.scss} +2 -6
  66. data/_sass/includes/_toc.scss +146 -0
  67. data/_sass/layouts/_blog.scss +1 -1
  68. data/_sass/layouts/_index.scss +2 -2
  69. data/_sass/layouts/_page.scss +0 -5
  70. data/_sass/layouts/_post.scss +8 -138
  71. data/_sass/layouts/{_tag-posts.scss → _posts_by_tag.scss} +3 -3
  72. data/_sass/layouts/_resume.scss +1 -1
  73. data/_sass/layouts/{_tag.scss → _tags.scss} +10 -6
  74. data/_sass/main.scss +2 -2
  75. data/_sass/theme/_dark.scss +2 -2
  76. data/_sass/theme/_light.scss +1 -1
  77. data/assets/js/blog.js +52 -18
  78. data/assets/js/contact.js +21 -20
  79. data/assets/js/default.js +67 -50
  80. data/assets/js/discus.js +2 -2
  81. data/assets/js/{donation.js → donate.js} +10 -9
  82. data/assets/js/home.js +18 -16
  83. data/assets/js/page.js +50 -172
  84. data/assets/js/toc.js +133 -0
  85. data/assets/json/blog_search.json +6 -6
  86. data/assets/vendors/fuse.min.js +9 -0
  87. data/blog/index.md +14 -0
  88. data/blog/tags/index.md +12 -0
  89. data/exe/rawfeed +29 -0
  90. data/index.md +15 -0
  91. data/lib/rawfeed/build/cleaner.rb +60 -0
  92. data/lib/rawfeed/build/image_minifier.rb +163 -0
  93. data/lib/rawfeed/build/minifier.rb +89 -0
  94. data/lib/rawfeed/build.rb +9 -0
  95. data/lib/rawfeed/command/backup.rb +124 -0
  96. data/lib/rawfeed/command/cli.rb +118 -0
  97. data/lib/rawfeed/command/installer.rb +156 -0
  98. data/lib/rawfeed/command/tools.rb +138 -0
  99. data/lib/rawfeed/{author.rb → content/author.rb} +7 -7
  100. data/lib/rawfeed/content/contact.rb +51 -0
  101. data/lib/rawfeed/content/donate.rb +48 -0
  102. data/lib/rawfeed/{draft.rb → content/draft.rb} +5 -3
  103. data/lib/rawfeed/content/licenses.rb +46 -0
  104. data/lib/rawfeed/{page.rb → content/page.rb} +4 -3
  105. data/lib/rawfeed/{pixel.rb → content/pixel.rb} +7 -4
  106. data/lib/rawfeed/content/post.rb +107 -0
  107. data/lib/rawfeed/{resume.rb → content/resume.rb} +23 -19
  108. data/lib/rawfeed/{layout.rb → core/layout.rb} +1 -1
  109. data/lib/rawfeed/core/utils.rb +103 -0
  110. data/lib/rawfeed/{version.rb → core/version.rb} +1 -1
  111. data/lib/rawfeed/{datelang.rb → plugin/datelang.rb} +5 -6
  112. data/lib/rawfeed/{pub.rb → plugin/pub.rb} +33 -31
  113. data/lib/rawfeed/{reading_time.rb → plugin/reading_time.rb} +5 -4
  114. data/lib/rawfeed/root.rb +3 -0
  115. data/lib/rawfeed.rb +36 -14
  116. data/pixels/index.md +11 -0
  117. data/robots.txt +26 -0
  118. metadata +167 -119
  119. data/_data/options.yml +0 -329
  120. data/_includes/layout/data.liquid +0 -31
  121. data/_includes/layout/header.html +0 -173
  122. data/_layouts/blog.html +0 -52
  123. data/_layouts/donation.html +0 -113
  124. data/_layouts/pixels.html +0 -71
  125. data/_layouts/tag.html +0 -33
  126. data/_layouts/tag_posts.html +0 -28
  127. data/assets/vendor/simple-jekyll-search.js +0 -433
  128. data/assets/vendor/simple-jekyll-search.min.js +0 -6
  129. data/lib/rawfeed/installer.rb +0 -37
  130. data/lib/rawfeed/post.rb +0 -60
  131. data/lib/rawfeed/utils.rb +0 -75
  132. /data/_includes/{details → markdown/details} +0 -0
  133. /data/_includes/{enddetails → markdown/enddetails} +0 -0
  134. /data/_includes/{endtabs → markdown/endtabs} +0 -0
  135. /data/_includes/{tabs → markdown/tabs} +0 -0
  136. /data/assets/images/{avatar_back.png → avatars/back.png} +0 -0
  137. /data/assets/images/{avatar_dark.png → avatars/dark.png} +0 -0
  138. /data/assets/images/{avatar_light.png → avatars/light.png} +0 -0
  139. /data/assets/images/{icons → donate}/bitcoin.svg +0 -0
  140. /data/assets/images/{icons → donate}/card.svg +0 -0
  141. /data/assets/images/{icons → donate}/donation.svg +0 -0
  142. /data/assets/images/{icons → donate}/lightning_network.svg +0 -0
  143. /data/assets/images/{icons → donate}/paypal.svg +0 -0
  144. /data/assets/images/{icons → donate}/pix.svg +0 -0
  145. /data/assets/images/{qrcode_btc_binance.jpg → donate/qrcode_btc_binance.jpg} +0 -0
  146. /data/assets/images/{qrcode_inter.jpg → donate/qrcode_inter.jpg} +0 -0
  147. /data/assets/images/{qrcode_wos.jpg → donate/qrcode_wos.jpg} +0 -0
  148. /data/assets/images/{icons → donate}/wos.png +0 -0
  149. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.css +0 -0
  150. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.css.map +0 -0
  151. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.min.css +0 -0
  152. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.min.css.map +0 -0
  153. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.css +0 -0
  154. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.css.map +0 -0
  155. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.min.css +0 -0
  156. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.min.css.map +0 -0
  157. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.css +0 -0
  158. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.css.map +0 -0
  159. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.min.css +0 -0
  160. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.min.css.map +0 -0
  161. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.css +0 -0
  162. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.css.map +0 -0
  163. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.min.css +0 -0
  164. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.min.css.map +0 -0
  165. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.css +0 -0
  166. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.css.map +0 -0
  167. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.min.css +0 -0
  168. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.min.css.map +0 -0
  169. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.css +0 -0
  170. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.css.map +0 -0
  171. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.min.css +0 -0
  172. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.min.css.map +0 -0
  173. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.css +0 -0
  174. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.css.map +0 -0
  175. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.min.css +0 -0
  176. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.min.css.map +0 -0
  177. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.css +0 -0
  178. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.css.map +0 -0
  179. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.min.css +0 -0
  180. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.min.css.map +0 -0
  181. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.js +0 -0
  182. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.js.map +0 -0
  183. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.min.js +0 -0
  184. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.min.js.map +0 -0
  185. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.js +0 -0
  186. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.js.map +0 -0
  187. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.min.js +0 -0
  188. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.min.js.map +0 -0
  189. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.js +0 -0
  190. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.js.map +0 -0
  191. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.min.js +0 -0
  192. /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.min.js.map +0 -0
  193. /data/lib/rawfeed/{csp_filters.rb → plugin/csp_filters.rb} +0 -0
  194. /data/lib/rawfeed/{typescript_liquid.rb → plugin/typescript_liquid.rb} +0 -0
  195. /data/lib/rawfeed/{with_class.rb → plugin/with_class.rb} +0 -0
data/exe/rawfeed ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "pathname"
5
+
6
+ # Try to find the gem by running it via exe/directly.
7
+ begin
8
+ exe_path = Pathname.new(__FILE__).realpath
9
+ gem_root = exe_path.parent.parent
10
+ lib_dir = gem_root.join("lib")
11
+
12
+ if lib_dir.directory? && !$LOAD_PATH.include?(lib_dir.to_s)
13
+ $LOAD_PATH.unshift(lib_dir.to_s)
14
+ end
15
+ rescue StandardError
16
+ # Simple fallback if realpath fails.
17
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
18
+ end
19
+
20
+ begin
21
+ require "rawfeed"
22
+ require "rawfeed/command/cli"
23
+ rescue LoadError => e
24
+ warn "Error: Could not load 'rawfeed' gem logic. (#{e.message})"
25
+ warn "If you are developing locally, run with: bundle exec exe/rawfeed"
26
+ exit 1
27
+ end
28
+
29
+ Rawfeed::CLI.run(ARGV)
data/index.md ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ layout: home
3
+ author: "William C. Canin"
4
+ text_center: true
5
+ published: true
6
+ permalink: / # do not change
7
+ ---
8
+
9
+ Hello, Welcome to rawfeed!
10
+
11
+ This is the space for your presentation. Do it right! 😃
12
+
13
+ This is the **rawfeed** theme for [Jekyll](https://jekyllrb.com){:target="\_blank"}, feel free to modify it.
14
+
15
+ You can see more customization details in [rawfeed doc](https://rawfeed.github.io/rawfeed-jekyll){:target="\_blank"}.
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "colorize"
5
+
6
+ module Rawfeed
7
+ module Build
8
+ class Cleaner
9
+ JEKYLL_CACHE_PATHS = [
10
+ ".jekyll-metadata",
11
+ ".jekyll-cache",
12
+ "_site"
13
+ ].freeze
14
+
15
+ PROJECT_PATHS = [
16
+ ".jekyll-metadata",
17
+ ".jekyll-cache",
18
+ "_site"
19
+ ].freeze
20
+
21
+ def self.clean_jekyll_cache
22
+ puts "Cleaning Jekyll cache...".yellow
23
+ deleted_paths = []
24
+
25
+ JEKYLL_CACHE_PATHS.each do |path|
26
+ if File.exist?(path) || Dir.exist?(path)
27
+ FileUtils.rm_rf(path)
28
+ deleted_paths << path
29
+ end
30
+ end
31
+
32
+ if deleted_paths.any?
33
+ puts "Items successfully removed:".green
34
+ deleted_paths.each { |path| puts " - #{path}" }
35
+ else
36
+ puts "No items to clean."
37
+ end
38
+ end
39
+
40
+ def self.clean_project
41
+ puts "Cleaning up project directories and files...".yellow
42
+ deleted_paths = []
43
+
44
+ PROJECT_PATHS.each do |path|
45
+ if File.exist?(path) || Dir.exist?(path)
46
+ FileUtils.rm_rf(path)
47
+ deleted_paths << path
48
+ end
49
+ end
50
+
51
+ if deleted_paths.any?
52
+ puts "Items successfully removed:".green
53
+ deleted_paths.each { |path| puts " - #{path}" }
54
+ else
55
+ puts "No items to clean."
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "colorize"
4
+ require "fileutils"
5
+
6
+ module Rawfeed
7
+ module Build
8
+ class ImageMinifier
9
+ BUILD_DIR = "_site"
10
+ IMAGE_FORMATS = %w[jpg jpeg png].freeze
11
+ QUALITY_JPEG = 75
12
+ QUALITY_WEBP = 75
13
+ QUALITY_AVIF = 50
14
+
15
+ def self.shell_escape(path)
16
+ "'#{path.to_s.gsub("'", "'\\\\''")}'"
17
+ end
18
+
19
+ def self.minify_images
20
+ begin
21
+ require "mini_magick"
22
+ rescue LoadError
23
+ # mini_magick is optional, but let user know they need it OR ImageMagick
24
+ end
25
+
26
+ # Check if ImageMagick is installed
27
+ begin
28
+ system("identify -version > /dev/null 2>&1")
29
+ unless $?.success?
30
+ raise "ImageMagick not found"
31
+ end
32
+ rescue => e
33
+ puts "[!] Error: ImageMagick is not installed or not found in PATH".red
34
+ puts "".red
35
+ if RUBY_PLATFORM.include?("linux")
36
+ if File.exist?("/etc/arch-release")
37
+ puts " Arch Linux: sudo pacman -S imagemagick".yellow
38
+ else
39
+ puts " Ubuntu/Debian: sudo apt-get install imagemagick".yellow
40
+ end
41
+ elsif RUBY_PLATFORM.include?("darwin")
42
+ puts " macOS: brew install imagemagick".yellow
43
+ elsif RUBY_PLATFORM.include?("mingw") || RUBY_PLATFORM.include?("mswin")
44
+ puts " Windows: https://imagemagick.org/script/download.php".yellow
45
+ else
46
+ puts " See: https://imagemagick.org/script/download.php".yellow
47
+ end
48
+ exit 1
49
+ end
50
+
51
+ puts "Minifying images...".yellow
52
+
53
+ image_files = Dir.glob("#{BUILD_DIR}/assets/images/**/*.{jpg,jpeg,png}", File::FNM_CASEFOLD)
54
+
55
+ if image_files.empty?
56
+ puts "No images found."
57
+ return
58
+ end
59
+
60
+ puts "Found #{image_files.length} images\n".cyan
61
+
62
+ total_before = 0
63
+ total_after = 0
64
+ successful = 0
65
+ failed = 0
66
+
67
+ image_files.each do |file|
68
+ begin
69
+ before_size = File.size(file)
70
+ total_before += before_size
71
+
72
+ # Get file info
73
+ dir = File.dirname(file)
74
+ basename = File.basename(file, File.extname(file))
75
+
76
+ # Process with ImageMagick convert command
77
+ # Use 'magick convert' for ImageMagick 7+, fallback to 'convert' for older versions
78
+ converter = system("magick --version > /dev/null 2>&1") ? "magick convert" : "convert"
79
+
80
+ # 1. Compress original to JPEG
81
+ temp_jpeg = "#{file}.temp.jpg"
82
+ cmd_jpeg = "#{converter} #{shell_escape(file)} -quality #{QUALITY_JPEG} -strip #{shell_escape(temp_jpeg)} 2>/dev/null"
83
+ system(cmd_jpeg)
84
+
85
+ if File.exist?(temp_jpeg) && File.size(temp_jpeg) > 0
86
+ FileUtils.mv(temp_jpeg, file)
87
+ jpeg_size = File.size(file)
88
+ total_after += jpeg_size
89
+ successful += 1
90
+
91
+ # Create WebP version
92
+ webp_path = File.join(dir, "#{basename}.webp")
93
+ cmd_webp = "#{converter} #{shell_escape(file)} -quality #{QUALITY_WEBP} #{shell_escape(webp_path)} 2>/dev/null"
94
+ system(cmd_webp)
95
+
96
+ # Create AVIF version
97
+ avif_path = File.join(dir, "#{basename}.avif")
98
+ cmd_avif = "#{converter} #{shell_escape(file)} -quality #{QUALITY_AVIF} #{shell_escape(avif_path)} 2>/dev/null"
99
+ system(cmd_avif)
100
+
101
+ # Display progress
102
+ human_before = format_bytes(before_size)
103
+ human_after = format_bytes(jpeg_size)
104
+ puts " #{green_checkmark} #{file} #{neutral("(#{human_before} → #{human_after})")}"
105
+ else
106
+ failed += 1
107
+ FileUtils.rm(temp_jpeg) if File.exist?(temp_jpeg)
108
+ puts " #{red_x} #{file} #{red("(Failed to convert)")}"
109
+ end
110
+ rescue => e
111
+ failed += 1
112
+ puts " #{red_x} #{file} #{red("(#{e.message}")}\n"
113
+ end
114
+ end
115
+
116
+ # Display summary
117
+ if successful > 0
118
+ saved = total_before - total_after
119
+ human_before = format_bytes(total_before)
120
+ human_after = format_bytes(total_after)
121
+ human_saved = format_bytes(saved)
122
+
123
+ puts ""
124
+ puts "Images optimized: #{human_before} → #{human_after} (saved #{human_saved})".bold.green
125
+ puts " Successful: #{successful}, Failed: #{failed}".cyan if failed > 0
126
+ puts ""
127
+ elsif failed > 0
128
+ puts ""
129
+ puts "No images were processed (#{failed} failed)".red
130
+ puts ""
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ def self.format_bytes(bytes)
137
+ if bytes >= 1_000_000
138
+ "#{(bytes.to_f / 1_000_000).round(2)}MB"
139
+ elsif bytes >= 1_000
140
+ "#{(bytes.to_f / 1_000).round(2)}KB"
141
+ else
142
+ "#{bytes}B"
143
+ end
144
+ end
145
+
146
+ def self.green_checkmark
147
+ "✔".green
148
+ end
149
+
150
+ def self.red_x
151
+ "✗".red
152
+ end
153
+
154
+ def self.neutral(text)
155
+ text
156
+ end
157
+
158
+ def self.red(text)
159
+ text.red
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "colorize"
4
+ require_relative "image_minifier"
5
+
6
+ module Rawfeed
7
+ module Build
8
+ class Minifier
9
+ BUILD_DIR = "_site"
10
+
11
+ def self.minify_javascript
12
+ begin
13
+ require "uglifier"
14
+ rescue LoadError
15
+ puts "[!] Error: uglifier gem is required for minify feature".red
16
+ puts " Install it with: gem install uglifier".yellow
17
+ puts " Or add to your Gemfile: gem 'uglifier', '~> 4.2.0'".yellow
18
+ exit 1
19
+ end
20
+
21
+ puts "Minifying JavaScript files...".yellow
22
+ js_files = Dir.glob("#{BUILD_DIR}/assets/js/**/*.js")
23
+
24
+ if js_files.empty?
25
+ puts "No JavaScript files found."
26
+ return
27
+ end
28
+
29
+ begin
30
+ js_files.each do |file|
31
+ content = File.read(file)
32
+ minified = Uglifier.compile(content, harmony: true)
33
+ File.write(file, minified)
34
+ end
35
+ puts "JavaScript minified successfully!".green
36
+ rescue => e
37
+ puts "Error minifying JavaScript: #{e.message}".red
38
+ raise
39
+ end
40
+ end
41
+
42
+ def self.minify_html
43
+ puts "Minifying HTML files...".yellow
44
+ html_files = Dir.glob("#{BUILD_DIR}/**/*.html")
45
+
46
+ if html_files.empty?
47
+ puts "No HTML files found."
48
+ return
49
+ end
50
+
51
+ begin
52
+ html_files.each do |file|
53
+ content = File.read(file)
54
+ # Simple HTML minification: remove comments, collapse whitespace
55
+ # Preserves whitespace inside <pre>, <code>, <textarea>, <svg>
56
+ minified = content
57
+ .gsub(/<!--.*?-->/m, "")
58
+ .gsub(/(<(pre|code|textarea|svg)\b[^>]*>.*?<\/\2>)|(\s+)/mi) { $1 || " " }
59
+ .strip
60
+ File.write(file, minified)
61
+ end
62
+ puts "HTML minified successfully!".green
63
+ rescue => e
64
+ puts "Error minifying HTML: #{e.message}".red
65
+ raise
66
+ end
67
+ end
68
+
69
+ def self.minify_all
70
+ puts "Starting minify process...".cyan
71
+ begin
72
+ minify_javascript
73
+ minify_html
74
+ minify_images
75
+ puts "\n Minify completed successfully!\n".bold.green
76
+ rescue => e
77
+ puts "\n An error occurred during minify.\n".bold.red
78
+ puts e.message.red
79
+ exit 1
80
+ end
81
+ end
82
+
83
+ def self.minify_images
84
+ ImageMinifier.minify_images
85
+ end
86
+ end
87
+ end
88
+ end
89
+
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "build/minifier"
4
+ require_relative "build/cleaner"
5
+
6
+ module Rawfeed
7
+ module Build
8
+ end
9
+ end
@@ -0,0 +1,124 @@
1
+ require "rawfeed/root"
2
+ require "fileutils"
3
+ require "time"
4
+ require "colorize"
5
+
6
+ module Rawfeed
7
+ class Backup
8
+ DEFAULT_FOLDERS = %w[assets pub .vscode _posts _pages _data].freeze
9
+ STATIC_FILES = %w[_config.yml index.md robots.txt CNAME].freeze
10
+ DYNAMIC_EXTENSIONS = %w[.sh .py .rb .bat .ps1].freeze
11
+ GOOGLE_PATTERN = /^google.*\.html$/.freeze
12
+
13
+ def self.site(*args)
14
+ project_root = "."
15
+ gemspec_path = File.join(project_root, "rawfeed.gemspec")
16
+
17
+ if File.exist?(gemspec_path)
18
+ puts "[!] Not allowed to create backup from gem source directory (rawfeed.gemspec found)".red
19
+ exit 1
20
+ end
21
+
22
+ options = parse_options(args)
23
+ destination = options[:destination] || project_root
24
+ append_paths = options[:append] || []
25
+
26
+ unless Dir.exist?(destination)
27
+ puts "[!] Destination directory does not exist: #{destination}".red
28
+ exit 1
29
+ end
30
+
31
+ backup_filename = "backup-#{Time.now.strftime('%Y-%m-%d_%H-%M-%S')}.zip"
32
+ backup_path = File.join(destination, backup_filename)
33
+
34
+ begin
35
+ create_backup(project_root, backup_path, append_paths)
36
+ puts "[✓] Backup created successfully: #{backup_path}".green
37
+ rescue StandardError => e
38
+ puts "[!] Error creating backup: #{e.message}".red
39
+ exit 1
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def self.parse_options(args)
46
+ options = { destination: nil, append: [] }
47
+ i = 0
48
+
49
+ while i < args.length
50
+ case args[i]
51
+ when "--destination", "-d"
52
+ options[:destination] = args[i + 1]
53
+ i += 2
54
+ when "--append", "-a"
55
+ options[:append] << args[i + 1]
56
+ i += 2
57
+ else
58
+ i += 1
59
+ end
60
+ end
61
+
62
+ options
63
+ end
64
+
65
+ def self.create_backup(project_root, backup_path, append_paths)
66
+ require "zip"
67
+ Zip::File.open(backup_path, Zip::File::CREATE) do |zipfile|
68
+ # Add default folders
69
+ DEFAULT_FOLDERS.each do |folder|
70
+ folder_path = File.join(project_root, folder)
71
+ add_folder_to_zip(zipfile, folder_path, folder) if Dir.exist?(folder_path)
72
+ end
73
+
74
+ # Add static files
75
+ STATIC_FILES.each do |file|
76
+ file_path = File.join(project_root, file)
77
+ add_file_to_zip(zipfile, file_path, file) if File.exist?(file_path)
78
+ end
79
+
80
+ # Add dynamic files
81
+ add_dynamic_files_to_zip(zipfile, project_root)
82
+
83
+ # Add append paths
84
+ append_paths.each do |path|
85
+ full_path = File.join(project_root, path)
86
+ if File.directory?(full_path)
87
+ add_folder_to_zip(zipfile, full_path, path)
88
+ elsif File.exist?(full_path)
89
+ add_file_to_zip(zipfile, full_path, path)
90
+ else
91
+ puts "[!] Warning: Path not found: #{path}".yellow
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def self.add_dynamic_files_to_zip(zipfile, project_root)
98
+ Dir.glob(File.join(project_root, "*")).each do |path|
99
+ next unless File.file?(path)
100
+
101
+ basename = File.basename(path)
102
+ ext = File.extname(path)
103
+
104
+ # Check if it's a dynamic file (has one of the extensions or matches google*.html)
105
+ if DYNAMIC_EXTENSIONS.include?(ext) || GOOGLE_PATTERN.match?(basename)
106
+ add_file_to_zip(zipfile, path, basename)
107
+ end
108
+ end
109
+ end
110
+
111
+ def self.add_folder_to_zip(zipfile, folder_path, zip_path)
112
+ Dir.glob(File.join(folder_path, "**/**")).each do |file|
113
+ next if File.directory?(file)
114
+
115
+ relative_path = File.join(zip_path, file.sub(Regexp.escape(folder_path), "").sub(/^\//, ""))
116
+ zipfile.add(relative_path, file)
117
+ end
118
+ end
119
+
120
+ def self.add_file_to_zip(zipfile, file_path, zip_path)
121
+ zipfile.add(zip_path, file_path)
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "pathname"
5
+ require "colorize"
6
+ require_relative "tools"
7
+
8
+ module Rawfeed
9
+ # Command Line Interface (CLI) entry point for rawfeed-jekyll.
10
+ # This class handles routing of commands passed from the "rawfeed" executable.
11
+ class CLI
12
+ # Parses and executes the CLI command.
13
+ # Exits the application if invalid commands are provided.
14
+ #
15
+ # @param args [Array<String>] The arguments passed from the shell.
16
+ # @return [void]
17
+ def self.run(args)
18
+ if args.empty?
19
+ puts "Use: rawfeed help"
20
+ exit 1
21
+ end
22
+
23
+ first_arg = args.shift
24
+
25
+ # Internal tools
26
+ if %w[minify clean help new install build serve backup].include?(first_arg)
27
+ case first_arg
28
+ when "minify"
29
+ Rawfeed::Tools.minify
30
+ when "clean"
31
+ Rawfeed::Tools.clean(*args)
32
+ when "help"
33
+ Rawfeed::Tools.help
34
+ when "new"
35
+ site_path = args.shift || "my-site"
36
+ Rawfeed::Installer.create_new_site(site_path, *args)
37
+ when "install"
38
+ Rawfeed::Tools.install
39
+ when "build"
40
+ Rawfeed::Tools.build(*args)
41
+ when "serve"
42
+ Rawfeed::Tools.serve(*args)
43
+ when "backup"
44
+ Rawfeed::Backup.site(*args)
45
+ end
46
+ return
47
+ end
48
+
49
+ # Separate namespace and task for the main commands.
50
+ namespace, task = first_arg.split(":")
51
+
52
+ case namespace
53
+ when "create"
54
+ case task
55
+ when "draft" then Rawfeed::Draft.draft_create(*args)
56
+ when "pixel" then Rawfeed::Pixel.pixel_create(*args)
57
+ when "page" then Rawfeed::Page.page_create(*args)
58
+ else
59
+ puts "Unknown create task: #{task}"
60
+ end
61
+ when "restore"
62
+ case task
63
+ when "donate" then Rawfeed::Donate.restore_page
64
+ when "contact" then Rawfeed::Contact.restore_page
65
+ when "licenses" then Rawfeed::Licenses.restore_page
66
+ when "resume" then Rawfeed::Resume.restore_page
67
+ else
68
+ puts "Unknown restore task: #{task}"
69
+ end
70
+ when "list"
71
+ case task
72
+ when "posts" then Rawfeed::PostList.display
73
+ else
74
+ puts "Unknown list task: #{task}"
75
+ end
76
+ when "home"
77
+ case task
78
+ when "about" then Rawfeed::Layout.home_about(true)
79
+ when "blog" then Rawfeed::Layout.home_blog
80
+ else
81
+ puts "Unknown home task: #{task}"
82
+ end
83
+ when "post"
84
+ case task
85
+ when "draft" then Rawfeed::Post.post(*args)
86
+ else
87
+ puts "Unknown post task: #{task}"
88
+ end
89
+ when "blog"
90
+ case task
91
+ when "enable" then
92
+ Rawfeed::Layout.blog_index(true)
93
+ Rawfeed::Layout.tags_index(true)
94
+ Rawfeed::Layout::change_yml("defaults", "published", true, "_posts")
95
+ Rawfeed::Layout.change_yml("pagination", "enabled", true)
96
+ when "disable" then
97
+ Rawfeed::Layout.blog_index(false)
98
+ Rawfeed::Layout.tags_index(false)
99
+ Rawfeed::Layout::change_yml("defaults", "published", false, "_posts")
100
+ Rawfeed::Layout.change_yml("pagination", "enabled", false)
101
+ else
102
+ puts "Unknown blog task: #{task}"
103
+ end
104
+ when "pixels"
105
+ case task
106
+ when "enable" then Rawfeed::Layout.pixels_index(true)
107
+ when "disable" then Rawfeed::Layout.pixels_index(false)
108
+ else
109
+ puts "Unknown pixels task: #{task}"
110
+ end
111
+ else
112
+ puts "Unknown namespace or command: #{namespace || first_arg}"
113
+ puts "Use:"
114
+ puts " rawfeed help"
115
+ end
116
+ end
117
+ end
118
+ end