Bliz documentation: Scripting tips
The % gem_header 20 text/gemini line
There is a fish function available called `gem_header`. This prints a status code, a mime type, and CRLF. The Gemini protocol needs to see this at the start of each response.
Bliz will automatically add a `20` header with an appropriate mime type when a static file is accessed, but not when a .bliz file is accessed. This means your script can return any status code or mime type it wants to. This flexibility allows .bliz scripts to act as a proxy for image files and audio files, for example.
Don't forget to call `gem_header` as the first piece of output in every .bliz file you create. (If you forget to add it, your client should warn you.)
Accessing script file source code
As you may have noticed on the previous page, appending ?source=1 to any .bliz file allows you to view its source code. This is a default feature, it works on your server too, and it cannot be turned off (unless you edit the Bliz source code and remove this feature).
Why is this a feature?
(a manifesto)
If other people wonder how you coded a feature in your script, it's really helpful for them to be able to look at your file in order to copy segments or draw inspiration from them.
The 1995 web, from independent hobbyists with no formal HTML education, was able to thrive due to the "view page source" feature. If you visited a site and saw a feature of its presentation that you liked, you could open the page source and find out how it is done. It was a mess, and it was a helpful beautiful inspirational mess.
With server-side scripting, the ability to learn and draw from other people's work is diminished, since you never normally get to see the code that would be executed, meaning you can't draw from it in your own work.
The hope of Bliz's ?source=1 parameter is to bring back a world of sharing techniques and learning from each other.
For example, if somebody wants to add a customised dynamic sitemap, they don't have to start from scratch - they can open the source code of my sitemap, copy it, tear it apart, learn from it, and create something new with half the effort.
The `personal` directory
Sometimes you want to use a file in your scripts that actually shouldn't be public. Something like a database, a list of passwords, whatever. I understand. I do this too.
A great place to store that file is in Bliz's `personal/` directory, which is just outside of `serve/`. Since there files are outside of `serve/`, nobody will be able to access them directly, but you'll still be able to use them in your scripts.
For example, the `bliz_hits` built-in function opens a database which is located in `personal/`.
Built-in functions and variables
You can open `src/script-includes.fish` to see a list of built-in functions and variables. These are available for use in any .bliz script.
In addition to those built-ins that come with Bliz, you can write your own functions and variables to always be included by simply adding them to `personal/script-includes.fish`.
These are fish functions, so you can call them in a multitude of ways.
One simple way is after % on a line:
- % function_name_here argument_1 argument_2
Or you can use them any way you want in a fish script. Here is just one example:
- if set result (function_name_here argument_1 argument_2)
function bliz_word_count
Usage: bliz_word_count
This counts the number of words in the current .bliz script. Executable code with % or %%% is not counted. You can use this to add an automatically calculated word count to gemlog articles.
function bliz_paragraph_count
Usage: bliz_paragraph_count
This counts the number of paragraphs in the current .bliz script, excluding special lines (i.e. headers, links, and code). Paragraphs are considered to be separated by two newlines.
function bliz_hits
Usage: bliz_hits
This registers a "hit" for the current path, then returns the all-time total number of hits to that path.
Hits are stored in `personal/hits.db`.
Be sure to only include this once on each page.
function gemlog_intro_meta
Usage: gemlog_intro_meta
This generates an introduction section to a gemlog article containing metadata about that article.
variable $blizfile
This is the path to the currently executing .bliz file on disk.
variable $req_path
This is the absolute URL that the visitor is requesting.
variable $req_query
This is the query string of the URL after the question mark. No additional processing has been performed on it yet.
If you have a script that needs to take simple input data, you can use a URL like:
/document?hello
and $req_query will contain:
hello
If you have a script that needs to take a string with special characters, you can un-percent-encode the query string like so:
string unescape --style=url -- $req_query
variables $req_query_FOO
If you have a script that needs to take key=value pairs, the value for key FOO is stored in variable $req_query_FOO. Only variables matching `^[a-z_]+$` are supported, to avoid users accidentally introducing security issues.
%%%
gem_header 20 text/gemini
set query_names (set -n | string replace -rf '^req_query_' '')
if test -n "$query_names"
echo 'Found these query parameters:' (string join ', ' -- $query_names)
else
echo 'No query parameters found'
end
%%%
In particular,
a = "%$req_query_a"
b = "%$req_query_b"
However, please do note that you are now *handling potentially malicious data in a shell script, of all things.* I think this code is good, but any bugs you write could have catastrophic consequences. If you want to be really safe against malicious input, consider writing the thing in an actual programming language.
% node personal/do-the-thing.js -- "$req_query"
and then...
const params = new URLSearchParams(process.argv[2])
const whatever = params.get("some-key")
variables $req_path_FOO
There is a little-known URL "feature" called path parameters or matrix parameters, where there's a semicolon in the URL, and it's sort of like a query string but associated with a particular path element. Like query strings, they don't contribute to the path of the file. They just convey extra information to the server.
You probably don't need them, but Bliz has them anyway! Note that because fish variables are just lists and can't store highly structured data, Bliz tells you the values of the path parameters but doesn't tell you which path element they're associated with.
%%%
gem_header 20 text/gemini
set param_names (set -n | string replace -rf '^req_path_' '')
if test -n "$param_names"
echo 'Found these path parameters:' (string join ', ' -- $param_names)
else
echo 'No path parameters found'
end
%%%
In particular,
a = "%$req_path_a"
b = "%$req_path_b"
req_path doesn't have path parameters: "%$req_path"
but req_pathparams does have them: "%$req_pathparams"
You might find these useful in Bliz for storing state in the URL without having to worry about the 10 INPUT status code overwriting that data.
%%%
set required name pronouns message
for key in $required
if not set -q req_path_$key
if test $req_query
gem_header 30 "$req_pathparams;$key=$req_query"
else
gem_header 10 "Please enter your $key"
end
exit
end
end
gem_header 20 text/gemini
%%%
This is a preview of your comment:
> %$req_path_message
> - %$req_path_name [%$req_path_pronouns]