CLI Runner (asppycli.py)
asppycli.py (in the repository root) renders a single .asp page from the command line and writes the result to stdout — without starting the HTTP server. It uses the exact same VM render pipeline as ASPPY/server.py, including global.asa, Application/Session state, #include resolution and Server.Execute, so the output matches the HTTP server byte-for-byte.
Quick Start
# Render a demo/test page to stdout (sample pages live in www_test)
python asppycli.py www_test/index.asp
# Save output to a file (for diffing against a known-good baseline)
python asppycli.py www_test/01-basics.asp -o out.html
Working Example
Run one of the demo pages that ships with ASPPY (in the www_test folder) and show the response headers:
python asppycli.py www_test/01-basics.asp --show-headers -o out.html
Output (headers go to stderr, the rendered body goes to out.html):
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
Pragma: no-cache
Expires: 0
Set-Cookie: ASP_PY_SESSIONID=4ba3049be4de42228f842e494f41e5ce; Path=/
The process exits with code 0, and out.html contains the fully rendered page — identical to what the HTTP server would send to a browser.
Command-Line Options
| Option | Description |
|---|---|
file |
Path to the .asp file to render (required, positional). |
-o, --output FILE |
Write the rendered body to FILE instead of stdout. Preserves bytes exactly (binary-safe). |
--docroot DIR |
Document root. Defaults to the directory containing the .asp file. Pass it explicitly for MVC apps (e.g. --docroot www) so includes and Server.MapPath() resolve exactly as under the HTTP server. |
--method VERB |
HTTP method to simulate (default GET). |
--query QS |
Query string, e.g. --query "id=42&mode=edit". Available via Request.QueryString. |
--body DATA |
Request body. Implies POST and Content-Type: application/x-www-form-urlencoded, so values arrive in Request.Form. |
--header "Name: Value" |
Extra request header (repeatable). |
--path URL |
Virtual request path seen by Request.Path — use this to test MVC front-controller routes, e.g. --path /contacts/1/edit. |
--show-headers |
Print the HTTP status line and response headers to stderr (keeps stdout clean for the body). |
--session NAME=VALUE |
Preload a Session variable before the page runs (repeatable). Lets you render a page behind a login without replaying the login. |
Testing pages behind a login
Each run gets a fresh session, so a login POST cannot carry over to the next invocation. Instead, set the variable your auth check reads:
python asppycli.py www/servicedeck/api.asp --docroot www \
--query "action=services" --session authed=True
Values are typed the way VBScript would type the literal:
| Argument | Stored as |
|---|---|
--session authed=True | Boolean True |
--session uid=42 | Integer 42 |
--session rate=1.5 | Double 1.5 |
--session name=Pieter | String "Pieter" |
--session e=Empty / --session n=Null | Empty / Null |
--session flag:s=True | String "True" — the :s suffix forces a string |
Variables are applied after Session_OnStart, so they override anything global.asa sets. Only scalars can be injected; a Session entry holding an object reference has to be built by the page itself.
Exit Codes
Designed for scripts: check the exit code before parsing any output.
| Exit Code | Meaning |
|---|---|
0 |
Page rendered with HTTP status < 400. |
1 |
Page rendered with HTTP status >= 400 (e.g. 500 ASP runtime error, 404 unknown route). The error page is still written to stdout. |
2 |
Usage error or .asp file not found. |
Simulating Requests
Form POST
python asppycli.py www_test/07-request-form.asp --method POST --body "username=Ann&color=blue"
The page sees Request.Form("username") = "Ann" and Request.ServerVariables("REQUEST_METHOD") = "POST", exactly as if a browser had submitted the form.
Query strings
python asppycli.py www/report.asp --query "id=42&mode=edit"
MVC front-controller routes
For MVC apps where default.asp routes on Request.Path, point the runner at the front controller and pass the route with --path:
# Renders the starter app home page (route "/")
python asppycli.py www_starter/default.asp --docroot www_starter --path /
# Unknown route: the router's 404 is returned, and the exit code is 1
python asppycli.py www_starter/default.asp --docroot www_starter --path /contacts/99
Error Pages
A VBScript runtime error produces the same IIS-style error page the HTTP server returns — with file, line number and a source caret — plus status 500 and exit code 1:
HTTP/1.1 500 Internal Server Error
<p>ASPPY runtime error '8000ffff'</p>
<p>Variable is undefined: 'UNDEFINEDVAR'</p>
<p>/err.asp, line 2</p>
<p>Response.Write undefinedVar<br>
-^</p>
Batch Checking a Whole Folder (asppycheck.py)
To validate every .asp page in a folder recursively with one command, use the companion script asppycheck.py (also in the repository root). It renders each page through the same VM pipeline and reports every failure with the file, line number and error description:
# Check all sample/test pages
python asppycheck.py www_test
# Check your own app
python asppycheck.py www
# Also list passing pages / stop at the first failure
python asppycheck.py www --verbose
python asppycheck.py www --fail-fast
Example output with one broken page:
FAIL zz-broken.asp [500] Variable is undefined: 'MISSINGVAR' (/zz-broken.asp, line 2)
checked 28 page(s): 27 ok, 0 warning(s), 1 failure(s)
failures:
zz-broken.asp: Variable is undefined: 'MISSINGVAR' (/zz-broken.asp, line 2)
| Exit Code | Meaning |
|---|---|
0 |
Every page rendered with HTTP status < 500. |
1 |
At least one page failed (status >= 500 or engine exception). |
2 |
Folder not found / no .asp files. |
- Pages returning
4xxare reported as warnings only — MVC routers legitimately return404for unknown routes. - Directories named
includesand directories starting with_are skipped by default, because include fragments (headers, footers, helper libraries) cannot render standalone. Add more with--exclude DIR, or scan everything with--no-default-excludes. - For MVC apps, exclude the fragment folder:
python asppycheck.py www --exclude asp— controllers, models and views inwww/asp/are include fragments, not standalone pages. The front controller will WARN with404(no route given); verify real routes withasppycli.py --pathas shown above. - The scanned folder is used as the docroot, so includes and
Server.MapPath()resolve exactly as under the HTTP server.
python asppycheck.py www once instead of re-testing pages one by one — it catches regressions across the whole app in a single command.
Automation and CI
Because output is deterministic and exit codes are meaningful, the runner slots directly into shell loops and CI pipelines. For the common case — "does every page still render?" — prefer asppycheck.py above; the loops below show the building blocks for custom assertions:
# bash: fail the build if any demo page errors
for f in www_test/*.asp; do
python asppycli.py "$f" --docroot www_test -o /dev/null || { echo "FAIL: $f"; exit 1; }
done
# PowerShell: render every page and report failures
Get-ChildItem www_test -Filter *.asp | ForEach-Object {
python asppycli.py $_.FullName --docroot www_test > $null 2>&1
if ($LASTEXITCODE -ne 0) { Write-Host "FAIL: $($_.Name)" }
}
Save a known-good baseline with -o baseline.html, then diff after parser/VM or app changes:
python asppycli.py www_test/01-basics.asp -o new.html
diff baseline.html new.html
Recommended Workflow for AI Agents
- Create or edit a page under
www/. - Run
python asppycli.py www/<page>.asp --docroot www. - Check the exit code:
0means the page rendered without a runtime error. - Inspect stdout for the expected HTML; on exit code
1, read the error page — it names the file, line and source of the failure. - For MVC routes, verify each real route:
--path /contacts/1,--path /contacts/1/edit, and confirm unknown routes exit1with a 404. - After changes that touch shared files (includes, models, layouts), run
python asppycheck.py wwwto batch-verify every page at once.
Notes
- Each invocation is a fresh process: the ASP compilation cache starts empty. (The long-running HTTP server also picks up file edits automatically - its cache invalidates on file change.)
- Session and Application state live only for the duration of the process.
global.asa(Application_OnStart,Session_OnStart) runs exactly as it does under the server. - Output is written as raw bytes, so binary responses and non-UTF-8 charsets survive intact.
- The runner needs no extra dependencies — it only imports from the
ASPPYpackage already in the repository.
Credits
The CLI runner was contributed by Jeffrey (@jeffreyheping) — proposed in issue #10.