BCL is a lightweight configuration language parser designed for ease-of-use and flexibility in configuration management.
Create a configuration file (e.g., config.bcl) with content like:
appName = "Boilerplate"
version = 1.2
@include "credentials.bcl"
server main {
host = "localhost"
port = 8080
secure = false
}
greeting = "Welcome to ${upper(appName)}"
calc = 10 + 5
negValue = -calc
IF (settings.debug) {
logLevel = "verbose"
} ELSE {
logLevel = "normal"
}
Then run an example provided in the repository:
go run examples/main.goThe tool will read the configuration, evaluate expressions, perform dynamic interpolation, and output the processed configuration.
Expected output includes the unmarshaled configuration and the AST.
- Dynamic Expression Evaluation: Supports inline expressions with interpolation syntax
${...}. - Function Support: Register custom functions (e.g.,
upper) that can be used in expressions. - Unary and Binary Operators: Handles arithmetic, relational, and unary operators (like
-and!). - Block and Map Structures: Easily define groups of configuration parameters using blocks or maps.
- Environment Variable Lookup: Lookup system environment variables with syntax like
${env.VAR_NAME}. - Include Directive: Incorporates external configuration files or remote resources using the
@includekeyword. - Control Structures: Basic support for control statements like
IF,ELSEIF, andELSEto drive conditional configuration.
- Dynamic Expression Evaluation:
text = "Current version: ${version}"
- Function Support:
bcl.RegisterFunction("upper", func(params ...any) (any, error) {
if len(params) == 0 {
return nil, errors.New("At least one param required")
}
str, ok := params[0].(string)
if !ok {
str = fmt.Sprint(params[0])
}
return strings.ToUpper(str), nil
})- Unary and Binary Operators:
negNumber = -10
notTrue = !false
calc = 20 + 5 * 2
- Block Structures:
database db1 {
host = "127.0.0.1"
port = 5432
username = "user"
password = "pass"
}
- Map Structures:
settings = {
debug = true
timeout = 30
rateLimit = 100
}
- Slice Examples (Primitive Types):
fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4, 5]
- Slice Examples (Objects):
users = [
{
name = "Alice"
age = 30
}
{
name = "Bob"
age = 25
}
]
- Environment Variable Lookup:
defaultShell = "${env.SHELL:/bin/bash}"
- Include Directive:
@include "other_config.bcl"
- Control Structures:
IF (settings.debug) {
logLevel = "verbose"
} ELSE {
logLevel = "normal"
}
Include configuration from a remote URL:
@include "https://raw.githubusercontent.com/oarkflow/proj/config.bcl"
Define a multi-line string with preserved line breaks:
description = <<EOF
This is a multi-line string.
It can include several lines,
and even special characters like # without being treated as a comment.
EOF
Comments are supported and can be added using the hash symbol:
# This is a comment and will be ignored by the parser.
appName = "SampleApp"
Below is a comprehensive configuration example that demonstrates maps, slices, blocks, external file inclusion, multi-line strings, dynamic expressions, environment variable lookup, function calls, and control structures:
appName = "Boilerplate"
version = 1.2
@include "credentials.bcl"
@include "https://raw.githubusercontent.com/oarkflow/proj/config.bcl"
# Block example for server configuration
server main {
host = "localhost"
port = 8080
secure = false
}
# Block example for database configuration
database db1 {
host = "127.0.0.1"
port = 5432
username = "user"
password = "pass"
}
# Map example for settings (note: maps are different from blocks)
settings = {
debug = true
timeout = 30
rateLimit = 100
}
# Slice example for users list
users = ["alice", "bob", "charlie"]
# Multi-line string using heredoc
description = <<EOF
This configuration demonstrates:
- External file inclusion via URL
- Blocks vs maps vs slices
- Dynamic expression evaluation
- Function calls and env variable lookup
EOF
# Dynamic expression using a registered function and env lookup
greeting = "Welcome to ${upper(appName)}"
envInfo = "Home directory: ${env.HOME}"
# Control structure example based on settings
IF (settings.debug) {
logLevel = "verbose"
} ELSE {
logLevel = "normal"
}
import (
"fmt"
"github.com/oarkflow/bcl"
)
func exampleUnmarshalString() {
configStr := `
appName = "Boilerplate"
version = 1.2
greeting = "Welcome to ${upper(appName)}"
`
var cfg map[string]any
nodes, err := bcl.Unmarshal([]byte(configStr), &cfg)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Unmarshaled Config:", cfg)
fmt.Println("Marshaled AST:")
fmt.Println(bcl.MarshalAST(nodes))
}import (
"fmt"
"os"
"github.com/oarkflow/bcl"
)
func exampleUnmarshalFile() {
data, err := os.ReadFile("config.bcl")
if err != nil {
fmt.Println("Error reading file:", err)
return
}
var cfg map[string]any
nodes, err := bcl.Unmarshal(data, &cfg)
if err != nil {
fmt.Println("Error unmarshaling:", err)
return
}
fmt.Println("Unmarshaled Config from file:", cfg)
fmt.Println("Marshaled AST:")
fmt.Println(bcl.MarshalAST(nodes))
}For more details, please review the source code and usage examples provided in the repository.