brief

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 21, 2022 License: MIT Imports: 8 Imported by: 0

README

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`

Documentation

Index

Constants

View Source
const (
	// Whitespace no newlines
	Whitespace = 1<<' ' | 1<<'\t'
	// Newline no space
	Newline = 1<<'\n' | 1<<'\r'
	// TabCount default
	TabCount = 4
)
View Source
const (
	NoKey  = "noKey"
	NoVal  = "noVal"
	NoCTX  = "noCTX"
	NoName = "noName"
)

ValueSpec states

Variables

This section is empty.

Functions

func NoQuote added in v1.0.1

func NoQuote(value string) bool

NoQuote tests if the value is an identifier or number

func ParseValueSpec added in v1.1.0

func ParseValueSpec(spec string) (string, string, bool)

ParseValueSpec type.key or type.Name return type, value and hasKey

Types

type Decoder

type Decoder struct {
	Err            error
	Roots, Nesting []*Node
	Text           Scanner
	ScanType       rune
	Token          string
	State          DecoderState
	Key, Feature   string
	Padding        int
	Dir            string
	Debug          bool
}

Decoder for brief formated files

func NewDecoder

func NewDecoder(reader io.Reader, tabsize int, srcdir string) *Decoder

NewDecoder from reader with tabsize and optional directory srcdir is used with #include files relative to this reader

func NewFileDecoder added in v1.1.0

func NewFileDecoder(filename string) (*Decoder, error)

NewFileDecoder new decoder that reads from a filename

func (*Decoder) Decode

func (dec *Decoder) Decode() ([]*Node, error)

Decode creates a Node by parsing brief format from reader

func (*Decoder) Error

func (dec *Decoder) Error(msg string) error

Error added to decoder and returned

func (*Decoder) Errorf added in v1.1.0

func (dec *Decoder) Errorf(format string, args ...interface{}) error

Errorf added to decoder and returned

type DecoderState

type DecoderState int

DecoderState constants

const (
	Unknown    DecoderState = iota
	NewLine                 // LineStart
	KeyElem                 // Key set to elem
	KeyValue                // Key set to Key
	KeyEmpty                // ready for next key or content  key is empty
	OnName                  // Set Name
	OnValue                 // Put key-value
	OnFeature               // Exec Feature
	FeatureSet              // Feature value is set
	NegValue                // Minus sign instead of a value
	OnComment               // A comment
)

States of the decoder

type Node

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

Node in a brief hierarchy

func Decode

func Decode(reader io.Reader, srcdir string) ([]*Node, error)

Decode creates a Node by parsing brief format from reader

func DecodeFile

func DecodeFile(filename string) ([]*Node, error)

DecodeFile into brief Nodes

func NewNode

func NewNode(elemType string, indent int) *Node

NewNode create a new Node

func (*Node) Child

func (node *Node) Child(path ...string) *Node

Child follow a path to a specific node in the body path elements are node type or type:name pair

func (*Node) Collect added in v1.1.0

func (node *Node) Collect(names ...string) []string

Collect value specs up the hierarchy into a Slice

func (*Node) Compile

func (node *Node) Compile()

Compile adds name and content only body Nodes to the keys

func (*Node) ContentOnly

func (node *Node) ContentOnly() bool

ContentOnly true if Node only has content

func (*Node) Context

func (node *Node) Context(name string) *Node

Context is a surrounding element found by node spec name is either a type or a type:name pair

func (*Node) Encode

func (node *Node) Encode() []byte

Encode converts a node into brief format

func (*Node) Find

func (node *Node) Find(name string) *Node

Find searches for a node matching name in the body of this node The name is a node type or a type:name pair

func (*Node) FindAll added in v1.1.0

func (node *Node) FindAll(name string) []*Node

FindAll nodes that match name

func (*Node) HasContent

func (node *Node) HasContent() bool

HasContent true if Node has content

func (*Node) HasKeys

func (node *Node) HasKeys() bool

HasKeys true if Node has keys

func (*Node) HasName

func (node *Node) HasName() bool

HasName true if Node has a name

func (*Node) IndentString

func (node *Node) IndentString() string

IndentString return a blank string width of indent.

func (*Node) Join

func (node *Node) Join(sep string, specs ...string) string

Join calls Lookup on each spec and Joins them using sep

func (*Node) Key

func (node *Node) Key(name string) string

Key get key value from node or return {unknown key}

func (*Node) Lookup

func (node *Node) Lookup(spec string) string

Lookup a value from the above context elements spec can be a single name or dotted pair single name, returns the Name of the context a dotted pair returns a key value from the context {context}.{key}

func (*Node) NoBody

func (node *Node) NoBody() bool

NoBody true if the Node has no body

func (*Node) Printf

func (node *Node) Printf(format string, specs ...string) string

Printf calls Lookup on each spec and prints them using format

func (*Node) Put

func (node *Node) Put(key, value string)

Put the value of a key

func (*Node) Slice

func (node *Node) Slice(specs ...string) []string

Slice calls Lookup on each spec and returns the slice

func (*Node) String

func (node *Node) String() string

func (*Node) WriteXML

func (node *Node) WriteXML(out io.Writer) error

WriteXML for a Node to a writer

type Scanner

type Scanner struct {
	scanner.Scanner
	LineStart bool
	TabCount  int
	Indent    int
	// contains filtered or unexported fields
}

Scanner for brief language

func (*Scanner) Init

func (s *Scanner) Init(src io.Reader, tabsize int) *Scanner

Init contents of the brief token scanner

func (*Scanner) Scan

func (s *Scanner) Scan() rune

Scan next token from input

type Spec

type Spec struct {
	Type, Name string
	NoName     bool
}

Spec for node Type:Name

func NewSpec

func NewSpec(spec string) *Spec

NewSpec from spec of Type:Name or just Type

func (*Spec) Find

func (s *Spec) Find(node *Node) *Node

Find looks for a specific node in the body that matches spec

func (*Spec) FindAll added in v1.1.0

func (s *Spec) FindAll(node *Node) []*Node

FindAll nodes in body that match spec

func (*Spec) Match

func (s *Spec) Match(node *Node) bool

Match spec for node

func (*Spec) String

func (s *Spec) String() string

type ValueSpec added in v1.1.0

type ValueSpec struct {
	Elem, Name string
	HasKey     bool
}

ValueSpec <elem>.<key> or <elem>.Name

func NewValueSpec added in v1.1.0

func NewValueSpec(spec string) *ValueSpec

NewValueSpec spec for a value Name or Key-value <elem>.<key> or <elem>.Name

func (*ValueSpec) Lookup added in v1.1.0

func (val *ValueSpec) Lookup(node *Node) string

Lookup value spec in parents

func (*ValueSpec) Value added in v1.1.0

func (val *ValueSpec) Value(node *Node) (string, bool)

Value from node if matching elem and has key

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL