💾 Archived View for cadence.moe › bliz-documentation › 04-scripting-tips.bliz captured on 2024-08-18 at 19:17:28. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2022-08-13)

🚧 View Differences

-=-=-=-=-=-=-

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.

Source code of my sitemap

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:

Or you can use them any way you want in a fish script. Here is just one example:

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.

You can see this in use on my personal gemlog.

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_]+ gemini - kennedy.gemi.dev 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"

Render this demo with ?a=1+2&b=hello%20world

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.

Read more about them on StackOverflow.

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"

Visit this demo at demos/path-parameters-keys-values.bliz;a=1+2&b=hello%20world

Visit this demo at demos;a=foo;b=bar/path-parameters-keys-values.bliz;b=baz

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]

Run this demo on my server

Download source code

Next page: Supported protocols

Back to documentation index