💾 Archived View for cadence.moe › bliz-documentation › 04-scripting-tips.bliz captured on 2022-03-01 at 15:06:45. Gemini links have been rewritten to link to archived content

View Raw

More Information

➡️ Next capture (2022-04-28)

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

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 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

If you have a script that needs to take key=value pairs, you should use code that looks something like this:

%%%
gem_header 20 text/gemini
# split on & to get a list of encoded key=value parameters
for parameter in (string split -- '&' $req_query)
    # split on = (just once) to get a tuple of an encoded key and value
    # then decode the name and value into appropriately named variables
    string split -m 1 -- = $parameter | string unescape --style=url | read -L name value
    # let's see what we've got!
    # note: it is NOT safe to `set $name $value` to bind the query parameters to fish variables - what if a malicious visitor overwrites a variable like $blizfile? that would be really bad!
    # it SHOULD BE safe to set the variables in a namespace like `set query_param_$name $value` if you want to.
    printf 'name: %s\n  value: %s\n' "$name" "$value"
end
%%%

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

However, please do note that you are now *parsing potentially malicious data in a shell script, of all things.* I think this code is good, but any bugs 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")

Next page: Supported protocols

Back to documentation index