Skip to content

robbyriverside/brief

Repository files navigation

Brief Specification Format

Version 1.1.0

In short, the brief format is XML with minimal syntax and indented blocks like Python.

This repo contains a decoder for the brief format written in Go.

Brief Example

A sample html page written in brief.

html
    head
        title `My Web Page`
    body class:mybody
        h1 `My Web Page`

        div id:main class:myblock
            p id:X `the quick brown fox
jumped over the moon and ran into a cow`

Here is the equivalent in HTML.

<html>
<head>
    <title>My Web Page</title>
</head>
<body class="mybody">
    <h1>My Web Page</h1>

    <div id="main" class="myblock">
        <p id="X">the quick brown fox
jumped over the moon and ran into a cow</p>
    </div>
</body>
</html>

Further, documentation of the format below.

Why?

The unique feature of XML having both keyword parameters and nested content body is useful when writing a specification. However, writing XML by hand can be tedious because of the verbose syntax. The primary design goal of brief to have the same structure as XML but easy to write.

Brief is not intended to be a data interchange format. However, it can be easily converted to XML.

Brief is the primary input format for the Brevity Code Generator.

Brief Library

Brief Decoder

Parses the brief format and creates a slice of Node objects.

type Node struct {
    Type, Name string
    Keys       map[string]string
    Body       []*Node
    Parent     *Node
    Content    string
    Indent     int
}

This is more efficient than using reflection to map to an arbitrary structure and the Node object has many helpful methods for writing templates.

var in io.Reader
rootNodes, err := brief.Decode(in)
rootNodes, err := brief.DecodeFile("spec.brief")

Multiple top-level forms are allowed and returned as an array of Nodes by the decoder.

Brief Encoder

Writes the Node object in brief format.

var node Node
var out io.Writer
err := node.Encode(out)

Brief XML Output

Writes the Node object in XML format.

var node Node
var out io.Writer
err := node.WriteXML(out)

XML output uses a template. This serves as an example of using brief with a template.

Contents of templates/xmlout.tmpl:

{{define "Node"}}
{{.IndentString}}<{{.Type}}{{if .Name}} name="{{.Name}}"{{end}}{{range $key, $val := .Keys}} {{$key}}="{{$val}}"{{end}}>
{{- if .Content}}{{.Content}}{{ if not .Body}}</{{.Type}}>{{end}}{{end}}
{{- if .Body}}{{.IndentString}}{{range .Body}}{{ template "Node" . }}{{end}}
{{.IndentString}}</{{.Type}}>{{end -}}
{{end}}
{{- template "Node" .}}

Template Methods

One of the primary targets of the Brief format is use in go text/templates. There are many helpful node methods to assist in template building.

Node Spec

A node spec is a node type or a type:name pair. This is used to identify a node when searching for it.

Here are some examples with an element foo with a name bar:

"foo:bar" match both type and name. "foo" matches only the type without considering name. "foo:" matches the type and requires the name to be empty.

Context

Find a Node in the elements that contain this Node.

Context will walk up the Parent hierarchy seeking a node with matches the node spec.

{{with .Context "project" }}
    print .Keys.id
{{end}}

Find

Find is a node method that searches the children for a node with a specific node spec.

{{ .Find "foo" }}        // search all the children for any node of type "foo"
{{ .Find "foo:bar" }}    // search all the children for any node of type "foo" whose name is "bar"

Child

Child is a node method that follows a path to a specific child node. The path is a series of node specs which must match each node as it walks down the children.

{{ .Child "foo" "zed" "x" }}  // return the "x" node child of "zed" node child of "foo" node child of the current node
{{ .Child "foo:bar" }}        // return a child of the current node which is of type "foo" and named "bar"

Value Spec

A value spec is a string that can be used to locate a key value or name in a context element.

A single name, refers to the Name of the context element. {context}.Name

A dotted pair refers to a key value from the context element. {context}.{key}

Lookup

Lookup is a Node method which gets a context value from a value spec.

{{ .Lookup "project.id" }}
{{ .Lookup "project" }}

Slice

Slice is a Node method which creates a slice of strings from a sequence of value specs.

{{ .Slice "project.id" "project" }}

Join

Join is a Node method which combines sequence of strings using a separator from a sequence of value specs.

{{ .Join "/" "project.id" "project" }}

Printf

Printf is a Node method which applies a sequence of strings using a format from a sequence of value specs.

{{ .Printf "%s:%s" "project.id" "project" }}

Brief Format

The first token on each line is the element type. After the element type, is a series of key-value pairs, optionally followed by a text body. Child elements are indented on the lines below the parent element.

Example

No better example of XML format than the widely known HTML dialect. HTML5 has some variations, but we will skip over them for our purposes.

An HTML page contains a single top-level structure and two sub-structures:

html
    head
    body

Sub-structures are indented to identify a sub-block. The first identifier on each line is an element name or type. The back-tic contains multiline text which forms the text contents of an element. The back-tic must be after any key-value pairs.

html
    head
        title `My Web Page`
    body class:mybody
        h1 `My Web Page`

        div id:main class:myblock
            p id:X `the quick brown fox
jumped over the moon and ran into a cow`

Here is the equivalent in HTML.

<html>
<head>
    <title>My Web Page</title>
</head>
<body class="mybody">
    <h1>My Web Page</h1>

    <div id="main" class="myblock">
        <p id="X">the quick brown fox
jumped over the moon and ran into a cow</p>
    </div>
</body>
</html>

Key Values

For key-value pairs with a value that is more than just a simple token double quotes are used.

elem key:"value of key"  <->   <elem key="value of key"/>

elem key:"my brother \"Bill\""  <->  <elem key="my brother \"Bill\"">

Simple tokens cannot contain brief syntactic characters: space, colon, back-tic, double-quote. This allows number formats to be simple tokens.

elem size:33 max:1.4e3

Name Key

Because specification elements often have a "name" keyword to identify them in the document, we give them a special place. The element type can be a keyword by adding a colon (:) to the end and so it can also have a value, which is the name.

body
    div:foo  <->  <div name="foo"/>

The purpose of this shorthand is to improve readability and standardize on elements having names. Names are important in a written specification which is the primary purpose of the brief format.

Multi-line and Content

Line cannot start with a back-tic. Body text requires an element. Content back-tic is last feature on a line. A line that starts with plus '+' is a continuation of the attributes on the line above. May be followed by a space.

Using simple back string (or rawstring):

elem:foo bar:zed
    + more:true range:"3 to 5" `
  more content here`

Or using special hash delimiter (#| |#, #@ @#, #$ $#, #% %#) for when you need a nested rawstring ``

elem:foo bar:zed
    + more:true range:"3 to 5" #|
  more content here
  |#

Include files

To keep files modular the #include directive allows other brief files to be inserted. The decoder handles indentation for you so each file can be indented naturally from zero. Include directives insert files so they can be treated like any other sub-element.

html
    head
        title `include other brief files`
        #include `standard_headers.brf`
        link rel:stylesheet href=mystyle.css
    body
        h1 `include other brief files`

Comments

In the brief format, comments are treated as whitespace.

// foo element
elem:foo bar:zed
   /* multiline
      comment */
    + more:true range:"3 to 5" `
  more content here`