Reputation: 5027
My Jekyll page is meant to provide sample solutions for a coding book's exercises. The solutions are a bunch of .cpp files (C++ code files) stored in a folder inside my Jekyll project so that I can open the same folder in an IDE.
I've managed to auto-generate one page per book chapter which displays the relevant solutions as a list of code blocks (one per each exercise). I do that by looping through site.static_files
and identifying the files by numbers in their filenames (e.g. two solutions for chapter 1: 01_1_FirstSolution.cpp
, 01_2_SecondSolution.cpp
)
Now, I want to also provide a zip archive per book chapter containing the relevant .cpp files. I don't want to make the zip file manually because then I would not be able to simply change one of the code files anymore. Ideally, I would like to build a zip file while looping through site.static_files
and filtering for the relevant files.
When searching for this, I mainly found speed-optimization plugins for bundling and compressing assets. I am running Jekyll on Windows.
Upvotes: 2
Views: 1174
Reputation: 5027
There is the jekyll-zip-bundler plugin for this.
Filenames as multiple parameters:
{% zip archiveToCreate.zip file1.txt file2.txt %}
Spaces in filenames:
{% zip archiveToCreate.zip file1.txt folder/file2.txt 'file with spaces.txt' %}
A variable to contain a list of files is also possible:
{% zip ziparchiveToCreate.zip {{ chapter_code_files }} %}
# frozen_string_literal: true
# Copyright 2021 by Philipp Hasper
# MIT License
# https://github.com/PhilLab/jekyll-zip-bundler
require 'jekyll'
require 'zip'
# ~ gem 'rubyzip', '~>2.3.0'
module Jekyll
# Valid syntax:
# {% zip archiveToCreate.zip file1.txt file2.txt %}
# {% zip archiveToCreate.zip file1.txt folder/file2.txt 'file with spaces.txt' %}
# {% zip {{ variableName }} file1.txt 'folder/file with spaces.txt' {{ otherVariableName }} %}
# {% zip {{ variableName }} {{ VariableContainingAList }} %}
class ZipBundlerTag < Liquid::Tag
VARIABLE_SYNTAX = /[^{]*(\{\{\s*[\w\-.]+\s*(\|.*)?\}\}[^\s{}]*)/mx.freeze
CACHE_FOLDER = '.jekyll-cache/zip_bundler/'
def initialize(tag_name, markup, tokens)
super
# Split by spaces but only if the text following contains an even number of '
# Based on https://stackoverflow.com/a/11566264
# Extended to also not split between the curly brackets of Liquid
# In addition, make sure the strings are stripped and not empty
@files = markup.strip.split(/\s(?=(?:[^'}]|'[^']*'|{{[^}]*}})*$)/)
.map(&:strip)
.reject(&:empty?)
end
def render(context)
# First file is the target zip archive path
target, files = resolve_parameters(context)
abort 'zip tag must be called with at least two files' if files.empty?
zipfile_path = CACHE_FOLDER + target
FileUtils.makedirs(File.dirname(zipfile_path))
# Create the archive. Delete file, if it already exists
File.delete(zipfile_path) if File.exist?(zipfile_path)
Zip::File.open(zipfile_path, Zip::File::CREATE) do |zipfile|
files.each do |file|
# Two arguments:
# - The name of the file as it will appear in the archive
# - The original file, including the path to find it
zipfile.add(File.basename(file), file)
end
end
puts "Created archive #{zipfile_path}"
# Add the archive to the site's static files
site = context.registers[:site]
site.static_files << Jekyll::StaticFile.new(site, "#{site.source}/#{CACHE_FOLDER}",
File.dirname(target),
File.basename(zipfile_path))
# No rendered output
''
end
def resolve_parameters(context)
# Resolve the given parameters to a file list
target, files = @files.map do |file|
next file unless file.match(VARIABLE_SYNTAX)
# This is a variable. Look it up.
context[file]
end
[target, files]
end
end
end
Liquid::Template.register_tag('zip', Jekyll::ZipBundlerTag)
Upvotes: 1
Reputation: 21
Jekyll is a primarily static website builder, so perhaps it'd be ideal to run a program or shell scripts before building with Jekyll and have them output to the site.static_files
location you wish to output to as above.
You could use a script before running Jekyll, to zip your files and then run the Jekyll build process.
There are also Generator plugins which you could write to achieve similar results, using either the plugin, or Ruby's system
commands to run scripts as part of your build.
Upvotes: 1
Reputation: 4381
If it's possible to get content of your files (fetch etc.) you may use JSZip library to pack them in Zip. I'm not sure that this fits for you in jekyll... but i would try doing like this.
To try an example just create an html file or open my example with fetch.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js" integrity="sha512-uVSVjE7zYsGz4ag0HEzfugJ78oHCI1KhdkivjQro8ABL/PRiEO4ROwvrolYAcZnky0Fl/baWKYilQfWvESliRA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="module" src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js" integrity="sha512-csNcFYJniKjJxRWRV1R7fvnXrycHP6qDR21mgz1ZP55xY5d+aHLfo9/FcGDQLfn2IfngbAHd8LdfsagcCqgTcQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<script>
var zip = new JSZip();
zip.file("Hello.txt", "Hello World\n");
zip.file("Hello2.txt", "Hello2 World\n");
//var img = zip.folder("images");
//img.file("smile.gif", imgData, {base64: true});
zip.generateAsync({type:"blob"})
.then((content) => {
// see FileSaver.js
saveAs(content, "example.zip");
});
</script>
</body>
</html>
Upvotes: 2