GuruHits

Developer / NetCloak

NetCloak was the angle-bracket templating system GuruHits pages ran on under the classic Mac OS WebSTAR server. This page documents the language and provides a clean, MIT-licensed PHP re-implementation you can download and reuse.

The live GuruHits .html files were never plain HTML. They were NetCloak templates — a system of angle-bracket tags (<hide>, <show_variable …>, <macro …>, <exec_cgi …>) that the Mac web server processed on every request before sending the page. This page documents that language and the MIT-licensed PHP engine we wrote to faithfully run it again.

How NetCloak was served

  • 2001 — WebSTAR. At launch, NetCloak ran as a plug-in to WebSTAR (the classic Mac OS web server), which also called the HyperCard quiz engine through an ACGI (<exec_cgi …>). GuruHits stayed on this WebSTAR + HyperCard combination for its whole life.
  • Oct–Nov 2002 — Tenon iTools (the rest of the network). Around this time Joe moved his wider network of NetCloak sites off WebSTAR onto Tenon iTools — an Apache-httpd-based server on Mac OS X 10.2 — where NetCloak ran via Apple's redirect-acgi Apache handler (AddHandler redirect-acgi nclk fdml). GuruHits itself couldn't follow: nothing ran its HyperCard ACGI backend under Apache, so the quiz kept running on WebSTAR.
  • Later (iTools 7) — FastCGI. On iTools, NetCloak then moved to Maxum's FastCGI module (mod_fastcgi.so), with the NetCloak runtime listening on localhost:9008. Adding .html to the handler list is exactly how NetCloak "cloaked" the .html pages it served.

The MIT engine

The re-implementation is a small, dependency-free library:

  • Lexer — tokenizes a template into text + tags (longest-command-first matching, so show_variable wins over show).
  • Interpreter::render($template, $context) — the mode engine: hide/show/flipmode set visibility; consecutive conditionals AND together (the latch that makes exactly one gated block show); plus all the insert_*/set_* handling.
  • Context — the per-request state (locals, variables, cookies, globals, redirect).
  • Two closures keep it game-independent: a macroLoader for <macro /x.macro> includes and an acgiDispatch for the <exec_cgi …> bridge.

Download & use

Download netcloak-php-1.0.0.zip

  • Inside the zip: src/ (the four classes) + tests + a runnable example + composer.json + MIT LICENSE.
  • Machine-readable tag reference: tags.json.
use NetCloak\{Interpreter, Lexer, Context, Rng};

$interp = new Interpreter(new Lexer(), new Rng(),
    fn($relPath) => file_get_contents("macros/$relPath"),  // <macro /x> includes
    fn($cmd, $ctx) => '');                                  // <exec_cgi …> hook

echo $interp->render('Hi <INSERT_VARIABLE name>!', new Context(variables: ['name' => 'Joe']));

Tag reference

Visibility

CommandSyntaxWhat it does
hide<hide>Hide all following output until the next show/flipmode. <hide>secret<show>
show<show>Show all following output until the next hide/flipmode.
flipmode<flipmode>Invert the current hide/show state — the NetCloak 'else'.

Conditions

CommandSyntaxWhat it does
show_local<show_local name = "value">Show the block when local variable `name` matches `value`. (chained conditionals AND together until the next hide/show/flipmode)
hide_local<hide_local name = "value">Hide the block when local variable `name` matches `value`.
show_variable<show_variable name = "value">Show when form/query variable `name` matches `value`. (# = numeric, == = list of alternatives, default = contains)
hide_variable<hide_variable name = "value">Hide when form/query variable `name` matches `value`.
show_cookie<show_cookie name = "value">Show when cookie `name` matches `value`.
hide_cookie<hide_cookie name = "value">Hide when cookie `name` matches `value`.
show_global<show_global name = "value">Show when global variable `name` matches `value`.
hide_global<hide_global name = "value">Hide when global variable `name` matches `value`.
show_client<show_client "WebTV">Show when the visitor's user-agent matches the given client string. <hide><show_client "WebTV">WebTV layout<hide>
hide_client<hide_client "WebTV">Hide when the user-agent matches the given client string.
show_host<show_host "1.2.3.">Show when the requesting host/IP matches.
hide_host<hide_host "1.2.3.">Hide when the requesting host/IP matches.
show_domain<show_domain ".edu">Show when the requesting domain matches.
hide_domain<hide_domain ".edu">Hide when the requesting domain matches.
show_thisurl<show_thisurl "/play">Show when the current request URL matches.
hide_thisurl<hide_thisurl "/play">Hide when the current request URL matches.
show_header<show_header "Referer: ...">Show when a raw request header matches.

Random

CommandSyntaxWhat it does
show_random<show_random threshold [range]>Pick a random number in 1..range (default 100) and show when it is ≤ threshold. (great for rotating ads/banners)
hide_random<hide_random threshold [range]>As show_random, inverted.
show_samerand<show_samerand weight>Reuse the current random draw to choose one weighted block among several (the weights accumulate). (weighted one-of-N selection)
hide_samerand<hide_samerand weight>As show_samerand, inverted.

Set

CommandSyntaxWhat it does
set_local<set_local name = "value">Set a request-scoped local variable. <set_local skin = "itunes">
set_variable<set_variable name = "value">Set a variable in the form/query scope.
set_cookie<set_cookie name = "value">Set a cookie on the response.
set_global<set_global name = "value">Set a server-global variable.

Insert

CommandSyntaxWhat it does
insert_local<insert_local name>Output the value of a local variable. [<insert_local skin>]
insert_variable<insert_variable name>Output the value of a form/query variable. Hi <insert_variable firstname>
insert_cookie<insert_cookie name>Output the value of a cookie.
insert_date<insert_date [year|month|day]>Output the server date (or a component, e.g. the 2-digit year).
insert_time<insert_time>Output the server time.
insert_modified<insert_modified>Output the file's last-modified date.
insert_count<insert_count [name]>Output (and increment) a hit counter.
insert_domain<insert_domain>Output the requesting domain.
insert_thisurl<insert_thisurl>Output the current request URL.
insert_referer<insert_referer>Output the Referer header.
insert_host<insert_host>Output the requesting host/IP.
insert_header<insert_header "Name">Output a raw request header.
insert_random<insert_random range>Output a fresh random number in 1..range.
insert_samerand<insert_samerand>Output the current (reused) random draw.

Flow

CommandSyntaxWhat it does
macro<macro /path.macro>Server-side include: insert and process another template fragment. (handled by the macroLoader closure) <macro /header.macro>
redirect<redirect "url">Stop processing and send an HTTP redirect to url.
exec_cgi<exec_cgi app.acgi command>Call an external (A)CGI and insert its output — the bridge to the HyperCard game engine. (handled by the acgiDispatch closure)

List

CommandSyntaxWhat it does
variable_list<variable_list>...<varname><varvalue>...Iterate over all variables, exposing each via varname/varvalue.
varname<varname>Inside variable_list: output the current variable's name.
varvalue<varvalue>Inside variable_list: output the current variable's value.

Integration

CommandSyntaxWhat it does
mail<mail to ... subject ...>...</mail>Queue an email via the server's NetCloak mail integration.
firesite<firesite ...>Clearway FireSite hook (geo-mirroring of multimedia assets) used by the Euregio.Net network.
audioscope<audioscope ...>GuruHits-era audio-player selection hook for the 5-second clips.

Conversion cookbook (NetCloak → PHP)

Conditional blocks. A NetCloak gated block becomes a render of the same template against a Context:

<hide><show_local rank = "Guru">You made Guru!<hide>
$ctx = new Context();
$ctx->setLocal('rank', $rank);
echo $interp->render($tpl, $ctx);
// chained conditionals AND together, so with several gated lines exactly one shows

Server-side includes. <macro /header.macro> is resolved by your macroLoader closure — point it at wherever your .macro fragments live; the engine processes the included text too.

The CGI bridge. <exec_cgi hypercard.acgi quizresult> becomes a call into your acgiDispatch closure: receive the command string (quizresult) + the Context, and return whatever HTML your back-end produces. In GuruHits's reconstruction this dispatches to the ported HyperTalk quiz logic.