Ir al contenido

Sintaxis

Esta es la referencia del lenguaje para el formato de bloque .httui — lo que parsea httui-lang, lo que valida httui-lsp, lo que la gramática tree-sitter ilumina.

Un archivo .httui es una secuencia de bloques, separados por líneas en blanco. Cada bloque abre con una línea de fence, luego tiene un body, opcionalmente seguido por una sección de expect.

<block> ::= <fence-line> NEWLINE
<body>
( "# expect:" NEWLINE <expect-line>+ )?
<fence-line> ::= ( "http" | "db-" CONN-NAME ) <info-token>*

Un runbook .md embebe la misma sintaxis dentro de bloques fenced de código:

```http alias=login
POST {{BASE_URL}}/auth/login
```

La línea de apertura ```http juega el rol de <fence-line>. La línea de cierre ``` termina el body. Tree-sitter maneja ambos modos (.httui standalone y .md embebido).

Después de la palabra del lenguaje (http o db-<conn>), tokens adicionales configuran el bloque. Pares key=value separados por whitespace.

http alias=login timeout=5000 display=split mode=raw
^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^
lang alias timeout display mode
TokenTipo de valorDefaultSignificado
aliasidentifiernonenombre del bloque (requerido para chain)
timeoutint (ms)30000 HTTP / 60000 DBtimeout de la request
displayinput | output | splitsplitlayout del panel del editor
moderaw | formrawmodo del body editor (solo HTTP)
connectionstringsolo DB; alternativa a la fence db-<name> (raro)

Orden canónico para serialización: alias → timeout → display → mode. El editor de httui reformatea a este orden al save.

<alias> y <connection-name> siguen las reglas estándar de identifier:

ident ::= [a-zA-Z_] [a-zA-Z0-9_-]*

Letras, dígitos, underscores, hyphens. Sin espacios, sin puntos, sin $.

Lo que sigue a la línea de fence hasta la fence de cierre (o EOF en un archivo .httui).

HTTP-body ::= <request-line> NEWLINE
( <header-line> NEWLINE )*
( NEWLINE <message-body> )?

Request line:

GET https://api.example.com/users/{{id}}
^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
verb URL (con interpolación de {{ref}})

El verb es uno de GET | POST | PUT | PATCH | DELETE | HEAD | OPTIONS. La URL es cualquier string, incluyendo expresiones {{ref}}, hasta el fin de línea.

Headers: líneas estándar de header HTTP (Name: value). Una por línea. Los values pueden contener {{ref}}.

Body: cualquier cosa después de la primera línea en blanco, hasta el cierre. El header Content-Type le dice al runtime cómo parsearlo; al lenguaje no le importa.

DB-body ::= <sql-statement> ( ";" <sql-statement> )*

SQL libre. Se permite multi-statement (separado por ;). Los {{ref}} dentro de SQL son convertidos a bind parameter por el runtime — el lenguaje los parsea como expresiones de referencia, pero el executor nunca hace string-interpolación.

Después del body, una sección opcional # expect: convierte al bloque en una assertion.

<expect-section> ::= "# expect:" NEWLINE
( "#" <assertion> NEWLINE )+
<assertion> ::= <field> <operator> <literal>
| <field> "exists"
| <field> "not" "exists"
| <field> "is" <type-name>
FieldFuente
statusHTTP status
timetotal elapsed (ms a menos que tenga suffix)
sizebytes del response body
body.<path>JSON path al response body
headers.<name>response header (case-insensitive)
cookies.<name>valor set-cookie
affected (DB)filas afectadas
row[N].<col> (DB)columna de n-ésima fila
rows.length (DB)row count
OpSignificado
==, !=igualdad / desigualdad estricta
<, <=, >, >=comparación numérica
contains, not containsmembership de string / array
matchesregex /.../
exists, not existspath resuelve
ischeck de tipo
time < 500ms time < 1.5s time < 1m
size < 10kb size < 100mb
SufijoSignifica
ms s mtiempo
b kb mb gbbytes (potencias de 1024)

La expresión {{...}} — completamente detallada en Referencias entre bloques, resumen aquí.

<reference> ::= "{{" ( <alias-ref> | <env-ref> | <positional-ref> ) "}}"
<alias-ref> ::= IDENT "." <field> ( "." <path-segment> )*
<env-ref> ::= IDENT # sin puntos, busca env vars
<positional-ref> ::= "$prev" ( "." <path-segment> )*
<path-segment> ::= IDENT | "[" INT "]" | "[" QUOTED-STRING "]"

Prioridad de resolución: alias de bloque > env var. Si ambos tienen el mismo nombre, el bloque gana.

# este es un comentario

Un # al inicio de una línea es un comentario, excepto dentro de la sección expect donde # introduce assertions. La gramática tree-sitter los distingue por contexto.

Los comentarios inline (mid-line) no están soportados. Un # foo al final de una línea dentro de una URL se trata como parte de la URL.

  • El whitespace al final de las líneas se ignora.
  • Una línea en blanco dentro del body separa headers del body (HTTP).
  • Una línea en blanco entre bloques separa bloques (en archivos .httui).
  • En runbooks .md, los delimitadores de fence sobreescriben las reglas de blank-line (el body de una fence es todo hasta la fence de cierre).

Estas keys no pueden ser usadas como valor de alias:

alias timeout display mode connection

Otros identifiers son libres.

Para implementadores de tree-sitter y parsers — la gramática completa:

file ::= block ( BLANK-LINE+ block )*
block ::= fence-line NEWLINE body expect-section?
fence-line ::= ( "http" | "db-" ident ) info-token*
info-token ::= info-key "=" info-value
info-key ::= "alias" | "timeout" | "display" | "mode" | "connection"
info-value ::= ident | int | "raw" | "form" | "input" | "output" | "split"
body ::= http-body | db-body
http-body ::= request-line NEWLINE
( header-line NEWLINE )*
( BLANK-LINE message-body )?
request-line::= verb SP url
verb ::= "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"
db-body ::= sql-statement ( ";" sql-statement )*
expect-section ::= "# expect:" NEWLINE
( "#" SP assertion NEWLINE )+
assertion ::= field op literal
| field "exists"
| field "not" SP "exists"
| field "is" SP type-name
reference ::= "{{" ( alias-ref | env-ref | "$prev" path? ) "}}"
alias-ref ::= ident "." field-name path?
env-ref ::= ident
path ::= ( "." ident | "[" int "]" | "[" QUOTED "]" )+
ident ::= [a-zA-Z_] [a-zA-Z0-9_-]*

(Simplificado — el source de tree-sitter es la versión canónica.)