iyi

Standard library, 0.18.0

std/eiy

Eiy: embedded iyi. A template is text with iyi code inside it, and this module turns the template into iyi source — at compile time, so a rendered template is ordinary compiled code by the time the program runs.

import std/eiy to use it. Source: src/std/eiy.iyi at v0.18.0.

The tags:

* `<%= expr %>` prints the expression's `to_s`.
* `<% code %>` runs the code; `<% if x %>` ... `<% end %>` and every other
  iyi statement work, because the code is spliced into the program as is.
* `<%- ... %>` drops the indentation before the tag when the tag is the
  first thing on its line; `<% ... -%>` drops the rest of the line after
  it, newline included. Both keep a control line from leaving a blank one.
* `<%# note %>` is a comment: nothing is printed and nothing runs.
* `<%% ... %>` is the text `<% ... %>` itself, for a template about
  templates.

Printing goes through one verb, print, so a template writes to anything that has it: an IyiIO (stdout, a File) or Eiy::Buffer, which collects the text for render.

Compile time:

* `Eiy.embed("page.eiy", io)` splices the template's code here; `io`
  names what it prints to.
* `Eiy.render("page.eiy")` is the rendered text as a `String`.
* `Eiy.def_to_s("page.eiy")` in a class defines `to_s(io)` and `to_s`
  from the template.
The template path is read beside the file that writes the macro, or as
itself when absolute. The macros run `eiy/process`, an iyi program in this
directory that reads the template with the lexer below and prints the
source; it is compiled once and cached with the compiler's other macro
programs, and rebuilt when this module changes.

Run time:

* `Eiy.process_string(text, filename, io_name)` and
  `Eiy.process_file(filename, io_name)` are that translation as a value;
  `Eiy::Lexer` is the tokeniser under it, for a tool that wants tokens.

What it refuses: a tag that is never closed, with its line and column (unterminated <%= tag at line 3, column 5); next_token?, process_string? and process_file? answer nil for the same template. A template file that cannot be read is refused by File.read's own sentence. Positions count lines from 1 and columns from 1 in characters, not bytes, so a column on a line with non-ASCII text is the column a reader sees.

Eiystruct

std/eiyiyi doc src/std/eiy.iyi
pub struct Eiy
  # Collects what a template prints, for `render` or for a caller that wants
  # the text rather than a stream.
  class Buffer
    def initialize
    def print(text : String) : Nil
    def print(value) : Nil
    # `print`, the one verb a template's target has, so a Buffer can be one.
    def to_s(io) : Nil
    def to_s : String
  end
  class Lexer
    class Token
      enum Type : Int32
        String = 0
        Output = 1
        Control = 2
        EOF = 3
      end
      def column_number : Int32
      def column_number=(value : Int32) : Int32
      def control? : Bool
      def eof? : Bool
      def initialize
      def line_number : Int32
      def line_number=(value : Int32) : Int32
      def output? : Bool
      def string? : Bool
      def suppress_leading : Bool
      def suppress_leading=(value : Bool) : Bool
      def suppress_leading? : Bool
      def suppress_trailing : Bool
      def suppress_trailing=(value : Bool) : Bool
      def suppress_trailing? : Bool
      def type : Type
      def type=(value : Type) : Type
      def value : String
      def value=(value : String) : String
    end
    # Why the last `next_token?` answered nil, or nil while it has not.
    def error : String | ::Nil
    def initialize(string : String)
    # The next token; the same `Token` object is reused, so copy what is
    # needed before asking again. Panics on a tag that is never closed.
    def next_token : Token
    # The next token, or nil on a tag that is never closed; `error` then says
    # where.
    def next_token? : Token | ::Nil
  end
  def initialize
  # Where `embed` reads *template* from when it is written in *caller*: the
  # template itself when absolute, otherwise beside the caller.
  def self.locate(template : String, caller : String) : String
  # `process_string` of the file at *filename*.
  def self.process_file(filename : String, io_name : String = DefaultIoName) : String
  # `process_string?` of the file at *filename*.
  def self.process_file?(filename : String, io_name : String = DefaultIoName) : String | ::Nil
  # The iyi source that prints the template *string* to *io_name*. Panics on
  # a tag that is never closed, naming *filename* when there is one.
  def self.process_string(string : String, filename : String = "", io_name : String = DefaultIoName) : String
  # The iyi source that prints the template *string* to *io_name*, or nil
  # for a tag that is never closed.
  def self.process_string?(string : String, filename : String = "", io_name : String = DefaultIoName) : String | ::Nil
  # Defines `to_s(io)` and `to_s` from the template at *filename*. The
  # parameter is `__io__`, a name no template means, so a template's own
  # `io` stays its own.
  macro def_to_s(filename)
  # Splices the template at *filename* here; its code prints to *io_name*.
  macro embed(filename, io_name)
  # The template at *filename*, rendered to a `String`. The buffer's name is
  # the macro's own, so a template that renders another one inside it does
  # not replace the buffer it is printing to.
  macro render(filename)
end