AshReports.FormatSpecification (ash_reports v0.1.0)

Format Specification DSL for AshReports.

This module provides a comprehensive DSL for defining custom formatting patterns that extend beyond the standard CLDR formatting capabilities. Format specifications allow for sophisticated control over how data is presented in reports across all output formats (HTML, HEEX, PDF, JSON).

Features

  • Custom Format Patterns: Define complex formatting rules using intuitive syntax
  • Type-Specific Formatting: Specialized formatting for numbers, currencies, dates, and text
  • Conditional Formatting: Apply different formats based on data values or conditions
  • Locale Integration: Works seamlessly with existing CLDR locale support
  • Cross-Renderer Support: Format specifications work across all output renderers
  • Performance Optimized: Compiled format specifications for efficient processing

Format Specification Syntax

Format specifications use a declarative syntax that combines pattern strings with configuration options:

Basic Pattern Syntax

# Number formatting with custom precision and separators
format_spec :custom_number do
  pattern "#,##0.000"
  locale_aware true
end

# Currency formatting with symbol placement
format_spec :custom_currency do
  pattern "¤ #,##0.00"
  currency :USD
  symbol_position :prefix
end

# Date formatting with custom patterns
format_spec :custom_date do
  pattern "dd/MM/yyyy"
  locale "en-GB"
end

Conditional Formatting

# Apply different formats based on value ranges
format_spec :conditional_number do
  condition value > 1000, pattern: "#,##0K", color: :green
  condition value < 0, pattern: "(#,##0)", color: :red
  default pattern: "#,##0.00"
end

Advanced Pattern Features

# Text formatting with transformations
format_spec :custom_text do
  pattern "%{value}"
  transform :uppercase
  max_length 50
  truncate_suffix "..."
end

# Percentage formatting with custom display
format_spec :custom_percentage do
  pattern "#0.##%"
  multiplier 100
  suffix " percent"
end

Integration with Renderers

Format specifications integrate seamlessly with all AshReports renderers:

In Element Definitions

field :amount do
  source :total_amount
  format_spec :custom_currency
end

expression :growth_rate do
  expression expr(current / previous - 1)
  format_spec :custom_percentage
end

In Band Processing

band :summary do
  elements do
    field :total do
      source :sum_amount
      format_spec conditional_number: [
        {value > 10000, pattern: "$#,##0K", color: :green},
        {value < 0, pattern: "($#,##0)", color: :red}
      ]
    end
  end
end

Performance Considerations

  • Format Compilation: Specifications are compiled at DSL build time for performance
  • Caching: Compiled formatters are cached per locale and specification
  • Lazy Evaluation: Complex conditional formats are evaluated only when needed
  • Memory Efficiency: Format specifications share common components

Error Handling

Format specifications include comprehensive error handling:

  • Pattern Validation: Syntax validation at compile time
  • Runtime Fallbacks: Graceful degradation when formatting fails
  • Type Checking: Ensures format compatibility with data types
  • Locale Validation: Verifies locale availability for format operations

Summary

Types

Conditional formatting rule

Format pattern string with placeholders and formatting directives

Format specification result

Format specification configuration

Format specification identifier

Functions

Adds a conditional formatting rule to a format specification.

Compiles a format specification for efficient runtime use.

Gets the effective format pattern for a given value and context.

Creates a new format specification with the given name and configuration.

Sets the default formatting pattern for a specification.

Validates a format specification without compiling it.

Types

condition_rule()

@type condition_rule() :: {condition :: any(), options :: keyword()}

Conditional formatting rule

format_pattern()

@type format_pattern() :: String.t()

Format pattern string with placeholders and formatting directives

format_result()

@type format_result() :: {:ok, String.t()} | {:error, term()}

Format specification result

format_spec()

@type format_spec() :: %{
  name: format_spec_name(),
  pattern: format_pattern() | nil,
  conditions: [condition_rule()],
  options: keyword(),
  compiled: boolean()
}

Format specification configuration

format_spec_name()

@type format_spec_name() :: atom()

Format specification identifier

Functions

add_condition(spec, condition, format_options)

@spec add_condition(format_spec(), any(), keyword()) :: format_spec()

Adds a conditional formatting rule to a format specification.

Conditional rules are evaluated in order, with the first matching condition being applied. If no conditions match, the default pattern is used.

Parameters

  • spec - The format specification to modify
  • condition - The condition expression to evaluate
  • format_options - Formatting options to apply when condition is true

Examples

spec = AshReports.FormatSpecification.new(:conditional_amount)
|> AshReports.FormatSpecification.add_condition(
     expr(value > 1000),
     pattern: "#,##0K",
     color: :green
   )
|> AshReports.FormatSpecification.add_condition(
     expr(value < 0),
     pattern: "(#,##0)",
     color: :red
   )

compile(spec)

@spec compile(format_spec()) :: {:ok, format_spec()} | {:error, term()}

Compiles a format specification for efficient runtime use.

Compilation validates the format patterns, optimizes condition evaluation, and prepares the specification for integration with the formatting system.

Parameters

  • spec - The format specification to compile

Returns

Returns {:ok, compiled_spec} on success or {:error, reason} if compilation fails.

Examples

spec = AshReports.FormatSpecification.new(:my_format, pattern: "#,##0.00")
{:ok, compiled} = AshReports.FormatSpecification.compile(spec)

get_effective_format(spec, value, context \\ %{})

@spec get_effective_format(format_spec(), any(), map()) ::
  {:ok, {format_pattern(), keyword()}} | {:error, term()}

Gets the effective format pattern for a given value and context.

Evaluates conditional rules against the provided value and returns the appropriate format pattern and options.

Parameters

  • spec - The compiled format specification
  • value - The value to format
  • context - Additional context for condition evaluation

Examples

spec = compiled_conditional_spec()
{:ok, {pattern, opts}} = AshReports.FormatSpecification.get_effective_format(
  spec,
  1500,
  %{locale: "en"}
)

new(name, options \\ [])

@spec new(
  format_spec_name(),
  keyword()
) :: format_spec()

Creates a new format specification with the given name and configuration.

Parameters

  • name - Unique identifier for the format specification
  • options - Configuration options for the specification

Options

  • :pattern - The base format pattern string
  • :locale_aware - Whether the format should respect locale settings (default: true)
  • :type - Expected data type (:number, :currency, :date, :text, :percentage)
  • :fallback - Fallback format specification or pattern
  • :cache - Whether to cache compiled format (default: true)

Examples

iex> spec = AshReports.FormatSpecification.new(:custom_number, pattern: "#,##0.00")
iex> spec.name
:custom_number

set_default_pattern(spec, pattern)

@spec set_default_pattern(format_spec(), format_pattern()) :: format_spec()

Sets the default formatting pattern for a specification.

The default pattern is used when no conditional rules match or when no conditions are defined.

Parameters

  • spec - The format specification to modify
  • pattern - The default format pattern string

Examples

spec = AshReports.FormatSpecification.new(:my_format)
|> AshReports.FormatSpecification.set_default_pattern("#,##0.00")

validate(spec)

@spec validate(format_spec()) :: :ok | {:error, term()}

Validates a format specification without compiling it.

Performs syntax and semantic validation of format patterns, conditions, and options without the overhead of full compilation.

Parameters

  • spec - The format specification to validate

Examples

spec = AshReports.FormatSpecification.new(:test, pattern: "invalid{pattern")
AshReports.FormatSpecification.validate(spec)
# => {:error, "Invalid pattern syntax..."}