scl

package module
v0.0.0-...-7ac2349 Latest Latest
Warning

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

Go to latest
Published: Mar 14, 2017 License: MIT Imports: 11 Imported by: 4

README

Build Status Coverage Status GoDoc Language reference

Sepia Configuration Language

The Sepia Configuration Language is a simple, declarative, semi-functional, self-documenting language that extends HashiCorp's HCL in the same sort of way that Sass extends CSS. The syntax of SCL is concise, intuitive and flexible. Critically, it also validates much of your configuration by design, so it's harder to configure an application that seems like it should work — but doesn't.

SCL transpiles to HCL and, like CSS and Sass, any properly formatted HCL is valid SCL. If you have an existing HCL setup, you can transplant it to SCL directly and then start making use of the code organisation, mixins, and properly scoped variables that SCL offers.

In addition to the language itself, there is a useful command-line tool than can compile your .scl files and write the output to the terminal, run gold standard tests against you code, and even fetch libraries of code from public version control systems.

This readme is concerned with the technical implementation of the Go package and the CLI tool. For a full language specification complete with examples and diagrams, see the wiki.

Installation

Assuming you have Go installed, the package and CLI tool can be fetched in the usual way:

$ go get -u github.com/homemade/scl/...

Contributions

This is fairly new software that has been tested intensively over a fairly narrow range of functions. Minor bugs are expected! If you have any suggestions or feature requests please open an issue. Pull requests for bug fixes or uncontroversial improvements are appreciated.

We're currently working on standard libraries for Terraform and Hugo. If you build an SCL library for anything else, please let us know!

Using SCL in your application

SCL is built on top of HCL, and the fundamental procedure for using it is the more or less the same: SCL code is decoded into a Go struct, informed by hcl tags on the struct's fields. A trivially simple example is as follows:

myConfigObject := struct {
    SomeVariable int `hcl:"some_variable"`
}{}

if err := scl.DecodeFile(&myConfigObject, "/path/to/a/config/file.scl"); err != nil {
    // handle error
}

// myConfigObject is now populated!

There are many more options—like include paths, predefined variables and documentation generation—available in the API. If you have an existing HCL set up in your application, you can easily swap out your HCL loading function for an SCL loading function to try it out!

CLI tool

The tool, which is installed with the package, is named scl. With it, you can transpile .scl files to stdout, run gold standard tests that compare .scl files to .hcl files, and fetch external libraries from version control.

Usage

Run scl for a command syntax.

Examples

Basic example:

$ scl run $GOPATH/src/bitbucket.org/homemade/scl/fixtures/valid/basic.scl
/* .../bitbucket.org/homemade/scl/fixtures/valid/basic.scl */
wrapper {
  inner = "yes"
  another = "1" {
    yet_another = "123"
  }
}

Adding includes:

$ scl run -include $GOPATH/src/bitbucket.org/homemade/scl $GOPATH/src/bitbucket.org/homemade/scl/fixtures/valid/import.scl
/* .../bitbucket.org/homemade/scl/fixtures/valid/import.scl */
wrapper {
  inner = "yes"
  another = "1" {
    yet_another = "123"
  }
}
output = "this is from simpleMixin"

Adding params via cli flags:

$ scl run -param myVar=1 $GOPATH/src/bitbucket.org/homemade/scl/fixtures/valid/variables.scl
/* .../bitbucket.org/homemade/scl/fixtures/valid/variables.scl */
outer {
  inner = 1
}

Adding params via environmental variables:

$ myVar=1 scl run $GOPATH/src/bitbucket.org/homemade/scl/fixtures/valid/variables.scl
/* .../bitbucket.org/homemade/scl/fixtures/valid/variables.scl */
outer {
  inner = 1
}

Skipping environmental variable slurping:

$ myVar=1 scl run -no-env -param myVar=2 $GOPATH/src/bitbucket.org/homemade/scl/fixtures/valid/variables.scl
/* .../src/bitbucket.org/homemade/scl/fixtures/valid/variables.scl */
outer {
  inner = 2
}

Documentation

Overview

Package scl is an implementation of a parser for the Sepia Configuration Language.

SCL is a simple, declarative, self-documenting, semi-functional language that extends HCL (as in https://github.com/hashicorp/hcl) in the same way that Sass extends CSS. What that means is, any properly formatted HCL is valid SCL. If you really enjoy HCL, you can keep using it exclusively: under the hood, SCL ‘compiles’ to HCL. The difference is that now you can explicitly include files, use ‘mixins’ to quickly inject boilerplate code, and use properly scoped, natural variables. The language is designed to accompany Sepia (and, specifically, Sepia plugins) but it's a general purpose language, and can be used for pretty much any configurational purpose.

Full documenation for the language itself, including a language specification, tutorials and examples, is available at https://github.com/homemade/scl/wiki.

Example (Basic)
package main

import (
	"github.com/homemade/scl"
)

func main() {

	myConfigObject := struct {
		SomeVariable int `hcl:"some_variable"`
	}{}

	if err := scl.DecodeFile(&myConfigObject, "/path/to/a/config/file.scl"); err != nil {
		// handle error
	}

	// myConfigObject is now populated!
}
Output:

Example (Parser)
package main

import (
	"fmt"
	"log"

	"github.com/hashicorp/hcl"

	"github.com/homemade/scl"
)

func main() {

	parser, err := scl.NewParser(scl.NewDiskSystem())

	if err != nil {
		log.Fatal(err)
	}

	if err := parser.Parse("myfile.scl"); err != nil {
		log.Fatal(err)
	}

	myConfig := struct {
		SomeThing string `hcl:"some-thing"`
	}{}

	if err := hcl.Decode(&myConfig, parser.String()); err != nil {
		log.Fatal(err)
	}

	fmt.Println(myConfig)
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeFile

func DecodeFile(out interface{}, path string) error

DecodeFile reads the given input file and decodes it into the structure given by `out`.

Types

type FileSystem

type FileSystem interface {
	Glob(pattern string) ([]string, error)
	ReadCloser(path string) (content io.ReadCloser, lastModified time.Time, err error)
}

A FileSystem is a representation of entities with names and content that can be listed using stangard glob syntax and read by name. The typical implementation for this is a local disk filesystem, but it could be anything – records in a database, objects on AWS S3, the contents of a zip file, virtual files stored inside a binary, and so forth. A FileSystem is required to instantiate the standard Parser implementation.

func NewDiskSystem

func NewDiskSystem(basePath ...string) FileSystem

NewDiskSystem creates a filesystem that uses the local disk, at an optional base path. The default base path is the current working directory.

type MixinDoc

type MixinDoc struct {
	Name      string
	File      string
	Line      int
	Reference string
	Signature string
	Docs      string
	Children  MixinDocs
}

MixinDoc documents a mixin from a particular SCL file. Since mixins can be nested, it also includes a tree of all child mixins.

type MixinDocs

type MixinDocs []MixinDoc

MixinDocs is a slice of MixinDocs, for convenience.

type Parser

type Parser interface {
	Parse(fileName string) error
	Documentation(fileName string) (MixinDocs, error)
	SetParam(name, value string)
	AddIncludePath(name string)
	String() string
}

A Parser takes input in the form of filenames, variables values and include paths, and transforms any SCL into HCL. Generally, a program will only call Parse() for one file (the configuration file for that project) but it can be called on any number of files, each of which will add to the Parser's HCL output.

Variables and includes paths are global for all files parsed; that is, if you Parse() multiple files, each of them will have access to the same set of variables and use the same set of include paths. The parser variables are part of the top-level scope: if a file changes them while it's being parsed, the next file will have the same variable available with the changed value. Similarly, if a file declares a new variable or mixin on the root scope, then the next file will be able to access it. This can become confusing quickly, so it's usually best to parse only one file and let it explicitly include and other files at the SCL level.

SCL is an auto-documenting language, and the documentation is obtained using the Parser's Documentation() function. Only mixins are currently documented. Unlike the String() function, the documentation returned for Documentation() only includes the nominated file.

Example
package main

import (
	"fmt"
	"log"

	"github.com/homemade/scl"
)

func main() {

	parser, err := scl.NewParser(scl.NewDiskSystem())

	if err != nil {
		log.Fatal(err)
	}

	if err := parser.Parse("myfile.scl"); err != nil {
		// This is a language error, which will include
		// the filename and line of the error, as well
		// as an explanatory message.
		log.Fatal(err)
	}

	fmt.Println("myfile.scl as HCL:", parser.String())
}
Output:

Example (Documentation)
package main

import (
	"fmt"
	"log"

	"github.com/homemade/scl"
)

func main() {

	parser, err := scl.NewParser(scl.NewDiskSystem())

	if err != nil {
		log.Fatal(err)
	}

	documentation, err := parser.Documentation("myfile.scl")

	if err != nil {
		log.Fatal(err)
	}

	for i, mixin := range documentation {
		fmt.Printf("Mixin %d: %+v", i, mixin)
	}
}
Output:

Example (IncludePaths)
package main

import (
	"fmt"
	"log"

	"github.com/homemade/scl"
)

func main() {

	parser, err := scl.NewParser(scl.NewDiskSystem())

	if err != nil {
		log.Fatal(err)
	}

	parser.AddIncludePath("path/to/library")

	if err := parser.Parse("myfile.scl"); err != nil {
		log.Fatal(err)
	}

	fmt.Println("myfile.scl as HCL:", parser.String())
}
Output:

Example (Variables)
package main

import (
	"fmt"
	"log"

	"github.com/homemade/scl"
)

func main() {

	parser, err := scl.NewParser(scl.NewDiskSystem())

	if err != nil {
		log.Fatal(err)
	}

	parser.SetParam("my-variable", "my value")

	if err := parser.Parse("myfile.scl"); err != nil {
		log.Fatal(err)
	}

	fmt.Println("myfile.scl as HCL:", parser.String())
}
Output:

func NewParser

func NewParser(fs FileSystem) (Parser, error)

NewParser creates a new, standard Parser given a FileSystem. The most common FileSystem is the DiskFileSystem, but any will do. The parser opens all files and reads all includes using the FileSystem provided.

Directories

Path Synopsis
cmd
scl

Jump to

Keyboard shortcuts

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