rawfeed 0.3.1 → 1.0.0
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 +4 -4
- data/.editorconfig +25 -0
- data/.gitignore +19 -0
- data/.gitlab/ci/gitlab-pages.yml +51 -0
- data/.hidden +6 -0
- data/404.html +6 -0
- data/Gemfile +51 -0
- data/README.md +102 -26
- data/_config.yml +77 -0
- data/_data/generic.yml +29 -0
- data/_data/screen/blog.yml +46 -0
- data/_data/screen/contact.yml +31 -0
- data/_data/screen/donate.yml +45 -0
- data/_data/screen/footer.yml +17 -0
- data/_data/screen/head.yml +49 -0
- data/_data/screen/home.yml +24 -0
- data/_data/screen/maintenance.yml +8 -0
- data/_data/screen/navbar.yml +41 -0
- data/_data/screen/page.yml +12 -0
- data/_data/screen/pixels.yml +7 -0
- data/_data/screen/pub.yml +4 -0
- data/_data/{resume.yml → screen/resume.yml} +8 -8
- data/_data/screen/socials.yml +20 -0
- data/_includes/assigned +36 -0
- data/_includes/layout/blog_search.html +22 -15
- data/_includes/layout/disqus.html +7 -8
- data/_includes/layout/footer.html +43 -28
- data/_includes/layout/giscus.html +15 -19
- data/_includes/layout/head.html +97 -75
- data/_includes/layout/maintenance.html +14 -11
- data/_includes/layout/navbar.html +253 -0
- data/_includes/layout/paginator.html +12 -13
- data/_includes/{alert → markdown/alert} +2 -2
- data/_includes/{chart → markdown/chart} +3 -3
- data/_includes/{image → markdown/image} +4 -4
- data/_includes/{socials → markdown/socials} +5 -5
- data/_includes/{video → markdown/video} +2 -2
- data/_layouts/blog/index.html +53 -0
- data/_layouts/{post.html → blog/post.html} +50 -36
- data/_layouts/blog/posts_by_tag.html +27 -0
- data/_layouts/blog/tags.html +32 -0
- data/_layouts/contact.html +156 -114
- data/_layouts/default.html +53 -48
- data/_layouts/donate.html +112 -0
- data/_layouts/error.html +9 -7
- data/_layouts/home.html +22 -19
- data/_layouts/licenses.html +2 -2
- data/_layouts/page.html +18 -17
- data/_layouts/pixels/index.html +75 -0
- data/_layouts/{pixel.html → pixels/post.html} +5 -5
- data/_layouts/pub.html +50 -44
- data/_layouts/resume.html +308 -265
- data/_pages/any-page.md +336 -0
- data/_pages/contact.md +16 -0
- data/_pages/donate.md +16 -0
- data/_pages/licenses.md +17 -0
- data/_pages/resume.md +14 -0
- data/_pixels/2025-10-15-first-my-pixel.md +34 -0
- data/_posts/2025-09-20-welcome-to-jekyll.md +36 -0
- data/_posts/2025-09-25-this-post-demonstrates-post-codeblocks.md +141 -0
- data/_posts/2025-09-25-this-post-demonstrates-post-content-styles.md +133 -0
- data/_posts/2025-10-04-content-styles-and-codeblocks.md +330 -0
- data/_sass/components/_markdown.scss +2 -2
- data/_sass/includes/_index.scss +2 -2
- data/_sass/includes/{_header.scss → _navbar.scss} +2 -6
- data/_sass/includes/_toc.scss +146 -0
- data/_sass/layouts/_blog.scss +1 -1
- data/_sass/layouts/_index.scss +2 -2
- data/_sass/layouts/_page.scss +0 -5
- data/_sass/layouts/_post.scss +8 -138
- data/_sass/layouts/{_tag-posts.scss → _posts_by_tag.scss} +3 -3
- data/_sass/layouts/_resume.scss +1 -1
- data/_sass/layouts/{_tag.scss → _tags.scss} +10 -6
- data/_sass/main.scss +2 -2
- data/_sass/theme/_dark.scss +2 -2
- data/_sass/theme/_light.scss +1 -1
- data/assets/js/blog.js +52 -18
- data/assets/js/contact.js +21 -20
- data/assets/js/default.js +67 -50
- data/assets/js/discus.js +2 -2
- data/assets/js/{donation.js → donate.js} +10 -9
- data/assets/js/home.js +18 -16
- data/assets/js/page.js +50 -172
- data/assets/js/toc.js +133 -0
- data/assets/json/blog_search.json +5 -7
- data/assets/vendors/fuse.min.js +9 -0
- data/blog/index.md +14 -0
- data/blog/tags/index.md +12 -0
- data/exe/rawfeed +29 -0
- data/index.md +15 -0
- data/lib/rawfeed/build/cleaner.rb +60 -0
- data/lib/rawfeed/build/image_minifier.rb +163 -0
- data/lib/rawfeed/build/minifier.rb +89 -0
- data/lib/rawfeed/build.rb +9 -0
- data/lib/rawfeed/command/backup.rb +124 -0
- data/lib/rawfeed/command/cli.rb +118 -0
- data/lib/rawfeed/command/installer.rb +156 -0
- data/lib/rawfeed/command/tools.rb +138 -0
- data/lib/rawfeed/{author.rb → content/author.rb} +7 -7
- data/lib/rawfeed/content/contact.rb +51 -0
- data/lib/rawfeed/content/donate.rb +48 -0
- data/lib/rawfeed/{draft.rb → content/draft.rb} +5 -3
- data/lib/rawfeed/content/licenses.rb +46 -0
- data/lib/rawfeed/{page.rb → content/page.rb} +4 -3
- data/lib/rawfeed/{pixel.rb → content/pixel.rb} +7 -4
- data/lib/rawfeed/content/post.rb +107 -0
- data/lib/rawfeed/{resume.rb → content/resume.rb} +23 -19
- data/lib/rawfeed/{layout.rb → core/layout.rb} +1 -1
- data/lib/rawfeed/core/utils.rb +103 -0
- data/lib/rawfeed/{version.rb → core/version.rb} +1 -1
- data/lib/rawfeed/{datelang.rb → plugin/datelang.rb} +5 -6
- data/lib/rawfeed/{pub.rb → plugin/pub.rb} +33 -31
- data/lib/rawfeed/{reading_time.rb → plugin/reading_time.rb} +5 -4
- data/lib/rawfeed/root.rb +3 -0
- data/lib/rawfeed.rb +36 -14
- data/pixels/index.md +11 -0
- data/robots.txt +26 -0
- metadata +153 -195
- data/_data/options.yml +0 -329
- data/_includes/layout/data.liquid +0 -31
- data/_includes/layout/header.html +0 -173
- data/_layouts/blog.html +0 -52
- data/_layouts/donation.html +0 -113
- data/_layouts/pixels.html +0 -71
- data/_layouts/tag.html +0 -33
- data/_layouts/tag_posts.html +0 -28
- data/assets/vendor/simple-jekyll-search.js +0 -433
- data/assets/vendor/simple-jekyll-search.min.js +0 -6
- data/lib/rawfeed/installer.rb +0 -37
- data/lib/rawfeed/post.rb +0 -60
- data/lib/rawfeed/utils.rb +0 -75
- /data/_includes/{details → markdown/details} +0 -0
- /data/_includes/{enddetails → markdown/enddetails} +0 -0
- /data/_includes/{endtabs → markdown/endtabs} +0 -0
- /data/_includes/{tabs → markdown/tabs} +0 -0
- /data/assets/images/{avatar_back.png → avatars/back.png} +0 -0
- /data/assets/images/{avatar_dark.png → avatars/dark.png} +0 -0
- /data/assets/images/{avatar_light.png → avatars/light.png} +0 -0
- /data/assets/images/{icons → donate}/bitcoin.svg +0 -0
- /data/assets/images/{icons → donate}/card.svg +0 -0
- /data/assets/images/{icons → donate}/donation.svg +0 -0
- /data/assets/images/{icons → donate}/lightning_network.svg +0 -0
- /data/assets/images/{icons → donate}/paypal.svg +0 -0
- /data/assets/images/{icons → donate}/pix.svg +0 -0
- /data/assets/images/{qrcode_btc_binance.jpg → donate/qrcode_btc_binance.jpg} +0 -0
- /data/assets/images/{qrcode_inter.jpg → donate/qrcode_inter.jpg} +0 -0
- /data/assets/images/{qrcode_wos.jpg → donate/qrcode_wos.jpg} +0 -0
- /data/assets/images/{icons → donate}/wos.png +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.min.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.min.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.min.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-grid.rtl.min.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.min.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.min.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.min.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-reboot.rtl.min.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.min.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.min.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.min.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap-utilities.rtl.min.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.min.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.min.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.min.css +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/css/bootstrap.rtl.min.css.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.js +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.js.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.min.js +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.bundle.min.js.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.js +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.js.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.min.js +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.esm.min.js.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.js +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.js.map +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.min.js +0 -0
- /data/assets/{vendor/bootstrap → vendors/bootstrap-5.2.3}/js/bootstrap.min.js.map +0 -0
- /data/lib/rawfeed/{csp_filters.rb → plugin/csp_filters.rb} +0 -0
- /data/lib/rawfeed/{typescript_liquid.rb → plugin/typescript_liquid.rb} +0 -0
- /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,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
|