Rand Stats

Actionable

zef:librasteve

Actions Status

method raku

method raku() returns Mu

fallback method raku to preempt 'Object<9230298340589>'

NAME

Actionable - auto-populate Raku classes from grammar match objects

SYNOPSIS

use Actionable;

grammar Grammar {
    token TOP {
        <invoice-line>
        [ \n+ <ws> [ <field-line> | <item-line> ] ]*
        \n*
    }
    rule  invoice-line { invoice  <id>                  }
    rule  field-line   { | date   <date>
                         | client <client=quoted>
                         | tax    <tax-rate=number> '%' }
    rule  item-line    { item     <description=quoted>
                         hours    <hours=number>
                         rate     <rate=number>         }
    token id     { <[A..Za..z0..9_-]>+       }
    token date   { \d**4 '-' \d**2 '-' \d**2 }
    token quoted { '"' <( <-["]>+ )> '"'     }
    token number { \d+ [ '.' \d+ ]?          }
    token ws { \h* }  #horizontal whitespace only
}

class Item does Actionable {
    has ($.description, $.hours, $.rate);
    method subtotal { $.hours * $.rate }
}

class Invoice does Actionable {
    has ($.id, $.date, $.client, $.tax-rate = 0);
    has Item @.items;
    method transform(Str $attr, $raw) {
        $attr eq 'tax-rate' ?? $raw / 100 !! $raw
    }
    method subtotal { @.items.map(*.subtotal).sum }
    method tax      { $.subtotal * $.tax-rate }
    method total    { $.subtotal + $.tax }
}

class Actions {
    method TOP($/) {
        my $inv = Invoice.action($<invoice-line>);
         { $inv.action($_) } for $<field-line>;
         { $inv.items.push(Item.action($_)) } for $<item-line>;
        make $inv;
    }
}

DESCRIPTION

Actionable is a role that eliminates boilerplate in Raku grammar Actions classes. Mix it into any class to get an action method that auto-populates attributes from a grammar match object.

action dispatches on whether the invocant is defined:

Attribute mapping

By default each attribute is looked up by its own name as a named capture. To override, provide a capture-map method returning a Hash of attr-name =E<gt> dot-path:

method capture-map {
    { qty   => 'number.0',   # $match<number>[0]
      price => 'number.1' }  # $match<number>[1]
}

Dot-path segments are hash keys or array indices (all-digit segments are treated as indices).

Alternatively, use Raku's aliased capture syntax in the grammar to name captures after the target attribute — avoiding capture-map entirely:

rule item-line { item <description=quoted-string> hours <hours=number> rate <rate=number> }

Type coercion

Attributes typed as Numeric (or any subtype: Int, Real, Rat, Num) are coerced with +; all others with ~. Array and hash attributes are skipped automatically.

Post-coercion transforms

Override transform to adjust a value after coercion:

method transform(Str $attr, $raw) {
    $attr eq 'tax-rate' ?? $raw / 100 !! $raw
}

AUTHOR

librasteve librasteve@furnival.net

COPYRIGHT AND LICENSE

Copyright 2026 librasteve

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.