Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1936 lines (1399 sloc) 47.7 KB
language: ruby
title: enorb
version: 0.10.2
-- intro
> Everyone is welcome to improve this documentation by editing [ruby.eno](https://github.com/eno-lang/eno-lang.org/blob/master/src/docs/ruby.eno) and submitting a Pull Request!
## Installation
### Bundler
Add `enorb` to your `Gemfile`:
```ruby
gem 'enorb'
```
Then let bundler install it for you:
```shell
bundle
```
### Manually
Alternatively you can also install it manually:
```shell
gem install enorb
```
## Getting started
Create an eno document, for instance `intro.eno`:
```eno
Greeting: Hello World!
```
A minimal example to read this file with `enorb`:
```ruby
require 'enorb'
input = File.read('intro.eno')
document = Eno.parse(input)
puts document.field('Greeting') #=> 'Hello World!'
```
## Links
Gem on rubygems - <https://rubygems.org/gems/enorb/>
Repository on github - <https://github.com/eno-lang/enorb/>
-- intro
# Modules
## Eno
-- class description
The main module. You'll be calling `parse` on this, and possibly supplying a custom
locale (such as `'de'`, `'es'`, ...), reporter type (`Eno::Reporters::Text`, `Eno::Reporters::HTML`, `Eno::Reporters::Terminal` are available)
or source label (usually to have filename appear in error messages) to that call.
-- class description
### ::parse
syntax:
- parse(input) → Eno::Section
- parse(input, options) → Eno::Section
-- description
Parse a string in eno notation.
-- description
-- eno
color: blue
-- eno
-- ruby
Eno.parse(input) #=> #<Eno::Section document elements=1>
# Errors will be ...
Eno.parse(input, locale: 'es') # In spanish - ¡hola!
Eno.parse(input, reporter: Eno::Reporters::HTML) # HTML for e.g. web integration
Eno.parse(input, reporter: Eno::Reporters::Terminal) # Colored for terminal output
Eno.parse(input, source_label: 'my-file.eno') # Annotated with a label as context
Eno.parse(input, zero_indexing: true) # Counting line and column numbers from 0
-- ruby
#### parameters
input: A string containing text in eno notation.
options:
locale = A string specifying the code of the locale to use for error messages (e.g. `'de'`, `'es'`), by default `'en'`.
reporter = One of `Eno::Reporters::Text`, `Eno::Reporters::HTML`, `Eno::Reporters::Terminal`.
source_label = A string that labels where the input comes from (e.g. a filename), this is included with error messages to help the user located the file.
zero_indexing = `true` or `false` (default), set `true` to display `0,1,2,..` line and column numbers in all error messages instead of the default `1,2,3,..` indexing.
#### return value
description: An `Eno::Section` representing the document.
## Eno::Loaders
-- class description
enorb provides a set of core loaders for important types that are available
out of the box (in addition to the possiblity to define your own custom loaders, which can
be bootstrapped and made accessible directly through the core loader API or be passed as
arguments, e.g. as lambdas, to all accessors).
The loaders are exposed through the API as drop in replacements to the standard accessors:
- `Eno::Field`
- `#value` => `#[loader_name]`
- `Eno::Fieldset`
- `#entry` => `#[loader_name]`
- `Eno::List`
- `#items` => `#[loader_name]_items`
- `Eno::Section`
- `#field` => `#[loader_name]`
- `#list` => `#[loader_name]_list`
So for instance, instead of calling ...
```ruby
document.field('done', required: true) #=> 'yes'
document.list('visitor_counts', required: true) #=> [ '476', '213', '330', ... ]
```
... you can just replace `field` or augment `list` with the loader name ...
```ruby
document.boolean('done', required: true) #=> true
document.integer_list('visitor_counts', required: true) #=> [ 476, 213, 330, ... ]
```
... and the method signature stays exactly the same as for the original accessor (except you can't provide a loader as an argument anymore).
Here's another full example:
```ruby
require 'enorb'
doc = parse(
<<~DOC
publish: yes
location: 36.987094, -25.091719
contact: contact@faulty
DOC
)
doc.boolean('publish')
#=> true
doc.lat_lng('location')
#=>
# {
# lat: 36.987094,
# lng: -25.091719
# }
doc.email('contact')
# raises an error: 'contact' must contain a valid email address, for instance 'jane.doe@eno-lang.org'.
```
Note that some loaders only perform validation and return their input unaltered
as string (e.g. `color`, `email`), while others both validate and transform the
value into a new type (e.g. `float`, `boolean`) or even object (e.g. `lat_lng`).
-- class description
### #boolean
-- description
Accepts `true`, `false`, `yes` and `no`, returns a `boolean` (or raises an error for invalid values).
-- description
syntax:
- boolean → boolean
- boolean_items → array
- boolean_list → array
-- eno
enabled: true
state_sequence:
- true
- false
- true
visible: not
-- eno
-- ruby
document = Eno.parse(input)
document.boolean('enabled') #=> true
document.boolean_list('state_sequence') #=> [ true, false, true ]
document.boolean('visible')
# raises an error: 'visible' must contain a boolean - allowed values are 'true', 'false', 'yes' and 'no'.
-- ruby
### #color
-- description
Accepts values formatted as `#RRGGBB` or `#RGB` (case insensitive), returns the value unchanged (or raises an error for invalid values).
-- description
syntax:
- color → string
- color_items → array
- color_list → array
-- eno
states:
active = #F00
hover = #ff0000
focus = blue
-- eno
-- ruby
states = Eno.parse(input).fieldset('states')
states.color('active') #=> '#F00'
states.color('hover') #=> '#ff0000'
states.color('focus')
# raises an error: 'focus' must contain a color, for instance '#B6D918', '#fff' or '#01b'.
-- ruby
### #comma_separated
-- description
Splits a comma-separated listing (single line, think a list of tags e.g.) into an array and strips whitespace.
-- description
syntax:
- comma_separated → array
- comma_separated_items → array
- comma_separated_list → array
-- eno
Tags: Coffee, Desk, Flavor
Categories: Computing , High performance , Cluster
-- eno
-- ruby
document = Eno.parse(input)
document.comma_separated('Tags') #=> ['Coffee', 'Desk', 'Flavor']
document.comma_separated('Categories') #=> ['Computing', 'High performance', 'Cluster']
-- ruby
### #date
-- description
Accepts `YYYY-MM-DD` and returns a Ruby `Time` object (or raises an error for invalid values).
-- description
syntax:
- date → Time
- date_items → array
- date_list → array
-- eno
start: 1992-12-01
end: 1994-03-04
invalid: 2018-03-14 13:10
-- eno
-- ruby
document = Eno.parse(input)
document.date('start') #=> #<Time>
document.date('end') #=> #<Time>
document.date('invalid')
# raises an error: 'invalid' must contain a valid date, for instance '1993-11-18'.
-- ruby
### #datetime
-- description
Accepts a subset of ISO 8601 date/time formats described in this W3C note: <https://www.w3.org/TR/NOTE-datetime>
Returns a ruby `Time` object (or raises an error for invalid values).
Some possible example values that match the format:
- `1990`
- `1990-12`
- `1990-12-31`
- `1990-12-31T23:59Z`
- `1990-12-31T23:59:00+01:00`
- `1990-12-31T23:59:00.999+01:00`
Note that spaces as separators are not allowed.
Development note:
A more appealing, less technical datetime format or an additional loader to serve that requirement would be generally welcome,
the current format is basically just an initial draft that should cover many usecases and is standardized through its
ISO origin, feel free to get in touch and propose an extension/update if you'd be interested in such development.
-- description
syntax:
- datetime → Time
- datetime_items → array
- datetime_list → array
-- eno
start: 1992-12-01
end: 1994-03-04T13:14Z
invalid: 2018-03-14 13:10
-- eno
-- ruby
document = Eno.parse(input)
document.datetime('start') #=> 1992-12-01 00:00:00 +0000
document.datetime('end') #=> 1994-03-04 13:14:00 +0000
document.datetime('invalid')
# raises an error: 'invalid' must contain a valid date or date and time, for instance '1997-07-16' or '1994-11-05T13:15Z' (see https://www.w3.org/TR/NOTE-datetime).
-- ruby
### #float
-- description
Accepts floats (decimal part can be missing though), returns a float (or raises an error for malformed values).
-- description
syntax:
- float → Float
- float_items → array
- float_list → array
-- eno
good: 49.9
fine: -2
bad: three
-- eno
-- ruby
document = Eno.parse(input)
document.float('good') #=> 49.9
document.float('fine') #=> -2.0
document.float('bad')
# raises an error: 'bad' must contain a decimal number, for instance '13.0', '-9.159' or '42'.
-- ruby
### #integer
-- description
Accepts integers only (float is not clipped but triggers an error), returns an integer.
-- description
syntax:
- integer → Integer
- integer_items → array
- integer_list → array
-- eno
good: -13
bad: 0.3
-- eno
-- ruby
document = Eno.parse(input)
document.integer('good') #=> -13
document.integer('bad')
# raises an error: 'bad' must contain an integer, for instance '42' or '-21'.
-- ruby
### #json
-- description
Parses the value as JSON and returns the result (or passes on the parser error for malformed JSON).
-- description
syntax:
- json → hash/array/value
- json_items → array
- json_list → array
-- eno
-- json payload
{
"status": 500
}
-- json payload
-- eno
-- ruby
document = Eno.parse(input)
document.json('json payload') #=> { status: 500 }
-- ruby
### #lat_lng
-- description
Accepts lat/lng coordinates of the form `dd.ddddd, dd.ddddd` (but with any number of decimal digits), returns a `Hash` (see example below) (or raises an error for invalid values).
-- description
syntax:
- lat_lng → hash
- lat_lng_items → array
- lat_lng_list → array
-- eno
location: 36.987094, -25.091719
-- eno
-- ruby
document = Eno.parse(input)
document.lat_lng('location')
#=>
# {
# lat: 36.987094,
# lng: -25.091719
# }
-- ruby
### #number
-- description
An alias for the `integer` loader.
-- description
syntax:
- number → Integer
- number_items → array
- number_list → array
-- eno
good: -13
bad: 0.3
-- eno
-- ruby
document = Eno.parse(input)
document.number('good') #=> -13
document.number('bad')
# raises an error: 'bad' must contain an integer, for instance '42' or '-21'.
-- ruby
### #string
-- description
Technically not a loader, but an alias for the standard accessors (which always
return strings), you can use it if you prefer a 100% type-oriented value access
terminology in your code. Note that this is not even a noop function, but really
just a direct alias, so using it instead of e.g. `field` does not incur any
performance drawback.
-- description
syntax:
- string → string
- string_items → array
- string_list → array
-- eno
name: Alice
number: 13
-- eno
-- ruby
document = Eno.parse(input)
document.string('name') #=> 'Alice'
document.string('number') #=> '13'
# .... these are completely identical to ...
document.field('name') #=> 'Alice'
document.field('number') #=> '13'
-- ruby
### #url
-- description
Validates a URL and returns it unaltered (or raises an error for an invalid URL).
-- description
syntax:
- url → string
- url_items → array
- url_list → array
-- eno
good: https://json.org
bad: www.boom
-- eno
-- ruby
document = Eno.parse(input)
document.url('good') #=> 'https://json.org'
document.url('bad')
# raises an error: 'bad' must contain valid URL, for instance 'https://eno-lang.org'.
-- ruby
## Eno::Section
-- class description
Every section in an eno document, such as for instance `# notes` or `### Appendix`, maps to an `Eno::Section`.
More prominently though **the document itself is represented as an `Eno::Section`**,
consequently this is the interface you'll be utilizing most for many usecases.
-- class description
### #assert_all_touched
syntax:
- assert_all_touched
- assert_all_touched(options)
- assert_all_touched(message, options)
- assert_all_touched(message_proc, options)
- assert_all_touched(options) { |name, value| message_block }
-- description
Assert that all elements of this section (and also, recursively, of all
subsections) that were present in the parsed eno document were also queried (and
therefore *touched*) by the application. This, combined with eno's query
methods, serves to ensure a two-way guarantee for both users and developers: No
data that the application requires can be left out, and no data that the
application does not process can be supplied.
-- description
-- eno
Important data A: I need to be processed!
Important data B: Me too!
-- eno
-- ruby
document = Eno.parse(input)
data_a = document.field('Important data A')
# ... processing happens only for data_a
document.assert_all_touched # raises an error with the default message
document.assert_all_touched(only: ['Important data A']) # passes
document.assert_all_touched(except: ['Important data B']) # passes
document.assert_all_touched do |name, value| # raises an error "Important data B is not ..."
"#{name} is not supported by the application, please contact support if you think it should be"
end
-- ruby
#### parameters
-- message, message_proc or message_block
Optional, usually the default message (*An excess element named [NAME] was
found, is it possibly a typo?*) will do fine. If you want to override it,
provide either a static message as a string, or alternatively a `Proc` or block
returning a string. (The arguments passed are `name` and `value`, although
`value` can be `nil` if the untouched element is a fieldset or section)
-- message, message_proc or message_block
options:
only = An array of strings, e.g. `['name', 'email']`, which specifies to only check these elements for whether they've been touched.
except = An array of strings, e.g. `['phone number']`, which specifies to exclude these elements from checking whether they've been touched.
### #fieldsets
syntax: fieldsets(name) → array
-- description
Retrieve fieldsets with the specified name from this current section.
-- description
-- eno
image:
src = red-roses.jpg
image:
src = white-roses.jpg
-- eno
-- ruby
document = Eno.parse(input)
images = document.fieldsets('image')
images.each do |image|
puts image.entry('src')
end
-- ruby
#### parameters
name: A string specifying the name of the fieldsets to retrieve.
#### return value
description: An array of `Eno::Fieldset`s.
### #fieldset
syntax:
- fieldset(name) → Eno::Fieldset or nil
- fieldset(name, options) → Eno::Fieldset or nil
-- description
Retrieve a fieldset from the section, optionally supplying an options.
-- description
-- eno
color ratings:
red = 5
green = 7
blue = 3
-- eno
-- ruby
section.fieldset('color ratings') #=> #<Eno::Fieldset name="color ratings" entries=3>
section.fieldset('temperature ratings') # raises an error
section.fieldset('temperature ratings', required: false) #=> nil
section.fieldset('temperature ratings', enforce_element: true) # raises an error
-- ruby
#### parameters
name: A string representing the name of the field to return.
options:
enforce_element = A boolean stating whether the fieldset must exist in the document. (defaults to `false`)
required = Alias for `enforce_element` (this exists on many methods and depending on context refers to either element or value)
#### return value
description: An `Eno::Fieldset`, or `nil`.
### #element
syntax:
- element(name) → element or nil
- element(name, options) → element or nil
-- description
Retrieve a single element of any allowed type from the section by its name.
Optionally specify whether it's optional or mandatory for the element to be
present in the eno document (by default it's optional).
-- description
-- eno
title: Artfest 2018
tags:
- art
- code
# content
> ...
-- eno
-- ruby
document = Eno.parse(input)
document.element('title') #=> #<Eno::Field name="title" value="Artfest 2018">
document.element('tags') #=> #<Eno::List name="tags" items=2>
document.element('content') #=> #<Eno::Section name="content" items=14>
document.element('fantasy') #=> nil
document.element('fantasy', enforce_element: true) #=> raises an error
document.element('fantasy', required: true) #=> raises an error
-- ruby
#### parameters
name: The name of the element to fetch from the section as a string.
options:
enforce_element = Whether the element must be present in the document (`true` or `false`, defaults to `false`)
required = Alias for `enforce_element` (this exists on many methods and depending on context refers to either element or value)
#### return value
description: An element (e.g. `Eno::List`, `Eno::Field`, etc.) or `nil`.
### #elements
syntax: elements → array
-- description
Retrieve the elements of this section in sequential order. This seamlessly lets you switch from
associative readout to sequential readout, allowing sections within, or the whole document, to
represent document-like structures (think generic markup) instead of just associative data stores.
-- description
-- eno
date: 2018-03-01
title: My blog post
# page
h1: Hello world
p: This is my first post, I'm so happy!
-- eno
-- ruby
document = Eno.parse(input)
metadata = {
date: document.field('date', my_date_loader_proc),
title: document.field('title')
}
html = ''
document.section('page').elements.each do |element|
html += "<#{element.name}>#{element.value}</#{element.name}>"
end
File.write('index.html', html)
-- ruby
#### return value
description: An array containing the elements of this section.
### #enforce_all_elements
syntax:
- enforce_all_elements
- enforce_all_elements(enforce)
-- description
Set the default for all following queries on this section of whether the
presence of elements in the eno input text should be enforced (by default it is
not). This can be used to prevent "template decay" - with presence enforcement
enabled elements may be empty, but they (at least their declaration) must be
there in the eno text and consequently they can not disappear from a template
over time without triggering an error.
-- description
-- eno
color: blue
-- eno
-- ruby
document = Eno.parse(input)
document.field('sound') #=> nil
document.enforce_all_elements
document.field('sound') # raises an error
document.field('sound', enforce_element: false) #=> nil
-- ruby
#### parameters
enforce: An optional boolean indicating whether to enforce or not. (otherwise `true` is assumed)
### #field
syntax:
- field(name) → value or nil
- field(name, options) → object/value or nil
- field(name, loader_proc, options) → value or nil
- field(name, options) { |name, value| loader_block } → object/value or nil
-- description
Retrieve a field's value from the section, optionally supplying a loader to
validate and/or transform the value, and/or an options object.
-- description
-- eno
color: blue
sound:
-- eno
-- ruby
section.field('color') #=> 'blue'
section.field('sound') #=> nil
section.field('sound', required: true) # raises an error
section.field('sound', enforce_value: true) # raises an error
section.field('sound', enforce_element: true) #=> nil
section.field('texture', enforce_element: true) # raises an error
section.field('color') { |name, value| value.upcase } #=> 'BLUE'
section.field('color') do |name, value|
raise 'Only green is allowed!' if value != 'green' # raises an error
value
end
section.field('color', with_element: true)
#=> [ #<Eno::Field name="color" value="blue">, 'blue' ]
-- ruby
#### parameters
name: A string representing the name of the field to return.
loader_block or loader_proc: A block or `Proc` returning the transformed/validated value or raising an error.
options:
enforce_element = Whether the field must be present in the document (`true` or `false`, defaults to `false`)
enforce_value = Whether there must be a value or the field is allowed to be empty (defaults to `false`)
required = Alias for `enforce_value` (this exists on many methods and depending on context refers to either element or value)
with_element = Whether to return an array of the form `[element, value]` instead of just the value (default `false`)
#### return value
description:
| The value of the field, or `nil` if empty, or an array of the form `[element, value]`
| when the option `with_element` is specified.
### #fields
syntax: fields(name) → array
-- description
Retrieve fields with the specified name from this current section.
-- description
-- eno
color: blue
color: red
color: orange
-- eno
-- ruby
document = Eno.parse(input)
colors = document.fields('color')
colors.each do |color|
puts color.value
end
-- ruby
#### parameters
name: A string specifying the name of the fields to retrieve.
#### return value
description: An array of `Eno::Field`s.
### #list
syntax:
- list(name) → array
- list(name, options) → array
- list(name, loader_proc, options) → array
- list(name, options) { |name, value| loader_block } → array
-- description
Retrieve the items of a list, optionally supplying a loader block or `Proc` and options.
-- description
-- eno
colors:
- pink
- peach
textures:
-
-- eno
-- ruby
document = Eno.parse(input)
document.list('colors') #=> [ 'pink', 'peach' ]
document.list('sounds') #=> []
document.list('textures') # raises an error
document.list('textures', enforce_values: false) #=> [ nil ]
document.list('sounds', required: true) # raises an error
document.list('sounds', enforce_element: true) # raises an error
document.list('colors') { |name, value| value.upcase } #=> [ 'PINK', 'PEACH' ]
document.list('colors') do |name, value|
raise 'Peach may not be on the list!' if value == 'peach' # raises an error
return "I like #{value}"
end
document.list('colors', with_elements: true)
#=> [[ #<Eno::Field value="pink">, 'pink' ],
# [ #<Eno::Field value="peach">, 'peach' ]]
document.list('colors', min_count: 3) # raises an error
-- ruby
#### parameters
name: The name of the list to return as a string.
loader_block or loader_proc:
| A block or `Proc` returning the transformed/validated value or raising an
| error. (The block or `Proc` is applied to each list item on its own, its
| argument signature is dynamic, you can either use `|value|` or `|name, value|`)
options:
enforce_element = Whether the list must be present in the document (defaults to `false`)
enforce_values = Whether empty list items (`- ` in the document, mapping to `nil`) are disallowed (defaults to `true`)
exact_count = Validate that there are exactly `n` items in the list (takes a number, defaults to `nil`)
min_count = Validate that there are at least `n` items in the list (takes a number, defaults to `nil`)
max_count = Validate that there are at most `n` items in the list (takes a number, defaults to `nil`)
required = Alias for `enforce_element` (this exists on many methods and depending on context refers to either element or value)
with_elements = Whether to return an array of arrays of the form `[element, value]` instead of just the values (defaults to `false`)
#### return value
description:
| The (optionally transformed/validated) values of the items of this list as an array.
| With `with_elements` set to `true` it returns an array of arrays of the form `[element, value]` instead.
### #lists
syntax: lists(name) → array
-- description
Retrieve lists with the specified name from this current section.
-- description
-- eno
route:
- vienna
- paris
- rome
route:
- moscow
- riga
-- eno
-- ruby
document = Eno.parse(input)
routes = document.lists('route')
routes.each do |route|
puts route.items
end
# prints ...
# [ 'vienna', 'paris', 'rome']
# [ 'moscow', 'riga' ]
-- ruby
#### parameters
name: A string specifying the name of the lists to retrieve.
#### return value
description: An array of `Eno::List`s.
### #lookup
syntax:
- lookup(index) → Hash or nil
- lookup(line, column) → Hash or nil
-- description
Ask the document *Hey what's at column X in line Y in my eno file?*. The lookup **always** returns an element for valid indices
(the document/section in case of an empty line/space), only indices outside the range of the document return `nil`. Note that
all arguments are zero-indexed, i.e. the first lines and columns are numbered `0`, not `1`.
-- description
-- eno
color: blue
# notes
-- eno
-- ruby
document = Eno.parse(input)
document.lookup(3) # 'o'
#=> { element: #<Eno::Field name="color" value="blue">, zone: :name }
document.lookup(7) # 'b'
#=> { element: #<Eno::Field name="color" value="blue">, zone: :value }
document.lookup(0, 3) # 'o'
#=> { element: #<Eno::Field name="color" value="blue">, zone: :name }
document.lookup(0, 7) # 'b'
#=> { element: #<Eno::Field name="color" value="blue">, zone: :value }
document.lookup(13) # '#'
#=> { element: #<Eno::Section name="notes" elements=0>, zone: :section_operator }
document.lookup(19) # 's'
#=> { element: #<Eno::Section name="notes" elements=0>, zone: :name }
document.lookup(2, 0) # '#'
#=> { element: #<Eno::Section name="notes" elements=0>, zone: :section_operator }
document.lookup(2, 6) # 's'
#=> { element: #<Eno::Section name="notes" elements=0>, zone: :name }
-- ruby
#### parameters
index: A one-dimensional index to look up (0-indexed, i.e. the first position is 0, not 1)- only applies when it's the only given argument.
line: The line to look up (0-indexed, i.e. the first line is 0, not 1) - only applies when you supply a `column` as well.
column: The column to look up (0-indexed, i.e. the first column is 0, not 1) - only applies when you supply a `line` as well.
#### return value
-- description
For all valid indices within the document returns a Hash with two keys:
`element`: An `Eno::Section`, `Eno::Fieldset`, `Eno::Field`.. . For empty lines you get the containing section.
`zone`: A symbol denoting the token at the specified position, here's the full list:
`'[empty-lines-and-spaces-between-tokens]'` => `:element`
`'[inside-blocks]'` => `:content`
`'[inside-comments]'` => `:comment`
`'[name]'` => `:name`
`'[template]'` => `:value`
`'[value]'` => `:value`
`':'` => `:name_operator`
`'-'` => `:item_operator`
`'='` => `:entry_operator`
`'--'` => `:block_operator`
`'>'` => `:comment_operator`
``'`'`` => `:escape_begin_operator`
``'`'`` => `:escape_end_operator`
`'|'` => `:newline_continuation_operator`
`'\'` => `:line_continuation_operator`
`'#'` => `:section_operator`
`'<'` => `:copy_operator`
`'<<'` => `:deep_copy_operator`
When an index outside the document is supplied, `nil` is returned.
-- description
### #raw
syntax: raw → array
-- description
Retrieve a ruby native representation of the section.
-- description
-- eno
color: blue
-- eno
-- ruby
document = Eno.parse(input)
document.raw #=> [{ 'color' => 'blue' }]
-- ruby
#### return value
description: An array of elements (also returned in their raw representation).
### #section
syntax:
- section(name) → Eno::Section or nil
- section(name, options) → Eno::Section or nil
-- description
Retrieve the subsection with the specified name from this current section.
-- description
-- eno
# content
title: A Manifest
# notes
-- eno
-- ruby
document = Eno.parse(input)
document.section('content') #=> #<Eno::Section name="content" elements=1>
document.section('notes') #=> #<Eno::Section name="notes" elements=0>
document.section('metadata') # raises an error
document.section('metadata', required: false) #=> nil
document.section('notes', enforce_element: true) #=> #<Eno::Section name="notes" elements=0>
document.section('metadata', enforce_element: true) # raises an error
-- ruby
#### parameters
name: A string specifying the name of the section to retrieve.
options:
enforce_element = Whether the section must be present in the document (defaults to `false`)
required = Alias for `enforce_element` (this exists on many methods and depending on context refers to either element or value)
#### return value
description: An `Eno::Section`, or `nil`.
### #sections
syntax: sections(name) → array
-- description
Retrieve subsections with the specified name from this current section.
-- description
-- eno
# Article
> ...
# Article
> ...
-- eno
-- ruby
document = Eno.parse(input)
sections = document.sections('Article')
sections.each do |section|
# do something with each Article
end
-- ruby
#### parameters
name: A string specifying the name of the sections to retrieve.
#### return value
description: An array of `Eno::Section`s.
## Eno::Field
-- class description
All values in an eno document, such as the value of a field like `name: value`,
the value of a block, the value of a list item, the value of a fieldset
entry, and so on, map to an `Eno::Field`. Usually you don't interact with this
because you directly get the values from a section or fieldset in most cases.
When you sequentially iterate a section (with `Eno::Section`'s
'`#elements` method) you will get to interact with this class.
-- class description
### #empty?
syntax: empty? → boolean
-- description
Query whether the value is empty (e.g. `comment: ` in an eno document), which in ruby terms is equivalent of `nil`.
-- description
#### return value
description: `true` if empty, otherwise `false`.
### #error
syntax:
- error → Eno::ValidationError
- error(message) → Eno::ValidationError
- error(message_proc) → Eno::ValidationError
- error { |name, value| message_block } → Eno::ValidationError
-- description
Generate an error in the context of the element. The error includes a generic
message by default, or a custom one if you supply it (which is the recommended
practice). You can also pass a message `Proc` or block which (like loaders
too) gets the name and value as arguments and returns a message string. This
serves to create highly informative error messages that pin-point to the exact
origin of the error, even when the initial reading of data is already past, e.g.
when the error condition is only apparent later, when more processing has
occurred or other data is available.
-- description
-- eno
color: cozy
-- eno
-- ruby
document = Eno.parse(input)
element, value = document.field('color', with_element: true)
# ...
if value == 'cozy'
raise element.error('Postprocessing determined that "cozy" is not a color after all.')
end
-- ruby
#### parameters
message, message_block or message_proc:
| Highly recommended to provide one (but it's optional).
|
| Either directly pass a string, or alternatively a `Proc` or block returning a string.
| (The `Proc` or block is passed `name` and `value` as arguments, and if both a `Proc` and block
| are supplied the block has higher precedence.)
#### return value
description:
| An `Eno::ValidationError` in the context of the element's value
| (and with an optional custom message).
### #raw
syntax: raw → object or value
-- description
Retrieve a native object representation of the value.
The representation differs depending on whether there is a name (e.g. for a field value),
or not (applies only to list item values).
-- description
-- eno
color: blue
numbers:
- 13
- 42
-- eno
-- ruby
document = Eno.parse(input)
document.element('color').raw #=> { 'color' => 'blue' }
list_items = document.element('numbers').elements
list_items.first.raw #=> '13'
-- ruby
#### return value
description: A native representation of the value element.
### #value
syntax:
- value → value or nil
- value(options) → value or nil
- value(loader_proc, options) → value or nil
- value(options) { |name, value| loader } → value or nil
-- description
Retrieve the value of an `Eno::Field`, optionally passing it through a loader
block or `Proc` and/or supplying options.
-- description
-- eno
flag color: beige
towel color:
|
-- eno
-- ruby
document = Eno.parse(input)
flag_color = document.element('flag color')
towel_color = document.element('towel color')
flag_color.value # => 'beige'
towel_color.value # => nil
flag_color.value(Proc.new { |name, value| value.gsub('eig', 'lu') }) #=> 'blue'
flag_color.value do |name, value|
value.gsub('eig', 'lu')
end #=> 'blue'
-- ruby
#### parameters
loader_block or loader_proc:
| A block or proc returning the transformed/validated value or raising an error.
| The block or proc's argument signature is dynamic, you can either use `|value|`
| or `|name, value|` depending on your needs.
options:
enforce_value = Whether there must be a value or the field is allowed to be empty (default to `false`)
required = Alias for `enforce_value` (this exists on many methods and depending on context refers to either element or value)
#### return value
description: The (optionally transformed/validated) value of this `Eno::Field`.
## Eno::List
-- class description
Lists such as the one below are represented as an `Eno::List`:
```eno
things:
- toys
- plants
```
Like `Eno::Field`, you will seldom interact with this class, and instead use the `list`
method on the document or its sections to directly obtain the values of a list.
-- class description
### #elements
syntax: elements → array
-- description
Retrieve the items of this list as `Eno::Field` elements (instead of
their direct values) in sequential order.
-- description
-- eno
numbers:
- 6
- 8
- 19
-- eno
-- ruby
list = Eno.parse(input).element('numbers')
list.elements
#=> [ #<Eno::Field value="6">, #<Eno::Field value="8">, #<Eno::Field value="19"> ]
-- ruby
#### return value
description: An array containing the items of this list as `Eno::Field` elements.
### #items
syntax:
- items → array
- items(options) → array
- items(loader_proc, options) → array
- items(options) { |name, value| loader_block } → array
-- description
Retrieve the items of the list, optionally passing them through a loader block or Proc.
Note that when you provide both a block and a Proc the block takes precedence.
-- description
-- eno
colors:
- pink
- peach
-- eno
-- ruby
document = Eno.parse(input)
list = document.element('colors')
list.items() #=> ['pink', 'peach']
list.items(Proc.new { |name, value| "#{value}!!" }) #=> ['pink!!', 'peach!!']
list.items { |name, value| { "#{value}!!" } #=> ['pink!!', 'peach!!']
-- ruby
#### parameters
loader_block or loader_proc:
| A `Proc` or block returning the transformed/validated value or raising an error.
| (The `Proc` or block is applied to each list item on its own, with a dynamic argument
| signature that allows to use `|value|` or `|name, value|` based on your needs.
options:
elements = Whether to return the elements (as `Eno::Field`) instead of the values of the list items. (defaults to `false`)
enforce_values = Whether empty list items (`- ` in the document, mapping to `nil`) are disallowed (defaults to `true`)
with_elements = Whether to return an array of arrays of the form `[element, value]` instead of just the values (defaults to `false`)
#### return value
description: The (optionally transformed/validated) items of this list as an array.
### #length
syntax: length → number
-- description
Returns the count of items in the list.
-- description
-- eno
colors:
- pink
- peach
-- eno
-- ruby
list = Eno.parse(input).element('colors')
list.length #=> 2
-- ruby
#### return value
description: The number of items in the list as a number.
### #raw
syntax: raw → object
-- description
Retrieve a native representation of the list.
-- description
-- eno
colors:
- pink
- peach
-- eno
-- ruby
list = Eno.parse(input).element('colors')
list.first.raw #=> { 'colors' => ['pink', 'peach'] }
-- ruby
#### return value
description: A native representation of the list.
## Eno::Fieldset
-- class description
Fieldsets are represented as an `Eno::Fieldset`:
```eno
rated things:
toys = 5 stars
plants = 3 stars
```
You will mostly obtain an instance of this through the `#fieldset` method on a document/section.
-- class description
### #assert_all_touched
syntax:
- assert_all_touched
- assert_all_touched(options)
- assert_all_touched(message, options)
- assert_all_touched(message_proc, options)
- assert_all_touched(options) { |name, value| message_block }
-- description
Assert that all entries of this fieldset that were present in the parsed eno document
were also queried (and therefore *touched*) by the application. This, combined with eno's
query methods, serves to ensure a two-way guarantee for both users and developers:
No data that the application requires can be left out, and no data that the application
does not process can be supplied.
-- description
-- eno
Important data:
A = I need to be processed!
B = Me too!
-- eno
-- ruby
document = Eno.parse(input)
fieldset = document.fieldset('Important data')
data_a = fieldset.entry('A')
# ... processing happens only for data_a
fieldset.assert_all_touched # raises an error
fieldset.assert_all_touched(only: ['A']) # passes
fieldset.assert_all_touched(except: ['B']) # passes
fieldset.assert_all_touched do |name, value| # raises an error "B is not ..."
"#{name} is not supported by the application, please contact support if you think it should be"
end
-- ruby
#### parameters
-- message, message_proc or message_block
Optional, usually the default message (*An excess element named [NAME] was
found, is it possibly a typo?*) will do fine. If you want to override it,
provide either a static message as a string, or alternatively a block or 'Proc'
returning a string. (`name` and `value` are provided as arguments)
-- message, message_proc or message_block
options:
only = An array of strings, e.g. `['name', 'email']`, which specifies to only check these elements for whether they've been touched.
except = An array of strings, e.g. `['phone number']`, which specifies to exclude these elements from checking whether they've been touched.
### #element
syntax:
- element(name) → element or nil
- element(name, options) → element or nil
-- description
Retrieve a single entry as `Eno::Field` (instead of
its direct value) from the fieldset by its name.
Optionally specify whether it's optional or mandatory for the entry to be
present in the eno document (by default it's optional).
-- description
-- eno
years:
2007 = Year of the gooseberry
2010 = Year of the awesome beanstalk
2011 = Year of yearning
-- eno
-- ruby
fieldset = Eno.parse(input).fieldset('years')
fieldset.element('2010') #=> #<Eno::Field name="2010" value="Year of the awesome beanstalk">
fieldset.element('2020') #=> nil
fieldset.element('2020', enforce_element: true) # raises an error
fieldset.element('2020', required: true) # raises an error
-- ruby
#### parameters
name: The name of the entry to fetch from the fieldset as a string.
options:
enforce_element = Whether the element must be present in the document (`true` or `false`, defaults to `false`)
required = Alias for `enforce_element` (this exists on many methods and depending on context refers to either element or value)
#### return value
description: An element (`Eno::Field`) or `nil`.
### #elements
syntax: elements → array
-- description
Retrieve the entries of this fieldset as `Eno::Field` elements (instead of
their direct values) in sequential order. This seamlessly lets you switch from
associative readout to sequential readout, allowing fieldsets to also represent
document-like structures instead of just associative data.
-- description
-- eno
dialogue:
alice = hi
bob = hey
alice = sup?
bob = nothin'
-- eno
-- ruby
document = Eno.parse(input)
dialogue = document.fieldset('dialogue')
html = '<h1>Observe this fascinating dialogue, presented in html!</h1>'
dialogue.elements.each do |element|
html += "<strong>#{element.name}</strong>: \"#{element.value}\""
end
File.write('index.html', html)
-- ruby
#### return value
description: An array containing the elements of this fieldset.
### #enforce_all_elements
syntax:
- enforce_all_elements
- enforce_all_elements(enforce)
-- description
Set the default for all following queries on this fieldset of whether the
presence of elements in the eno input text should be enforced (by default it is
not). This can be used to prevent "template decay" - with presence enforcement
enabled entries may be empty, but they (at least their declaration) must be
there in the eno document and consequently they can not disappear from a
template at any point without triggering an error.
-- description
-- eno
conversions:
0001 = 1
0010 = 2
-- eno
-- ruby
document = Eno.parse(input)
conversions = document.fieldset('conversions')
conversions.enforce_all_elements
conversions.entry('0011') # raises an error
conversions.entry('0011', enforce_element: false) #=> nil
-- ruby
### #entry
syntax:
- entry(name) → value or nil
- entry(name, loader) → value or nil
- entry(name, enforce_element: false, required: false, with_element: false) → object/value or nil
-- description
Retrieve an entry's value from the fieldset, optionally supplying a loader to
validate and/or transform the value, and/or an options object.
-- description
-- eno
Q&A:
Meaning = 42
Green = Yes
Purpose =
-- eno
-- ruby
document = Eno.parse(input)
qa = document.fieldset('Q&A')
qa.entry('Meaning') #=> '42'
qa.entry('Purpose') #=> nil
qa.entry('Purpose', required: true) # raises an error
qa.entry('Purpose', enforce_element: true) #=> nil
qa.entry('Beige', enforce_element: true) # raises an error
qa.entry('Green', Proc.new { |name, value| value.upcase }) #=> 'YES'
qa.entry('Meaning') do |name, value| # raises an error
raise "That one's getting old!" if value == '42'
value
end
qa.entry('Meaning', with_element: true) #=> [ #<Eno::Field name="Meaning" value="42"> , '42' ]
-- ruby
#### parameters
name: The name of the entry as a string.
loader:
| A `Proc` returning the transformed/validated value or raising an error.
| The `Proc` is passed `name` and `value` as arguments.
options:
enforce_element = Whether the entry must be present in the document (`true` or `false`, defaults to `false`)
enforce_value = Whether there must be a value or the entry is allowed to be empty (default to `false`)
required = Alias for `enforce_value` (this exists on many methods and depending on context refers to either element or value)
with_element = Whether to return an array with both the element and the value (defaults to `false`)
#### return value
description: The entry's value, or `nil` if empty.
### #raw
syntax: raw → object
-- description
Retrieve a native representation of the fieldset.
-- description
-- eno
weights:
apple = 100g
pineapple = 800g
-- eno
-- ruby
document = Eno.parse(input)
document.fieldset('weights').raw
#=> { 'weights' => [{ 'apple' => '100g' }, { 'pineapple' => '800g' }] }
-- ruby
#### return value
description: A native object representation of the fieldset.
## Eno::Empty
-- class description
This represents empty elements such as `color:`, where it is not clear if it is an empty field, list, or fieldset.
-- class description
### #error
syntax:
- error → Eno::ValidationError
- error(message) → Eno::ValidationError
- error(message_proc) → Eno::ValidationError
- error { |name, value| message_block } → Eno::ValidationError
-- description
Generate an error in the context of the element. The error includes a generic
message by default, or a custom one if you supply it (which is the recommended
practice). You can also pass a message block or Proc which (like loaders too)
gets the name and value as arguments and returns a message string. This serves
to create highly informative error messages that pin-point to the exact origin
of the error, even when the initial reading of data is already past, e.g. when
the error condition is only apparent later, when more processing has occurred or
other data is available.
-- description
-- eno
color:
-- eno
-- ruby
document = Eno.parse(input)
element, value = document.field('color', with_element: true)
# ...
if value.nil?
raise element.error('Postprocessing determined that this value needs to be provided.')
end
-- ruby
#### parameters
message, message_block or message_proc:
| Highly recommended to provide one (but it's optional).
|
| Either directly pass a string, or alternatively a block or `Proc` returning a string.
| (The block or `Proc` is passed `name` and `value` as arguments)
#### return value
description:
| An `Eno::ValidationError` in the context of the element's value
| (and with an optional custom message).
### #raw
syntax: raw → object
-- description
Retrieve a native representation of the empty element.
-- description
-- eno
color:
-- eno
-- ruby
empty = Eno.parse(input).element('color')
empty.raw #=> { 'color' => nil }
-- ruby
#### return value
description: A native representation of the empty element.
### #value
syntax: value → nil
-- description
Retrieve the value (always returns `nil`).
-- description
-- eno
note:
-- eno
-- ruby
empty = Eno.parse(input).element('note')
note.value #=> nil
-- ruby
#### return value
description: Always `nil`.
## Eno::Error
-- class description
The single generic error interface for all (user) errors that eno generates.
Note that this is never raised by itself, but only in one of it's subclassed
variants (`Eno::ParseError` and `Eno::ValidationError`). However, you can still
utilize this generic class in cases where you want to catch both parser and
validation errors indiscriminately, like so:
```ruby
begin
# ...
rescue Eno::Error => e # catches both Eno::ParseError and Eno::ValidationError
# ...
end
```
-- class description
### #cursor
syntax: cursor → [line, column]
-- description
Returns a cursor position as an array of the form [line, column], indicating
where a cursor should be placed if an application wants to offer the user a way
to move the cursor straight to the error location.
-- description
-- ruby
begin
# ...
rescue Eno::Error => e
e.cursor #=> [3, 14]
end
-- ruby
#### return value
description: An array, where [0] is the line number, and [1] is the column number.
### #selection
syntax: selection → [[line, column], [line, column]]
-- description
Returns a selection as an array of the form [[line, column], [line, column]],
indicating a text range to select if an application wants to offer the user a
way to get a selection for the error in the input.
-- description
-- ruby
begin
# ...
rescue Eno::Error => e
e.selection #=> [[3, 14], [3, 23]]
end
-- ruby
#### return value
description:
| An array, where [0] is the begin of the selection and [1] is the end,
| and both begin and end are each again an array, where [0] is the
| line number, and [1] is the column number.
### #message
syntax: message → string
-- description
Contains both the error text as well as the snippet. This is also what you
get in the console when you don't catch the error.
-- description
-- ruby
begin
# ...
rescue Eno::Error => e
e.message #=> "In line 4 'a' is copied into itself.\n\n Line | Content\n ..."
end
-- ruby
#### return value
description: Both the error text as well as the snippet.
### #snippet
syntax: snippet → string
-- description
Returns a formatted excerpt of those passage(s) from the input where the error occurred.
-- description
-- ruby
begin
# ...
rescue Eno::Error => e
e.snippet
# returns something like ...
#
# Line | Content
# 1 |
# * 2 | # a
# * 3 | ## b
# > 4 | ### c < a
# 5 |
end
-- ruby
#### return value
description: A formatted excerpt of those passage(s) from the input where the error occurred.
### #text
syntax: text → string
-- description
Returns a one-liner that describes the error in human language.
-- description
-- ruby
begin
# ...
rescue Eno::Error => e
e.message #=> "In line 4 'a' is copied into itself."
end
-- ruby
#### return value
description: A single sentence that describes the error in human language.
## Eno::ParseError
-- class description
When this is raised, it indicates an error regarding syntax or grammatical semantics of the document.
Functionally this behaves exactly like `Eno::Error`, therefore the interface methods are not repeated here and can
be looked up on the `Eno::Error` documentation.
-- class description
## Eno::ValidationError
-- class description
When this is raised, it indicates an error regarding application-specific requirements for the document.
Functionally this behaves exactly like `Eno::Error`, therefore the interface methods are not repeated here and can
be looked up on the `Eno::Error` documentation.
-- class description