# USEE Text Format (FTU) Specification

## Data Exchange Format of the Protocol

**Version 1.0**

> **Note on vocabulary:** USEE is a Spanish-originated protocol. The acronym **FTU** (*Formato de Texto USEE*) is kept as the normative name — not translated to "UTF" to avoid conflict with UTF-8. Field names and values shown in code blocks (`nombre`, `si`, `no`, `usuario`, etc.) are part of the normative standard and must be used as-is, the same way HTML tags like `<div>` are not translated. The surrounding prose is in English; the identifiers are not.

---

## Purpose

The USEE Text Format (FTU) is the common language of every USEE piece. It defines how input and output data are structured so that any piece can communicate with any other, regardless of programming language or platform.

### Design Principles

The format was designed following the USEE principles:

| Principle | How it applies to the format |
|-----------|------------------------------|
| **Useful** | Expresses any data a piece might need to communicate |
| **Simple** | Parseable with basic code in any language |
| **Essential** | Only the necessary features, nothing superfluous |
| **Enduring** | Based on plain text, it will work 50 years from now |

### Goals

1. A programmer should be able to write a complete parser in under 2 hours
2. A non-technical person should be able to read and understand the data
3. It should not require external libraries to be processed
4. It should be editable with any text editor

---

## Formal Specification

### 1. Encoding

- **Character encoding**: UTF-8
- **Line ending**: LF (`\n`) or CRLF (`\r\n`), both accepted
- **Indentation**: 2 spaces (where applicable)

### 2. Line Structure

An FTU document is a sequence of lines. Each line is one of:

| Type | Identification | Example |
|------|----------------|---------|
| Key-value pair | Contains `:` | `nombre: Juan` |
| Comment | Starts with `#` | `# This is a comment` |
| Separator | Exactly `---` | `---` |
| Continuation | Starts with 2 spaces | `  continued line` |
| Empty | No content | `` |

### 3. Key-Value Pairs

#### Syntax

```
key: value
```

#### Key Rules

| Rule | Valid | Invalid |
|-------|--------|----------|
| Lowercase only | `nombre` | `Nombre`, `NOMBRE` |
| Letters, digits, underscore | `fecha_nacimiento`, `direccion2` | `fecha-nacimiento` |
| Must start with a letter | `usuario1` | `1usuario` |
| No spaces | `nombre_completo` | `nombre completo` |
| No special characters | `direccion` | `dirección`, `año` |
| Max length: 64 characters | `campo_corto` | `campo_extremadamente_largo_que_supera_el_limite_establecido_de_caracteres` |

#### Regular Expression for Keys

```
^[a-z][a-z0-9_]{0,63}$
```

#### Value Rules

- Everything after `:` and the optional space is the value
- Leading and trailing whitespace in the value is trimmed
- A value may be empty
- A value may contain any UTF-8 character, including `:`

#### Examples

```
# Simple value
nombre: Juan Pérez

# Value with colons (the first one is the separator, the rest are part of the value)
horario: 10:30:00

# Value with spaces (internal spaces are preserved)
direccion: Avenida Reforma 222, Piso 5

# Empty value (valid)
comentario:

# Spaces around : are ignored (all equivalent)
edad: 30
edad:30
edad :30
edad : 30
```

---

### 4. Value Types

FTU has no strict types. Every value is text. However, it defines conventions for interpreting common values:

#### 4.1 Text

Any sequence of characters.

```
mensaje: Hola, este es un texto libre con acentos: é, ñ, ü
```

#### 4.2 Numbers

A sequence of digits, optionally with sign and decimal point.

```
edad: 30
temperatura: -5.5
precio: 1234.56
```

**Rules:**
- Decimal separator: period `.`
- No thousands separator
- Negative sign: hyphen `-` at the start

#### 4.3 Booleans

Two possible values: `si` or `no`

```
activo: si
eliminado: no
```

**Note:** Variants such as `true`, `false`, `1`, `0`, or `sí` (with accent) are not accepted.

#### 4.4 Dates and Times

Simplified ISO 8601 format.

```
# Date only
fecha_nacimiento: 2025-01-15

# Date and time (UTC implied)
creado: 2025-01-15T10:30:00

# Date and time with timezone
modificado: 2025-01-15T10:30:00-06:00
```

**Accepted formats:**
- `YYYY-MM-DD` — Date only
- `YYYY-MM-DDTHH:MM:SS` — Date and time
- `YYYY-MM-DDTHH:MM:SSZ` — Date and time in UTC
- `YYYY-MM-DDTHH:MM:SS±HH:MM` — Date and time with timezone

#### 4.5 Lists

Multiple values separated by a comma and a space.

```
etiquetas: rojo, verde, azul
numeros: 1, 2, 3, 4, 5
```

**Rules:**
- Separator: `, ` (comma followed by space)
- Elements are trimmed
- Empty list: empty value or missing key

**Examples:**

```
# Three-element list
colores: rojo, verde, azul

# One-element list
colores: rojo

# Empty list
colores:
```

#### 4.6 Null Value

The absence of a value, or the absence of the key entirely, represents null.

```
# Explicitly empty value (null)
segundo_nombre:

# Equivalent: omit the key
```

---

### 5. Comments

#### Syntax

```
# This is a comment
```

#### Rules

- A line is a comment if its first non-space character is `#`
- Comments are ignored entirely during parsing
- There are no end-of-line comments

```
# This is a valid comment
nombre: Juan  # This is NOT a comment, it is part of the value

  # This is also a comment (spaces before #)
```

#### Recommended Usage

```
# === User Section ===
nombre: Juan
edad: 30

# Contact data
correo: juan@example.com
telefono: 555-1234
```

---

### 6. Record Separator

#### Syntax

```
---
```

#### Purpose

Allows multiple independent records to be included in a single document.

#### Rules

- Exactly three hyphens on a line by themselves
- No spaces before or after
- Each section between separators is an independent record

#### Example

```
nombre: Juan
edad: 30
---
nombre: María
edad: 25
---
nombre: Pedro
edad: 40
```

This represents three records, each with `nombre` and `edad`.

#### Special Cases

```
# Separator at the start (first record is empty and ignored)
---
nombre: Juan

# Separator at the end (last record is empty and ignored)
nombre: Juan
---

# Consecutive separators (empty records in the middle are ignored)
nombre: Juan
---
---
nombre: María
```

---

### 7. Multiline Values

#### Syntax

```
clave: |
  First line of the value
  Second line of the value
  Third line of the value
```

#### Rules

- The lone `|` (pipe) character indicates a multiline value
- Following lines must be indented with exactly 2 spaces
- The value ends when an unindented line, or a line with fewer than 2 leading spaces, appears
- Line breaks are preserved
- The 2-space indentation is stripped from the final value

#### Example

```
descripcion: |
  This is a long text that
  spans multiple lines.
  
  It can even have blank lines
  in the middle of the content.
siguiente_campo: valor normal
```

**Resulting value of `descripcion`:**
```
This is a long text that
spans multiple lines.

It can even have blank lines
in the middle of the content.
```

#### Whitespace

```
poema: |
  Dos caminos se bifurcaban
    en un bosque amarillo,
  y apenado por no poder
    tomar los dos...
```

Additional spaces (beyond the 2 of indentation) are preserved.

---

### 8. Nested Data

#### Syntax

```
padre.hijo: valor
padre.hijo.nieto: otro valor
```

#### Purpose

Represents hierarchical structures without using braces or brackets.

#### Rules

- The period `.` indicates hierarchy
- Each segment follows the key naming rules
- There is no depth limit (though 4 levels maximum is recommended)

#### Example

```
usuario.nombre: Juan Pérez
usuario.edad: 30
usuario.direccion.calle: Reforma 222
usuario.direccion.ciudad: Ciudad de México
usuario.direccion.pais: México
usuario.roles: admin, editor
```

**Equivalent logical structure:**
```
usuario
├── nombre: Juan Pérez
├── edad: 30
├── direccion
│   ├── calle: Reforma 222
│   ├── ciudad: Ciudad de México
│   └── pais: México
└── roles: [admin, editor]
```

#### Combining with Lists

For lists of objects, use either the record separator or numeric indices:

**Option A: Record separator**
```
# List of users
nombre: Juan
rol: admin
---
nombre: María
rol: editor
---
nombre: Pedro
rol: lector
```

**Option B: Numeric indices**
```
usuarios.0.nombre: Juan
usuarios.0.rol: admin
usuarios.1.nombre: María
usuarios.1.rol: editor
usuarios.2.nombre: Pedro
usuarios.2.rol: lector
```

Option A is recommended for lists of complete records; Option B is recommended when the records are mixed with other data.

---

### 9. Empty Lines

- Empty lines are ignored during parsing
- They may be used freely to improve readability
- They do not affect the document's structure

```
nombre: Juan

edad: 30


ciudad: México
```

Is equivalent to:

```
nombre: Juan
edad: 30
ciudad: México
```

---

### 10. Key Order

- The order of keys is NOT significant
- Parsers may return keys in any order
- If a key appears more than once, the last value wins

```
nombre: Juan
nombre: Pedro
```

The final value of `nombre` is `Pedro`.

---

## Formal Grammar

### EBNF Notation

```ebnf
document       = { line } ;
line           = ( pair | comment | separator | continuation | empty ) , line_end ;
pair           = key , ":" , [ space ] , value ;
key            = letter , { letter | digit | "_" } ;
value          = simple_value | multiline_value ;
simple_value   = { character } ;
multiline_value = "|" , line_end , { indented_line } ;
indented_line  = "  " , { character } , line_end ;
comment        = { space } , "#" , { character } ;
separator      = "---" ;
continuation   = "  " , { character } ;
empty          = { space } ;
letter         = "a" | "b" | ... | "z" ;
digit          = "0" | "1" | ... | "9" ;
space          = " " | "\t" ;
line_end       = "\n" | "\r\n" ;
character      = (* any UTF-8 character except line_end *) ;
```

---

## Parsing Algorithm

### Pseudocode

```
function parse_ftu(text):
    records = []
    current_record = {}
    multiline_key = null
    multiline_value = []
    
    for each line in text.split_lines():
        clean_line = line.strip_trailing_whitespace()
        
        # Finalize multiline value if necessary
        if multiline_key != null:
            if line.starts_with("  "):
                multiline_value.append(line.substring(2))
                continue
            else:
                current_record[multiline_key] = multiline_value.join("\n")
                multiline_key = null
                multiline_value = []
        
        # Empty line
        if clean_line == "":
            continue
        
        # Comment
        if clean_line.starts_with("#"):
            continue
        
        # Separator
        if clean_line == "---":
            if current_record is not empty:
                records.append(current_record)
                current_record = {}
            continue
        
        # Key-value pair
        if clean_line.contains(":"):
            position = clean_line.index_of(":")
            key = clean_line.substring(0, position).trim()
            value = clean_line.substring(position + 1).trim()
            
            if value == "|":
                multiline_key = key
                multiline_value = []
            else:
                current_record[key] = value
            continue
        
        # Invalid line (ignore or report an error, depending on implementation)
        warn("Unrecognized line: " + line)
    
    # Finalize last multiline value
    if multiline_key != null:
        current_record[multiline_key] = multiline_value.join("\n")
    
    # Append last record
    if current_record is not empty:
        records.append(current_record)
    
    return records
```

---

## Conversion to Other Formats

### FTU to JSON

#### Conversion Rules

| FTU | JSON |
|-----|------|
| `clave: valor` | `"clave": "valor"` |
| `clave: 123` | `"clave": 123` (if numeric) |
| `clave: si` | `"clave": true` |
| `clave: no` | `"clave": false` |
| `clave: a, b, c` | `"clave": ["a", "b", "c"]` |
| `clave:` (empty) | `"clave": null` |
| `padre.hijo: valor` | `"padre": {"hijo": "valor"}` |
| Separator `---` | Array elements |

#### Example

**FTU:**
```
nombre: Juan
edad: 30
activo: si
roles: admin, editor
direccion.ciudad: México
direccion.pais: MX
---
nombre: María
edad: 25
activo: no
roles: lector
direccion.ciudad: Bogotá
direccion.pais: CO
```

**JSON:**
```json
[
  {
    "nombre": "Juan",
    "edad": 30,
    "activo": true,
    "roles": ["admin", "editor"],
    "direccion": {
      "ciudad": "México",
      "pais": "MX"
    }
  },
  {
    "nombre": "María",
    "edad": 25,
    "activo": false,
    "roles": ["lector"],
    "direccion": {
      "ciudad": "Bogotá",
      "pais": "CO"
    }
  }
]
```

### JSON to FTU

The reverse process follows the same rules.

**Special rules:**
- Arrays of primitives → comma-separated list
- Arrays of objects → records separated by `---`
- Nested objects → dot notation
- `null` → empty value
- `true` → `si`
- `false` → `no`

---

## Error Handling

### Recoverable Errors

The parser should continue processing on these errors:

| Error | Behavior |
|-------|---------------|
| Line without `:` | Ignore the line, emit a warning |
| Duplicate key | Use the last value |
| Key with invalid format | Try to normalize it, or ignore it |
| Empty record | Ignore |

### Fatal Errors

The parser should stop on these errors:

| Error | Reason |
|-------|-------|
| Non-UTF-8 encoding | Content cannot be interpreted |
| Binary file | Not text |

---

## Reserved Extensions

The following features are reserved for future versions of the format. Current parsers should ignore them if encountered:

| Syntax | Reserved Purpose |
|----------|---------------------|
| `@directiva` | Processing directives |
| `<<clave` | References to other values |
| `!tipo` | Explicit type annotations |
| `[indice]` | Array access (alternative to `.N`) |

---

## Full Examples

### Login Piece Input

```
# Authentication request
# Generated: 2025-01-15T10:30:00Z

usuario: maria@example.com
clave: contraseña_segura_123
recordar: si

# Additional options
origen: web
ip_cliente: 192.168.1.100
```

### Successful Output of the Login Piece

```
estado: ok
sesion_id: ses_7f8a9b2c3d4e5f6g
expira: 2025-01-16T10:30:00Z

usuario.id: usr_001
usuario.nombre: María García
usuario.correo: maria@example.com
usuario.roles: admin, editor

metadata.tiempo_respuesta_ms: 45
metadata.servidor: auth-01
```

### Error Output

```
estado: error
codigo: credenciales_invalidas
mensaje: The email or password is incorrect
detalle: No match was found in the database
sugerencia: Check that the email is spelled correctly

metadata.intentos_restantes: 2
metadata.bloqueado_hasta:
```

### List of Results

```
# User search results
# Total: 3 records

nombre: Juan Pérez
correo: juan@example.com
rol: admin
activo: si
---
nombre: María García
correo: maria@example.com
rol: editor
activo: si
---
nombre: Pedro López
correo: pedro@example.com
rol: lector
activo: no
```

### Piece Configuration

```
# Configuration for the email piece
# File: correo.config.usee

servidor.host: smtp.example.com
servidor.puerto: 587
servidor.seguro: si

credenciales.usuario: sistema@example.com
credenciales.clave: clave_smtp_segura

opciones.reintentos: 3
opciones.timeout_segundos: 30
opciones.registro_envios: si

plantilla.saludo: |
  Dear {nombre},
  
  Thank you for contacting us.
plantilla.despedida: |
  Sincerely,
  The support team
```

---

## Reference Implementations

To facilitate adoption, reference parsers will be provided in:

- Python
- JavaScript
- Go
- Rust
- C

These parsers will be available as USEE pieces:

```
usee-ftu-python
usee-ftu-javascript
usee-ftu-go
usee-ftu-rust
usee-ftu-c
```

---

## Syntax Summary

| Element | Syntax | Example |
|----------|----------|---------|
| Key-value pair | `clave: valor` | `nombre: Juan` |
| Comment | `# text` | `# This is a comment` |
| List | `clave: a, b, c` | `roles: admin, editor` |
| Boolean | `si` / `no` | `activo: si` |
| Date | `YYYY-MM-DD` | `fecha: 2025-01-15` |
| Date-time | ISO 8601 | `creado: 2025-01-15T10:30:00Z` |
| Separator | `---` | `---` |
| Multiline | `clave: \|` + indented | See section 7 |
| Nested | `padre.hijo: valor` | `usuario.nombre: Juan` |
| Empty/null | `clave:` | `comentario:` |

---

**FTU**: Text that humans and machines understand alike.
