Skip to content

Phony Expression Language (PEL)

PEL is the template syntax used within Phony for composing and transforming generated data. It uses {{expression}} placeholders that are resolved at generation time.

Basic Syntax

text
┌─────────────────────────────────────────────────────────────────────────┐
│           PEL SYNTAX OVERVIEW                                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  PLACEHOLDERS:                                                           │
│  ──────────────                                                          │
│  { {generator_name} }            Reference a generator                   │
│  { {generator.property} }        Access nested property                  │
│  { {function(arg)} }             Apply transformation                    │
│  { {function(gen, arg)} }        Transform generator output              │
│                                                                          │
│  LITERAL TEXT:                                                           │
│  ──────────────                                                          │
│  Everything outside { {...} } is literal text                           │
│                                                                          │
│  ESCAPING:                                                               │
│  ─────────                                                               │
│  \{ \{ produces literal { {                                             │
│  \} \} produces literal } }                                             │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Note: Template syntax uses double curly braces: {{ expression }}


Generator References

Basic Reference

Reference any generator defined in the schema:

json
{
  "generators": {
    "first_name": {
      "type": "model",
      "source": "models/names.ngram"
    },
    "greeting": {
      "type": "template",
      "pattern": "Hello, {{first_name}}!"
    }
  }
}

Output: "Hello, Mehmet!"

Entity Field Reference

Reference fields from entities (for relationships):

json
{
  "entities": {
    "User": {
      "fields": {
        "id": { "generator": "user_id" },
        "name": { "generator": "first_name" }
      }
    },
    "Order": {
      "fields": {
        "user_id": { "ref": "User.id" }
      }
    }
  }
}

Self Reference

Reference other fields in the same entity:

json
{
  "entities": {
    "User": {
      "fields": {
        "first_name": { "generator": "first_name" },
        "last_name": { "generator": "last_name" },
        "full_name": {
          "type": "template",
          "pattern": "{{self.first_name}} {{self.last_name}}"
        }
      }
    }
  }
}

Nested Property Access

Access properties of object-returning generators:

json
{
  "generators": {
    "country": {
      "type": "list",
      "source": "lists/countries.json"
    },
    "phone": {
      "type": "template",
      "pattern": "{{country.phone_prefix}} {{pattern:### ### ## ##}}"
    }
  }
}

Output: "+90 532 847 23 91"

Note: List file returns objects like { "name": "Turkey", "code": "TR", "phone_prefix": "+90" }


Inline Generators

Generate values directly within templates without separate generator definitions.

Numbers

yaml
{{number:1-100}}           # Random int 1-100
{{number:0.0-1.0}}         # Random float 0.0-1.0
{{number:0.0-1.0:2}}       # Random float, 2 decimal places
{{number:1-100:step:5}}    # Random from 1,5,10,15...100

Patterns

Pattern characters:

  • # → Random digit (0-9)
  • ? → Random letter (A-Z)
  • ^ → Random hex (0-F)
  • * → Random alphanumeric
yaml
{{pattern:###-##-####}}           # 123-45-6789 (SSN format)
{{pattern:??-####}}               # AB-1234 (license plate)
{{pattern:^^^^-^^^^-^^^^-^^^^}}   # A3F2-B7C1-9D4E-2F8A (hex code)
{{pattern:****-****}}             # K7b2-9Xm4 (alphanumeric)

Dates and Times

yaml
{{date:2020-01-01..2024-12-31}}             # Random date in range
{{date:2020-01-01..now}}                     # From date to now
{{datetime:-1year..now}}                     # Relative ranges
{{time:09:00..18:00}}                        # Random time
{{datetime:now-7days..now:ISO8601}}          # With format

UUIDs and IDs

yaml
{{uuid}}                    # UUID v4
{{uuid:v7}}                 # UUID v7
{{ulid}}                    # ULID
{{nanoid}}                  # Nano ID (21 chars)
{{nanoid:10}}               # Nano ID (10 chars)

List Operations

pick()

Random selection from a list:

yaml
{{pick(email_domains)}}           # Random from generator/list
{{pick(["gmail.com", "yahoo.com", "hotmail.com"])}}  # Inline list

pick_weighted()

Weighted random selection:

yaml
{{pick_weighted(order_status)}}   # Uses weights from generator definition

cycle()

Sequential cycling through values:

yaml
{{cycle(days_of_week)}}           # Monday, Tuesday, ... (repeats)
{{cycle(["A", "B", "C"])}}        # A, B, C, A, B, C, ...

sample()

Multiple unique selections:

yaml
{{sample(tags, 3)}}               # Pick 3 unique items
{{sample(categories, 2-5)}}       # Pick 2-5 unique items

String Transformations

Case Transformations

yaml
{{lowercase(first_name)}}         # mehmet
{{uppercase(city)}}               # İSTANBUL
{{capitalize(word)}}              # Mehmet
{{titlecase(full_name)}}          # Mehmet Yılmaz
{{camelcase(field_name)}}         # fieldName
{{snakecase(field_name)}}         # field_name
{{kebabcase(field_name)}}         # field-name
{{pascalcase(field_name)}}        # FieldName

String Operations

yaml
{{trim(text)}}                    # Remove whitespace
{{truncate(description, 50)}}     # First 50 chars
{{truncate(desc, 50, "...")}}     # With suffix
{{pad(code, 6, "0")}}             # 000042
{{padRight(code, 6, "0")}}        # 420000
{{reverse(text)}}                 # Reverse string
{{repeat(char, 5)}}               # Repeat 5 times

Slug and URL

yaml
{{slugify(product_name)}}         # yilmaz-holding-as
{{urlEncode(query)}}              # URL-safe encoding
{{base64(data)}}                  # Base64 encode

Data Masking and Privacy

Masking

yaml
{{mask(phone, 4)}}                # ******4567 (show last 4)
{{mask(phone, 4, "start")}}       # 0532****** (show first 4)
{{mask(email, "partial")}}        # m***t@g***l.com
{{mask(credit_card, 4, "*")}}     # ************1234

Hashing

yaml
{{hash(email)}}                   # SHA256 hash
{{hash(email, "md5")}}            # MD5 hash
{{hash(email, "sha1")}}           # SHA1 hash
{{hmac(data, secret)}}            # HMAC-SHA256

Anonymization

yaml
{{anonymize(name)}}               # Consistent fake replacement
{{anonymize(email, "domain")}}    # Keep domain, fake local part
{{redact(ssn)}}                   # [REDACTED]

Numeric Operations

Arithmetic

yaml
{{add(price, tax)}}               # price + tax
{{subtract(total, discount)}}     # total - discount
{{multiply(price, quantity)}}     # price * quantity
{{divide(total, count)}}          # total / count
{{modulo(number, 10)}}            # number % 10

Formatting

yaml
{{format(price, "currency")}}     # $1,234.56
{{format(price, "currency:TRY")}} # ₺1.234,56
{{format(number, "decimal:2")}}   # 1234.56
{{format(number, "percent")}}     # 12.34%
{{format(bytes, "filesize")}}     # 1.5 MB

Math Functions

yaml
{{round(number)}}                 # Round to integer
{{round(number, 2)}}              # Round to 2 decimals
{{floor(number)}}                 # Round down
{{ceil(number)}}                  # Round up
{{abs(number)}}                   # Absolute value
{{min(a, b)}}                     # Minimum
{{max(a, b)}}                     # Maximum
{{clamp(value, min, max)}}        # Constrain to range

Date and Time Functions

Current Time

yaml
{{now()}}                         # Current datetime
{{today()}}                       # Current date
{{now("Y-m-d")}}                  # Formatted

Date Manipulation

yaml
{{addDays(date, 7)}}              # Add 7 days
{{addMonths(date, 3)}}            # Add 3 months
{{addYears(date, 1)}}             # Add 1 year
{{subtractDays(date, 7)}}         # Subtract 7 days
{{startOfMonth(date)}}            # First day of month
{{endOfMonth(date)}}              # Last day of month
{{startOfYear(date)}}             # January 1

Date Formatting

yaml
{{format(date, "Y-m-d")}}         # 2024-03-15
{{format(date, "d/m/Y")}}         # 15/03/2024
{{format(date, "F j, Y")}}        # March 15, 2024
{{format(datetime, "ISO8601")}}   # 2024-03-15T14:30:00Z
{{format(datetime, "RFC2822")}}   # Fri, 15 Mar 2024 14:30:00 +0000

Date Calculations

yaml
{{dateDiff(start, end, "days")}}  # Days between
{{dateDiff(start, end, "months")}}# Months between
{{age(birthdate)}}                # Years since date

Conditional Logic

if()

yaml
{{if(age > 65, "Senior", "Adult")}}
{{if(score >= 90, "A", if(score >= 80, "B", "C"))}}  # Nested
{{if(is_premium, premium_price, regular_price)}}

switch()

yaml
{{switch(status,
  active: "Aktif",
  pending: "Beklemede",
  cancelled: "İptal",
  default: "Bilinmiyor"
)}}

{{switch(gender,
  M: "Bay",
  F: "Bayan",
  default: ""
)}}

Null Coalescing

yaml
{{coalesce(nickname, first_name)}}        # First non-null
{{default(middle_name, "")}}              # Default if null
{{optional(suffix)}}                       # Empty string if null

Computed Fields

For complex calculations:

json
{
  "entities": {
    "OrderItem": {
      "fields": {
        "quantity": { "generator": "quantity" },
        "unit_price": { "generator": "price" },
        "discount_rate": { "generator": "discount" },
        "subtotal": { "computed": "unit_price * quantity" },
        "discount_amount": { "computed": "subtotal * discount_rate" },
        "total": { "computed": "subtotal - discount_amount" },
        "tax": { "computed": "total * 0.18" },
        "grand_total": { "computed": "total + tax" }
      }
    }
  }
}

Computed Syntax

json
{ "computed": "expression" }

Available operators:

  • + - * / % — Arithmetic
  • == != < > <= >= — Comparison
  • && || ! — Logical
  • ( ) — Grouping

Examples:

json
{ "computed": "price * quantity" }
{ "computed": "price * (1 - discount_rate)" }
{ "computed": "if(quantity > 10, price * 0.9, price) * quantity" }
{ "computed": "round(total * tax_rate, 2)" }

Uniqueness Constraints

unique

Ensure unique values:

json
{
  "generators": {
    "email": {
      "type": "template",
      "pattern": "{{lowercase(first_name)}}.{{lowercase(last_name)}}@{{pick(domains)}}",
      "unique": true
    }
  }
}

unique with fallback

Handle exhaustion:

json
{
  "generators": {
    "username": {
      "type": "template",
      "pattern": "{{lowercase(first_name)}}{{number:1-99}}",
      "unique": true,
      "unique_fallback": "user_{{uuid:v4}}"
    }
  }
}

unique within scope

json
{
  "generators": {
    "product_code": {
      "type": "template",
      "pattern": "{{category.code}}-{{number:0001-9999}}",
      "unique_within": "category"
    }
  }
}

Full Example

json
{
  "$schema": "https://phony.cloud/schemas/pdl-1.0.json",
  "version": "1.0",
  "locale": "tr_TR",

  "generators": {
    "first_name": {
      "type": "model",
      "source": "models/names.ngram"
    },
    "last_name": {
      "type": "model",
      "source": "models/surnames.ngram"
    },
    "country": {
      "type": "list",
      "source": "lists/countries.json"
    },
    "full_name": {
      "type": "template",
      "pattern": "{{first_name}} {{last_name}}"
    },
    "email": {
      "type": "template",
      "pattern": "{{lowercase(first_name)}}.{{lowercase(last_name)}}@{{pick(['gmail.com', 'yahoo.com', 'hotmail.com'])}}",
      "unique": true
    },
    "phone": {
      "type": "template",
      "pattern": "{{country.phone_prefix}} {{pattern:### ### ## ##}}"
    },
    "username": {
      "type": "template",
      "variants": [
        { "pattern": "{{lowercase(first_name)}}{{number:1-999}}", "weight": 50 },
        { "pattern": "{{lowercase(first_name)}}.{{lowercase(last_name)}}", "weight": 30 },
        { "pattern": "{{lowercase(first_name)}}_{{number:1000-9999}}", "weight": 20 }
      ],
      "unique": true
    },
    "address": {
      "type": "template",
      "variants": [
        { "pattern": "{{street_name}} No:{{number:1-200}} {{district}}/{{city}}", "weight": 40 },
        { "pattern": "{{neighborhood}} Mah. {{street_name}} No:{{number:1-150}}", "weight": 35 },
        { "pattern": "{{street_name}} {{number:1-100}}/{{number:1-20}} {{city}}", "weight": 25 }
      ]
    }
  },

  "entities": {
    "User": {
      "fields": {
        "id": {
          "type": "logic",
          "algorithm": "uuid_v7",
          "primary_key": true
        },
        "first_name": { "generator": "first_name" },
        "last_name": { "generator": "last_name" },
        "full_name": { "generator": "full_name" },
        "email": { "generator": "email" },
        "phone": { "generator": "phone" },
        "username": { "generator": "username" },
        "address": { "generator": "address" },
        "initials": {
          "type": "template",
          "pattern": "{{uppercase(substring(self.first_name, 0, 1))}}{{uppercase(substring(self.last_name, 0, 1))}}"
        },
        "created_at": {
          "type": "logic",
          "algorithm": "datetime_between",
          "params": { "start": "-2years", "end": "now" }
        },
        "is_verified": {
          "type": "logic",
          "algorithm": "boolean",
          "params": { "probability": 0.75 }
        }
      }
    }
  }
}

Quick Reference

CategorySyntaxExample
Reference{{name}}{{first_name}}
Nested{{obj.prop}}{{country.code}}
Number{{number:min-max}}{{number:1-100}}
Pattern{{pattern:format}}{{pattern:###-####}}
Pick{{pick(list)}}{{pick(colors)}}
Transform{{func(val)}}{{lowercase(name)}}
Conditional{{if(cond,t,f)}}{{if(age>18,"Adult","Minor")}}
Computedcomputed: "expr"computed: "price * qty"

Phony Cloud Platform Specification